Files
nocobase-mcp/src/config.ts
T

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;
}