import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { isRecord } from "./types.js"; export const PROJECT_PROGRESS_VALUES: Record = { 询价阶段: "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; 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; } 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, source: Record): 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 { if (!isRecord(raw)) return {}; const normalized: Record = {}; 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, normalizeSection(collections.customers), ); assignKnown(config.contact as unknown as Record, normalizeSection(collections.contacts)); assignKnown( config.followup as unknown as Record, normalizeSection(collections.followups), ); assignKnown( config.user as unknown as Record, normalizeSection(parsed.users ?? parsed.user), ); assignKnown( config.team as unknown as Record, normalizeSection(parsed.teams ?? parsed.team), ); assignKnown(config.project as unknown as Record, 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; }