300 lines
9.3 KiB
TypeScript
300 lines
9.3 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { isRecord } from "./types.js";
|
|
|
|
export const PROJECT_PROGRESS_VALUES: Record<string, string> = {
|
|
询价阶段: "fk9xj8akn46",
|
|
已报价: "k4w2vcz819w",
|
|
送样: "g95vqeetu83",
|
|
已定点: "fe0peg505d7",
|
|
SOP: "a9t9awhtt3r",
|
|
终止: "a9t9awhtsfasg3r",
|
|
};
|
|
|
|
export interface CustomerConfig {
|
|
resource: string;
|
|
idField: string;
|
|
fields: string[] | null;
|
|
searchFields: string[] | null;
|
|
nameFields: string[] | null;
|
|
englishNameField: string;
|
|
territorialNameField: string;
|
|
statusField: string;
|
|
sourceField: string;
|
|
ownerField: string;
|
|
managerField: string;
|
|
detailFields: Record<string, string>;
|
|
sort: string[] | null;
|
|
}
|
|
|
|
export interface ContactConfig {
|
|
resource: string;
|
|
idField: string;
|
|
fields: string[] | null;
|
|
searchFields: string[] | null;
|
|
nameFields: string[] | null;
|
|
titleField: string;
|
|
phoneField: string;
|
|
emailField: string;
|
|
customerField: string;
|
|
sort: string[] | null;
|
|
}
|
|
|
|
export interface FollowupConfig {
|
|
resource: string;
|
|
idField: string;
|
|
fields: string[] | null;
|
|
searchFields: string[] | null;
|
|
customerField: string;
|
|
contactField: string;
|
|
ownerField: string;
|
|
ownerFields: string[] | null;
|
|
dateField: string;
|
|
contentField: string;
|
|
typeField: string;
|
|
statusField: string;
|
|
projectField: string;
|
|
locationField: string;
|
|
sort: string[] | null;
|
|
}
|
|
|
|
export interface UserConfig {
|
|
resource: string;
|
|
idField: string;
|
|
fields: string[] | null;
|
|
matchFields: string[] | null;
|
|
labelFields: string[] | null;
|
|
}
|
|
|
|
export interface TeamConfig {
|
|
resource: string;
|
|
idField: string;
|
|
nameField: string;
|
|
managerField: string;
|
|
memberField: string;
|
|
userStatusField: string;
|
|
formalUserStatusValue: string;
|
|
}
|
|
|
|
export interface ProjectConfig {
|
|
resource: string;
|
|
idField: string;
|
|
nameField: string;
|
|
nameFields: string[] | null;
|
|
customerField: string;
|
|
customerRelationField: string;
|
|
inquiryDateField: string;
|
|
progressField: string;
|
|
applicationsField: string;
|
|
partNumbersField: string;
|
|
periodUsageField: string;
|
|
detailsField: string;
|
|
attachmentField: string;
|
|
contactField: string;
|
|
followupField: string;
|
|
updateField: string;
|
|
updateResource: string;
|
|
updateProjectField: string;
|
|
updateDateField: string;
|
|
updateContentField: string;
|
|
updateAttachmentField: string;
|
|
progressValues: Record<string, string>;
|
|
}
|
|
|
|
export interface CrmConfig {
|
|
dataSource: string;
|
|
timezone: string;
|
|
customer: CustomerConfig;
|
|
contact: ContactConfig;
|
|
followup: FollowupConfig;
|
|
user: UserConfig;
|
|
team: TeamConfig;
|
|
project: ProjectConfig;
|
|
}
|
|
|
|
export const DEFAULT_CONFIG: CrmConfig = {
|
|
dataSource: "main",
|
|
timezone: "Asia/Shanghai",
|
|
customer: {
|
|
resource: "customers",
|
|
idField: "id",
|
|
fields: ["id", "name", "code", "status", "source", "owner", "createdAt", "updatedAt"],
|
|
searchFields: ["name", "code"],
|
|
nameFields: ["f_q2q60q7bqbg", "f_kh_english_name", "f_territorial_name", "name", "title", "code"],
|
|
englishNameField: "f_kh_english_name",
|
|
territorialNameField: "f_territorial_name",
|
|
statusField: "status",
|
|
sourceField: "source",
|
|
ownerField: "owner",
|
|
managerField: "kehu_manage_person",
|
|
detailFields: {
|
|
address: "f_y8iyf2jhana",
|
|
intro: "f_spgoie0arjq",
|
|
tags: "kh_tags",
|
|
followup_interval_days: "f_kyf51uh0ky7",
|
|
},
|
|
sort: ["-updatedAt"],
|
|
},
|
|
contact: {
|
|
resource: "contacts",
|
|
idField: "id",
|
|
fields: ["id", "name", "phone", "email", "title", "customer", "createdAt", "updatedAt"],
|
|
searchFields: ["name", "phone", "email"],
|
|
nameFields: ["f_2zpg91gtmwp", "name", "title"],
|
|
titleField: "f_tn8zdm2f938",
|
|
phoneField: "khlxr_phone_number",
|
|
emailField: "khlxr_email",
|
|
customerField: "customer",
|
|
sort: ["-updatedAt"],
|
|
},
|
|
followup: {
|
|
resource: "followups",
|
|
idField: "id",
|
|
fields: [
|
|
"id",
|
|
"customer",
|
|
"contact",
|
|
"owner",
|
|
"content",
|
|
"followedAt",
|
|
"nextFollowAt",
|
|
"createdAt",
|
|
"updatedAt",
|
|
],
|
|
searchFields: ["content"],
|
|
customerField: "customer",
|
|
contactField: "contact",
|
|
ownerField: "owner",
|
|
ownerFields: ["owner"],
|
|
dateField: "followedAt",
|
|
contentField: "f_19iplxz0nds",
|
|
typeField: "gjjl_followup_type",
|
|
statusField: "f_hskl7gjvos9",
|
|
projectField: "f_gjjl_projects",
|
|
locationField: "f_b5c4hfi5og6",
|
|
sort: ["-followedAt", "-createdAt"],
|
|
},
|
|
user: {
|
|
resource: "users",
|
|
idField: "id",
|
|
fields: ["id", "username", "nickname", "email", "f_yh_english_name"],
|
|
matchFields: ["username", "nickname", "email", "f_yh_english_name"],
|
|
labelFields: ["nickname", "f_yh_english_name", "username", "email"],
|
|
},
|
|
team: {
|
|
resource: "t_team_management",
|
|
idField: "id",
|
|
nameField: "f_team_name",
|
|
managerField: "tdgl_manager",
|
|
memberField: "tdgl_team_members",
|
|
userStatusField: "yh_status",
|
|
formalUserStatusValue: "nkluuumb1r1",
|
|
},
|
|
project: {
|
|
resource: "t_projects",
|
|
idField: "id",
|
|
nameField: "f_project_name",
|
|
nameFields: ["f_project_name", "name", "title"],
|
|
customerField: "client_ID",
|
|
customerRelationField: "f_projects_client",
|
|
inquiryDateField: "xm_project_inquiry_date",
|
|
progressField: "f_projects_progress",
|
|
applicationsField: "f_projects_applications",
|
|
partNumbersField: "f_projects_part_numbers",
|
|
periodUsageField: "f_projects_period_usage",
|
|
detailsField: "f_projects_details",
|
|
attachmentField: "f_projects_attachments",
|
|
contactField: "f_projects_contacts",
|
|
followupField: "f_projects_followup_records",
|
|
updateField: "f_u28gm9h2uzl",
|
|
updateResource: "t_qxeepjvi39s",
|
|
updateProjectField: "f_k37rbpi4vye",
|
|
updateDateField: "f_6g9fw5oqef9",
|
|
updateContentField: "f_ei16g0ihwwd",
|
|
updateAttachmentField: "f_6pk5pv17i27",
|
|
progressValues: { ...PROJECT_PROGRESS_VALUES },
|
|
},
|
|
};
|
|
|
|
function cloneDefaults(): CrmConfig {
|
|
return structuredClone(DEFAULT_CONFIG);
|
|
}
|
|
|
|
function assignKnown(target: Record<string, unknown>, source: Record<string, unknown>): void {
|
|
for (const [key, value] of Object.entries(source)) {
|
|
if (!(key in target) || value === undefined || value === null || value === "") continue;
|
|
const current = target[key];
|
|
if (Array.isArray(current)) {
|
|
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
throw new Error(`CRM config ${key} must be an array of strings`);
|
|
}
|
|
target[key] = [...value];
|
|
continue;
|
|
}
|
|
if (typeof current === "string") {
|
|
if (typeof value !== "string") throw new Error(`CRM config ${key} must be a string`);
|
|
target[key] = value;
|
|
continue;
|
|
}
|
|
if (isRecord(current)) {
|
|
if (!isRecord(value)) throw new Error(`CRM config ${key} must be an object`);
|
|
const stringMap = Object.values(current).every((item) => typeof item === "string");
|
|
if (stringMap) {
|
|
if (!Object.values(value).every((item) => typeof item === "string")) {
|
|
throw new Error(`CRM config ${key} must map strings to strings`);
|
|
}
|
|
target[key] = { ...value };
|
|
} else assignKnown(current, value);
|
|
continue;
|
|
}
|
|
throw new Error(`CRM config ${key} has an unsupported value`);
|
|
}
|
|
}
|
|
|
|
function normalizeSection(raw: unknown): Record<string, unknown> {
|
|
if (!isRecord(raw)) return {};
|
|
const normalized: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(raw)) {
|
|
normalized[key.replace(/_([a-z])/g, (_match, letter: string) => letter.toUpperCase())] = value;
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function loadConfig(path = process.env.NOCOBASE_MCP_CONFIG): CrmConfig {
|
|
const config = cloneDefaults();
|
|
if (path) {
|
|
const parsed: unknown = JSON.parse(readFileSync(resolve(path), "utf8"));
|
|
if (!isRecord(parsed)) throw new Error("CRM config must be a JSON object");
|
|
const collections = isRecord(parsed.collections) ? parsed.collections : {};
|
|
assignKnown(
|
|
config.customer as unknown as Record<string, unknown>,
|
|
normalizeSection(collections.customers),
|
|
);
|
|
assignKnown(config.contact as unknown as Record<string, unknown>, normalizeSection(collections.contacts));
|
|
assignKnown(
|
|
config.followup as unknown as Record<string, unknown>,
|
|
normalizeSection(collections.followups),
|
|
);
|
|
assignKnown(
|
|
config.user as unknown as Record<string, unknown>,
|
|
normalizeSection(parsed.users ?? parsed.user),
|
|
);
|
|
assignKnown(
|
|
config.team as unknown as Record<string, unknown>,
|
|
normalizeSection(parsed.teams ?? parsed.team),
|
|
);
|
|
assignKnown(config.project as unknown as Record<string, unknown>, normalizeSection(parsed.projects));
|
|
const dataSource = parsed.dataSource ?? parsed.data_source;
|
|
if (typeof dataSource === "string" && dataSource.trim()) config.dataSource = dataSource.trim();
|
|
if (typeof parsed.timezone === "string" && parsed.timezone.trim())
|
|
config.timezone = parsed.timezone.trim();
|
|
}
|
|
if (process.env.NOCOBASE_MCP_DATA_SOURCE?.trim())
|
|
config.dataSource = process.env.NOCOBASE_MCP_DATA_SOURCE.trim();
|
|
if (process.env.NOCOBASE_MCP_TIMEZONE?.trim()) config.timezone = process.env.NOCOBASE_MCP_TIMEZONE.trim();
|
|
if (!config.followup.ownerFields)
|
|
config.followup.ownerFields = config.followup.ownerField ? [config.followup.ownerField] : [];
|
|
if (!config.project.nameFields) config.project.nameFields = [config.project.nameField, "name", "title"];
|
|
return config;
|
|
}
|