feat: expose readable submission choices
This commit is contained in:
+188
-69
@@ -1515,6 +1515,11 @@ function normalizeSubmissionField(value: JsonObject): XftSubmissionField[] {
|
||||
const name = stringAt(value, ["name"]);
|
||||
const type = stringAt(value, ["type"]);
|
||||
if (!fieldNumber || !name || !type || value.isDisplay === false) return [];
|
||||
const options = Array.isArray(value.options) ? normalizeSubmissionOptions(value.options) : [];
|
||||
const normalizedType = type.toUpperCase();
|
||||
const optionSource = isExpenseTypeField(fieldNumber, name) ? expenseTypeOptionSource()
|
||||
: normalizedType.includes("DEPARTMENT") || normalizedType.includes("ORG") ? departmentOptionSource()
|
||||
: /城市|city/i.test(`${fieldNumber} ${name}`) ? cityOptionSource() : undefined;
|
||||
return [{
|
||||
fieldNumber,
|
||||
name,
|
||||
@@ -1522,10 +1527,10 @@ function normalizeSubmissionField(value: JsonObject): XftSubmissionField[] {
|
||||
required: value.required === true,
|
||||
...(typeof value.description === "string" && value.description.trim() ? { description: value.description.trim() } : {}),
|
||||
...(typeof value.multiple === "boolean" ? { multiple: value.multiple } : {}),
|
||||
...(Array.isArray(value.options) ? { options: value.options.map((option) => sanitizeOption(option)).filter((option) => option !== undefined) } : {}),
|
||||
...(options.length ? { options } : {}),
|
||||
...(typeof value.moduleType === "string" && value.moduleType ? { moduleType: value.moduleType } : {}),
|
||||
...(isExpenseTypeField(fieldNumber, name) ? { optionSource: expenseTypeOptionSource() } : {}),
|
||||
...submissionFieldShape(value, type),
|
||||
...(optionSource ? { optionSource } : {}),
|
||||
...submissionFieldShape(value, type, options, optionSource),
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -1537,32 +1542,89 @@ function expenseTypeOptionSource() {
|
||||
return { tool: "approval_get_submission_options" as const, kind: "expense_type" as const };
|
||||
}
|
||||
|
||||
function submissionFieldShape(value: JsonObject, type: string): Pick<XftSubmissionField, "valueFormat" | "itemFields"> {
|
||||
function cityOptionSource() {
|
||||
return { tool: "approval_get_submission_options" as const, kind: "city" as const };
|
||||
}
|
||||
|
||||
function departmentOptionSource() {
|
||||
return { tool: "approval_get_submission_options" as const, kind: "department" as const };
|
||||
}
|
||||
|
||||
const LABEL_ONLY_SELECTION = {
|
||||
status: "supported" as const,
|
||||
userInput: "label" as const,
|
||||
submitValue: "code" as const,
|
||||
exposeInternalValue: false as const,
|
||||
};
|
||||
|
||||
function clientOnlySelection(reason: string) {
|
||||
return { status: "client_only" as const, reason };
|
||||
}
|
||||
|
||||
function submissionFieldShape(
|
||||
value: JsonObject,
|
||||
type: string,
|
||||
options: XftSubmissionOption[],
|
||||
optionSource?: ReturnType<typeof expenseTypeOptionSource> | ReturnType<typeof cityOptionSource> | ReturnType<typeof departmentOptionSource>,
|
||||
): Pick<XftSubmissionField, "valueFormat" | "itemFields" | "selectionCapability"> {
|
||||
const normalized = type.toUpperCase();
|
||||
if (normalized.includes("TABLE")) {
|
||||
const candidates = [value.columns, value.children, value.fieldList, value.tableColumns]
|
||||
.find(Array.isArray) as unknown[] | undefined;
|
||||
const itemFields = (candidates ?? []).filter(isObject).flatMap((column) => {
|
||||
const titleCode = stringAt(column, ["titleCode", "fieldNumber", "fieldCode", "code"]);
|
||||
const label = stringAt(column, ["name", "title", "label"]);
|
||||
const columnType = stringAt(column, ["type", "fieldType"]);
|
||||
if (!titleCode) return [];
|
||||
return [{ titleCode, ...(label ? { label } : {}), ...(columnType ? { type: columnType } : {}), required: column.required === true }];
|
||||
});
|
||||
const itemFields = (candidates ?? []).filter(isObject).flatMap(normalizeSubmissionTableColumn);
|
||||
return {
|
||||
valueFormat: "JSON array: [{sequence:1,columnValues:[{titleCode,value}]}]",
|
||||
...(itemFields.length ? { itemFields } : {}),
|
||||
};
|
||||
}
|
||||
if (normalized.includes("ATTACH") || normalized.includes("ACCESSOR")) return { valueFormat: "附件编号字符串(须先通过薪福通附件接口取得)" };
|
||||
if (normalized.includes("PERSON") || normalized.includes("EMP")) return { valueFormat: "薪福通花名册编号 JSON 数组" };
|
||||
if (normalized.includes("DEPARTMENT") || normalized.includes("ORG")) return { valueFormat: "薪福通组织编码 JSON 数组" };
|
||||
if (normalized.includes("ATTACH") || normalized.includes("ACCESSOR")) return {
|
||||
valueFormat: "当前 MCP 无法安全上传附件,请在薪福通客户端补充",
|
||||
selectionCapability: clientOnlySelection("当前 MCP 没有薪福通附件上传能力"),
|
||||
};
|
||||
if (normalized.includes("PERSON") || normalized.includes("EMP")) return {
|
||||
valueFormat: "当前 MCP 无法按姓名安全选择人员,请在薪福通客户端补充",
|
||||
selectionCapability: clientOnlySelection("正式接口需要内部花名册值,但当前没有按姓名查询人员的安全接口"),
|
||||
};
|
||||
if (normalized.includes("DEPARTMENT") || normalized.includes("ORG")) return {
|
||||
valueFormat: "向用户展示部门名称,Agent 内部使用所选项",
|
||||
selectionCapability: LABEL_ONLY_SELECTION,
|
||||
};
|
||||
if (normalized === "DATE") return { valueFormat: "yyyy-MM-dd HH:mm" };
|
||||
if (normalized.includes("SELECT")) return { valueFormat: "使用 options 中的编码" };
|
||||
if (normalized.includes("SELECT")) {
|
||||
if (options.length || optionSource) return {
|
||||
valueFormat: "向用户展示选项名称,Agent 内部使用所选项",
|
||||
selectionCapability: LABEL_ONLY_SELECTION,
|
||||
};
|
||||
return {
|
||||
valueFormat: "当前模板未返回可读选项,请在薪福通客户端补充",
|
||||
selectionCapability: clientOnlySelection("模板没有返回可验证的名称与内部值映射"),
|
||||
};
|
||||
}
|
||||
if (normalized.includes("INPUT")) return { valueFormat: "文本字符串" };
|
||||
return {};
|
||||
}
|
||||
|
||||
function normalizeSubmissionTableColumn(column: JsonObject): Array<Record<string, unknown>> {
|
||||
const titleCode = stringAt(column, ["titleCode", "fieldNumber", "fieldCode", "code"]);
|
||||
const label = stringAt(column, ["name", "title", "label"]);
|
||||
const type = stringAt(column, ["type", "fieldType"]);
|
||||
if (!titleCode) return [];
|
||||
const options = Array.isArray(column.options) ? normalizeSubmissionOptions(column.options) : [];
|
||||
const normalizedType = (type ?? "").toUpperCase();
|
||||
const optionSource = isExpenseTypeField(titleCode, label ?? "") ? expenseTypeOptionSource()
|
||||
: normalizedType.includes("DEPARTMENT") || normalizedType.includes("ORG") ? departmentOptionSource()
|
||||
: /城市|city/i.test(`${titleCode} ${label ?? ""}`) ? cityOptionSource() : undefined;
|
||||
return [{
|
||||
titleCode,
|
||||
...(label ? { label } : {}),
|
||||
...(type ? { type } : {}),
|
||||
required: column.required === true,
|
||||
...(options.length ? { options } : {}),
|
||||
...(optionSource ? { optionSource } : {}),
|
||||
...submissionFieldShape(column, type ?? "", options, optionSource),
|
||||
}];
|
||||
}
|
||||
|
||||
function deduplicateSubmissionFields(fields: XftSubmissionField[]): XftSubmissionField[] {
|
||||
const seen = new Set<string>();
|
||||
return fields.filter((field) => {
|
||||
@@ -1575,13 +1637,16 @@ function deduplicateSubmissionFields(fields: XftSubmissionField[]): XftSubmissio
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeOption(value: unknown): unknown {
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
||||
function sanitizeOption(value: unknown): XftSubmissionOption | undefined {
|
||||
if (!isObject(value)) return undefined;
|
||||
const label = stringAt(value, ["label", "name", "value", "optionName"]);
|
||||
const code = stringAt(value, ["code", "number", "optionCode", "key"]);
|
||||
if (!label && !code) return undefined;
|
||||
return { ...(label ? { label } : {}), ...(code ? { code } : {}) };
|
||||
const label = stringAt(value, ["label", "name", "optionName", "displayName", "value"]);
|
||||
const code = stringAt(value, ["code", "number", "optionCode", "key", "value"]);
|
||||
if (!label || !code) return undefined;
|
||||
return { label, code };
|
||||
}
|
||||
|
||||
function normalizeSubmissionOptions(values: unknown[]): XftSubmissionOption[] {
|
||||
return uniqueOptions(values.map(sanitizeOption).filter((item): item is XftSubmissionOption => Boolean(item)));
|
||||
}
|
||||
|
||||
function submissionInputContract(kind: XftSubmissionKind): Record<string, unknown> {
|
||||
@@ -1632,13 +1697,67 @@ function submissionInputContractSummary(kind: XftSubmissionKind): Record<string,
|
||||
return values[kind];
|
||||
}
|
||||
|
||||
function cityField(key: string, label: string, required: boolean): Record<string, unknown> {
|
||||
return {
|
||||
key, label, required, type: "string",
|
||||
description: "向用户展示城市名称,Agent 内部使用所选项",
|
||||
optionSource: cityOptionSource(),
|
||||
selectionCapability: LABEL_ONLY_SELECTION,
|
||||
};
|
||||
}
|
||||
|
||||
function expenseTypeField(key: string, required: boolean): Record<string, unknown> {
|
||||
return {
|
||||
key, label: "费用类型", required, type: "string",
|
||||
description: "向用户展示费用名称,Agent 内部使用所选项",
|
||||
optionSource: expenseTypeOptionSource(),
|
||||
selectionCapability: LABEL_ONLY_SELECTION,
|
||||
};
|
||||
}
|
||||
|
||||
function readableEnumField(
|
||||
key: string,
|
||||
label: string,
|
||||
required: boolean,
|
||||
labels: Record<string, string>,
|
||||
extra: Record<string, unknown> = {},
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
key, label, required, type: "string", enum: Object.keys(labels), enumLabels: labels,
|
||||
description: "只向用户展示中文选项名称,Agent 内部使用对应值",
|
||||
selectionCapability: LABEL_ONLY_SELECTION,
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function clientOnlyField(
|
||||
key: string,
|
||||
label: string,
|
||||
required: boolean,
|
||||
reason: string,
|
||||
extra: Record<string, unknown> = {},
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
key, label, required, ...extra,
|
||||
selectionCapability: clientOnlySelection(reason),
|
||||
description: `${reason};不要向用户索取内部编码或 ID,如需填写请在薪福通客户端完成`,
|
||||
};
|
||||
}
|
||||
|
||||
const TRAVEL_MODE_LABELS = {
|
||||
Train: "火车", Bus: "汽车", Ship: "轮船", Hotel: "住宿", RideHailing: "网约车", AirPlane: "飞机", Other: "其他",
|
||||
} as const;
|
||||
const ROUND_TRIP_LABELS = { N: "单程", Y: "往返" } as const;
|
||||
const HALF_DAY_LABELS = { AM: "上午", PM: "下午" } as const;
|
||||
const CURRENCY_LABELS = { CNY: "人民币" } as const;
|
||||
|
||||
function expenseApplicationFields(): Array<Record<string, unknown>> {
|
||||
return [{
|
||||
key: "applyExpenses", label: "事前申请费用列表", required: true, type: "array", minItems: 1, itemFields: [
|
||||
{ key: "expenseNumber", label: "费用类型", required: true, type: "string", description: "向用户展示可读名称,内部填写所选项的 code", optionSource: expenseTypeOptionSource() },
|
||||
expenseTypeField("expenseNumber", true),
|
||||
{ key: "amount", label: "申请金额", required: true, type: "decimal", precision: 2 },
|
||||
{ key: "occurrenceDate", label: "费用发生时间", required: false, type: "datetime", description: "ISO 8601 时间" },
|
||||
{ key: "payCityCode", label: "消费城市编码", required: false, type: "string" },
|
||||
cityField("payCityCode", "消费城市", false),
|
||||
{ key: "participantNum", label: "参与人数", required: false, type: "integer" },
|
||||
{ key: "merchantName", label: "商户名称", required: false, type: "string" },
|
||||
{ key: "remark", label: "备注", required: false, type: "string" },
|
||||
@@ -1650,7 +1769,7 @@ function expenseApplicationFields(): Array<Record<string, unknown>> {
|
||||
function entertainmentApplicationFields(): Array<Record<string, unknown>> {
|
||||
return [{
|
||||
key: "entertainmentApplyApiCreateDTO", label: "招待信息", required: true, type: "object", fields: [
|
||||
{ key: "entertainmentCityCode", label: "招待城市编码", required: true, type: "string", description: "薪福通城市 cityCode" },
|
||||
cityField("entertainmentCityCode", "招待城市", true),
|
||||
{ key: "entertainmentPersonCount", label: "招待人数", required: true, type: "integer", minimum: 1 },
|
||||
{ key: "entertainmentStartTime", label: "招待开始日期", required: true, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "entertainmentEndTime", label: "招待结束日期", required: true, type: "date", format: "yyyy-MM-dd" },
|
||||
@@ -1663,44 +1782,44 @@ function travelApplicationFields(): Array<Record<string, unknown>> {
|
||||
key: "businessTrip", label: "差旅行程", required: true, type: "object", fields: [{
|
||||
key: "businessTripDetails", label: "行程明细", required: true, type: "array", minItems: 1,
|
||||
itemFields: [
|
||||
{ key: "departCityCode", label: "出发城市编码", required: true, type: "string" },
|
||||
{ key: "destinationCityCode", label: "目的城市编码", required: true, type: "string" },
|
||||
cityField("departCityCode", "出发城市", true),
|
||||
cityField("destinationCityCode", "目的城市", true),
|
||||
{ key: "beginTime", label: "开始时间", required: true, type: "datetime", format: "yyyy-MM-dd HH:mm" },
|
||||
{ key: "endTime", label: "结束时间", required: true, type: "datetime", format: "yyyy-MM-dd HH:mm" },
|
||||
{ key: "travelMode", label: "出行方式", required: false, type: "comma_separated_enum", enum: ["Train", "Bus", "Ship", "Hotel", "RideHailing", "AirPlane", "Other"] },
|
||||
{ key: "roundTrip", label: "是否往返", required: false, type: "string", enum: ["N", "Y"] },
|
||||
{ key: "beginTimePrecision", label: "开始时间半天精度", required: false, type: "string", enum: ["AM", "PM"] },
|
||||
{ key: "endTimePrecision", label: "结束时间半天精度", required: false, type: "string", enum: ["AM", "PM"] },
|
||||
{ key: "customArchiveOptionNumbers", label: "行程自定义档案选项", required: false, type: "array" },
|
||||
readableEnumField("travelMode", "出行方式", false, TRAVEL_MODE_LABELS, { type: "comma_separated_enum", multiple: true }),
|
||||
readableEnumField("roundTrip", "是否往返", false, ROUND_TRIP_LABELS),
|
||||
readableEnumField("beginTimePrecision", "开始时间范围", false, HALF_DAY_LABELS),
|
||||
readableEnumField("endTimePrecision", "结束时间范围", false, HALF_DAY_LABELS),
|
||||
clientOnlyField("customArchiveOptionNumbers", "行程自定义档案", false, "当前 MCP 没有按名称查询企业自定义档案的可靠能力", { type: "array" }),
|
||||
{ key: "estimateExpenses", label: "预计费用", required: false, type: "array", itemFields: [
|
||||
{ key: "expenseTypeNumber", label: "费用类型", required: true, type: "string", description: "向用户展示可读名称,内部填写所选项的 code", optionSource: expenseTypeOptionSource() },
|
||||
expenseTypeField("expenseTypeNumber", true),
|
||||
{ key: "applyAmount", label: "申请金额", required: true, type: "decimal" },
|
||||
{ key: "remark", label: "备注", required: false, type: "string" },
|
||||
{ key: "spreadInfo", label: "分摊信息", required: false, type: "object" },
|
||||
clientOnlyField("spreadInfo", "分摊信息", false, "当前 MCP 无法把企业分摊名称安全映射为正式接口值", { type: "object" }),
|
||||
] },
|
||||
],
|
||||
}] },
|
||||
{ key: "relatedBillId", label: "关联单据 ID", required: false, type: "string" },
|
||||
{ key: "peerEmpNumbers", label: "同行人员工号", required: false, type: "array" },
|
||||
{ key: "billCompanyCode", label: "单据公司编码", required: false, type: "string" },
|
||||
{ key: "billDepartmentCode", label: "单据部门编码", required: false, type: "string" },
|
||||
clientOnlyField("relatedBillId", "关联申请单", false, "当前 MCP 没有按业务名称安全选择前置单据的能力", { type: "string" }),
|
||||
clientOnlyField("peerEmpNumbers", "同行人员", false, "当前 MCP 没有按姓名查询薪福通人员的安全接口", { type: "array" }),
|
||||
{ key: "billCompanyCode", label: "单据公司", required: false, type: "string", serverManaged: true, userVisible: false, description: "省略后由薪福通按当前登录用户填写" },
|
||||
{ key: "billDepartmentCode", label: "单据部门", required: false, type: "string", serverManaged: true, userVisible: false, description: "省略后由薪福通按当前登录用户填写" },
|
||||
];
|
||||
}
|
||||
|
||||
function reimbursementHeaderFields(): Array<Record<string, unknown>> {
|
||||
return [
|
||||
{ key: "reimAmount", label: "报销总金额", required: true, type: "decimal", precision: 2 },
|
||||
{ key: "reimAmountCurrencyCode", label: "报销币种", required: true, type: "string", enum: ["CNY"], description: "正式接口目前仅支持人民币" },
|
||||
{ key: "outRelId", label: "外部关联 ID", required: false, type: "string", maxLength: 100 },
|
||||
{ key: "billDepartmentCode", label: "单据部门编码", required: false, type: "string", maxLength: 100 },
|
||||
{ key: "billCompanyCode", label: "单据公司编码", required: false, type: "string", maxLength: 50 },
|
||||
{ key: "customFieldValues", label: "单据自定义字段", required: false, type: "array", itemFields: customFieldValueFields() },
|
||||
{ key: "billCollectionInfo", label: "收款信息", required: false, type: "object", description: "需要付款时填写;包含收款人、银行、账号、金额和币种" },
|
||||
{ key: "billLendPayRelList", label: "核销借款信息", required: false, type: "array", itemFields: [
|
||||
readableEnumField("reimAmountCurrencyCode", "报销币种", true, CURRENCY_LABELS, { default: "CNY" }),
|
||||
{ key: "outRelId", label: "外部关联标识", required: false, type: "string", maxLength: 100, userVisible: false, description: "仅供系统集成使用,不应询问普通用户" },
|
||||
{ key: "billDepartmentCode", label: "单据部门", required: false, type: "string", maxLength: 100, serverManaged: true, userVisible: false, description: "省略后由薪福通按当前登录用户填写" },
|
||||
{ key: "billCompanyCode", label: "单据公司", required: false, type: "string", maxLength: 50, serverManaged: true, userVisible: false, description: "省略后由薪福通按当前登录用户填写" },
|
||||
{ key: "customFieldValues", label: "单据自定义字段", required: false, type: "array", userVisible: false, description: "由 Agent 根据实时 fields 组装,不向用户展示字段编码", itemFields: customFieldValueFields() },
|
||||
clientOnlyField("billCollectionInfo", "收款信息", false, "当前 MCP 无法完整验证收款账户的正式接口引用", { type: "object" }),
|
||||
clientOnlyField("billLendPayRelList", "核销借款", false, "当前 MCP 没有按可读单号安全选择本人借款单的能力", { type: "array", itemFields: [
|
||||
{ key: "lendBillId", label: "借款单号", required: true, type: "string" },
|
||||
{ key: "currencyCode", label: "核销币种", required: true, type: "string", enum: ["CNY"] },
|
||||
readableEnumField("currencyCode", "核销币种", true, CURRENCY_LABELS, { default: "CNY" }),
|
||||
{ key: "originalPayAmount", label: "核销金额", required: true, type: "decimal", precision: 2 },
|
||||
] },
|
||||
] }),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1712,21 +1831,21 @@ function travelReimbursementFields(): Array<Record<string, unknown>> {
|
||||
return [
|
||||
...reimbursementHeaderFields(),
|
||||
expenseListField(true),
|
||||
{ key: "peerEmpNumbers", label: "同行人员工号", required: false, type: "array", maxItems: 50 },
|
||||
clientOnlyField("peerEmpNumbers", "同行人员", false, "当前 MCP 没有按姓名查询薪福通人员的安全接口", { type: "array", maxItems: 50 }),
|
||||
{
|
||||
key: "businessTrip", label: "差旅行程", required: true, type: "object", fields: [{
|
||||
key: "businessTripDetails", label: "行程明细", required: true, type: "array", minItems: 1,
|
||||
itemFields: [
|
||||
{ key: "departCityCode", label: "出发城市编码", required: true, type: "string", description: "薪福通城市 cityCode" },
|
||||
{ key: "destinationCityCode", label: "目的城市编码", required: true, type: "string", description: "薪福通城市 cityCode" },
|
||||
cityField("departCityCode", "出发城市", true),
|
||||
cityField("destinationCityCode", "目的城市", true),
|
||||
{ key: "beginTime", aliases: ["startTime"], label: "出发时间", required: true, type: "datetime", format: "yyyy-MM-dd HH:mm" },
|
||||
{ key: "endTime", label: "结束时间", required: true, type: "datetime", format: "yyyy-MM-dd HH:mm" },
|
||||
{ key: "travelMode", label: "出行方式", required: false, type: "comma_separated_enum", enum: ["Train", "Bus", "Ship", "Hotel", "RideHailing", "AirPlane", "Other"] },
|
||||
{ key: "roundTrip", label: "是否往返", required: false, type: "string", enum: ["N", "Y"], default: "N" },
|
||||
{ key: "beginTimePrecision", label: "开始时间半天精度", required: false, type: "string", enum: ["AM", "PM"], description: "模板时间精度为半天时必填" },
|
||||
{ key: "endTimePrecision", label: "结束时间半天精度", required: false, type: "string", enum: ["AM", "PM"], description: "模板时间精度为半天时必填" },
|
||||
readableEnumField("travelMode", "出行方式", false, TRAVEL_MODE_LABELS, { type: "comma_separated_enum", multiple: true }),
|
||||
readableEnumField("roundTrip", "是否往返", false, ROUND_TRIP_LABELS, { default: "N" }),
|
||||
readableEnumField("beginTimePrecision", "开始时间范围", false, HALF_DAY_LABELS, { condition: "模板时间精度为半天时必填" }),
|
||||
readableEnumField("endTimePrecision", "结束时间范围", false, HALF_DAY_LABELS, { condition: "模板时间精度为半天时必填" }),
|
||||
{ key: "tripReason", label: "出差事由", required: false, type: "string", maxLength: 200, description: "模板将行程事由设为必填时必须填写" },
|
||||
{ key: "customArchiveOptionNumbers", label: "行程自定义档案选项", required: false, type: "array" },
|
||||
clientOnlyField("customArchiveOptionNumbers", "行程自定义档案", false, "当前 MCP 没有按名称查询企业自定义档案的可靠能力", { type: "array" }),
|
||||
],
|
||||
}],
|
||||
},
|
||||
@@ -1735,25 +1854,25 @@ function travelReimbursementFields(): Array<Record<string, unknown>> {
|
||||
|
||||
function expenseListField(travel: boolean): Record<string, unknown> {
|
||||
const itemFields: Array<Record<string, unknown>> = [
|
||||
{ key: "expTypeCode", label: "费用类型", required: true, type: "string", maxLength: 100, description: "向用户展示可读名称,内部填写所选项的 code", optionSource: expenseTypeOptionSource() },
|
||||
{ key: "currencyCode", label: "费用币种", required: true, type: "string", enum: ["CNY"] },
|
||||
{ ...expenseTypeField("expTypeCode", true), maxLength: 100 },
|
||||
readableEnumField("currencyCode", "费用币种", true, CURRENCY_LABELS, { default: "CNY" }),
|
||||
{ key: "originalCurrencyReimAmt", label: "费用金额", required: true, type: "decimal", precision: 2 },
|
||||
{ key: "wrTim", label: "消费日期", required: false, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "remark", label: "费用备注", required: false, type: "string", maxLength: 500 },
|
||||
{ key: "merchantName", label: "商户名称", required: false, type: "string", maxLength: 200 },
|
||||
{ key: "participants", label: "费用参与人员工号", required: false, type: "array", maxItems: 50 },
|
||||
clientOnlyField("participants", "费用参与人员", false, "当前 MCP 没有按姓名查询薪福通人员的安全接口", { type: "array", maxItems: 50 }),
|
||||
{ key: "partiNbr", label: "参与人数", required: false, type: "integer" },
|
||||
{ key: "taxRate", label: "税率", required: false, type: "decimal", precision: 2 },
|
||||
{ key: "taxFreeAmount", label: "不含税金额", required: false, type: "decimal", precision: 2 },
|
||||
{ key: "departureCode", label: "费用出发城市编码", required: false, type: "string" },
|
||||
{ key: "destinationCode", label: "费用目的城市编码", required: false, type: "string" },
|
||||
cityField("departureCode", "费用出发城市", false),
|
||||
cityField("destinationCode", "费用目的城市", false),
|
||||
{ key: "bgnTim", label: "交通出发日期", required: false, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "endTim", label: "交通到达日期", required: false, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "checkInDate", label: "酒店入住日期", required: false, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "checkOutDate", label: "酒店离店日期", required: false, type: "date", format: "yyyy-MM-dd" },
|
||||
{ key: "payCityCode", label: "消费城市编码", required: false, type: "string" },
|
||||
{ key: "hotelStar", label: "酒店星级", required: false, type: "string", enum: ["1", "2", "3", "4"], enumLabels: { "1": "五星", "2": "四星", "3": "三星", "4": "其他" } },
|
||||
{ key: "aircraftCabin", label: "飞机舱位", required: false, type: "string", enum: ["1", "2", "3", "4"] },
|
||||
cityField("payCityCode", "消费城市", false),
|
||||
readableEnumField("hotelStar", "酒店星级", false, { "1": "五星", "2": "四星", "3": "三星", "4": "其他" }),
|
||||
clientOnlyField("aircraftCabin", "飞机舱位", false, "正式接口文档未提供可验证的舱位名称映射", { type: "string" }),
|
||||
{ key: "airlineNo", label: "航班号", required: false, type: "string" },
|
||||
{ key: "airlineCom", label: "航空公司", required: false, type: "string" },
|
||||
{ key: "flightETicketNo", label: "电子客票号", required: false, type: "string" },
|
||||
@@ -1761,20 +1880,20 @@ function expenseListField(travel: boolean): Record<string, unknown> {
|
||||
{ key: "civilAviationFund", label: "民航发展基金", required: false, type: "decimal", precision: 2 },
|
||||
{ key: "discount", label: "机票折扣", required: false, type: "decimal", precision: 2 },
|
||||
{ key: "trainNo", label: "列车号", required: false, type: "string" },
|
||||
{ key: "trainCabin", label: "火车席别", required: false, type: "string", description: "使用官方席别编码 1-15" },
|
||||
clientOnlyField("trainCabin", "火车席别", false, "正式接口文档未提供可验证的席别名称映射", { type: "string" }),
|
||||
{ key: "trainETicketNo", label: "火车票票号", required: false, type: "string" },
|
||||
{ key: "shipCabin", label: "轮船舱位", required: false, type: "string", enum: ["1", "2", "3"] },
|
||||
{ key: "customFieldValues", label: "费用自定义字段", required: false, type: "array", itemFields: customFieldValueFields() },
|
||||
{ key: "spreadInfo", label: "费用分摊", required: false, type: "object", description: "按企业分摊方式填写" },
|
||||
clientOnlyField("shipCabin", "轮船舱位", false, "正式接口文档未提供可验证的舱位名称映射", { type: "string" }),
|
||||
{ key: "customFieldValues", label: "费用自定义字段", required: false, type: "array", userVisible: false, description: "由 Agent 根据实时 fields 组装,不向用户展示字段编码", itemFields: customFieldValueFields() },
|
||||
clientOnlyField("spreadInfo", "费用分摊", false, "当前 MCP 无法把企业分摊名称安全映射为正式接口值", { type: "object" }),
|
||||
];
|
||||
if (travel) itemFields.push({ key: "dstntIndex", label: "归属行程序号", required: false, type: "string", description: "从 0 开始;不填则不归组到行程" });
|
||||
if (travel) itemFields.push({ key: "dstntIndex", label: "归属行程", required: false, type: "string", userVisible: false, internallyDerived: true, description: "由 Agent 根据用户选择的行程位置内部换算,不向用户展示序号" });
|
||||
return { key: "expenseList", label: "费用明细", required: true, type: "array", minItems: 1, itemFields };
|
||||
}
|
||||
|
||||
function customFieldValueFields(): Array<Record<string, unknown>> {
|
||||
return [
|
||||
{ key: "fieldNumber", label: "自定义字段编码", required: true, type: "string" },
|
||||
{ key: "fieldValue", label: "自定义字段值", required: true, type: "string", description: "复杂值按字段类型序列化为 JSON 字符串" },
|
||||
{ key: "fieldNumber", label: "字段引用", required: true, type: "string", userVisible: false, description: "由 Agent 从实时 Schema 内部复制,不向用户展示或索取" },
|
||||
{ key: "fieldValue", label: "字段值", required: true, type: "string", userVisible: false, description: "由 Agent 根据字段类型内部序列化" },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user