fix: keep submission previews user-readable
This commit is contained in:
@@ -1108,6 +1108,164 @@ test("Xft application templates support API submission with live post-write stat
|
||||
assert.equal(travelPrepared.status, "confirmation_required");
|
||||
});
|
||||
|
||||
test("Xft preparation preview resolves every selection to user-readable business names", async () => {
|
||||
const template: XftSubmissionTemplate = {
|
||||
eventSeqId: "event-travel", eventNumber: "XFT-TRAVEL", eventName: "差旅报销单",
|
||||
eventType: "TRAVEL_REIM", kind: "travel_reimburse",
|
||||
};
|
||||
const fields: XftSubmissionField[] = [
|
||||
{
|
||||
fieldNumber: "C-TOPIC", name: "申请事项", type: "SELECT", required: true,
|
||||
options: [{ code: "PROCUREMENT", label: "采购事项" }],
|
||||
selectionCapability: { status: "supported", userInput: "label", submitValue: "code", exposeInternalValue: false },
|
||||
},
|
||||
{
|
||||
fieldNumber: "C-TABLE", name: "补充明细", type: "TABLE", required: true,
|
||||
itemFields: [{
|
||||
titleCode: "T-TYPE", label: "明细类型", type: "SELECT", required: true,
|
||||
options: [{ code: "MARKETING", label: "市场活动" }],
|
||||
selectionCapability: { status: "supported", userInput: "label", submitValue: "code", exposeInternalValue: false },
|
||||
}],
|
||||
},
|
||||
];
|
||||
const standardFields = [
|
||||
{ key: "reimAmount", label: "报销总金额", type: "decimal" },
|
||||
{ key: "reimAmountCurrencyCode", label: "报销币种", type: "string", enumLabels: { CNY: "人民币" } },
|
||||
{
|
||||
key: "expenseList", label: "费用明细", type: "array", itemFields: [
|
||||
{ key: "expTypeCode", label: "费用类型", type: "string", optionSource: { kind: "expense_type" } },
|
||||
{ key: "currencyCode", label: "费用币种", type: "string", enumLabels: { CNY: "人民币" } },
|
||||
{ key: "originalCurrencyReimAmt", label: "费用金额", type: "decimal" },
|
||||
{ key: "payCityCode", label: "消费城市", type: "string", optionSource: { kind: "city" } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "businessTrip", label: "差旅行程", type: "object", fields: [{
|
||||
key: "businessTripDetails", label: "行程明细", type: "array", itemFields: [
|
||||
{ key: "departCityCode", label: "出发城市", type: "string", optionSource: { kind: "city" } },
|
||||
{ key: "destinationCityCode", label: "目的城市", type: "string", optionSource: { kind: "city" } },
|
||||
{ key: "beginTime", label: "出发时间", type: "datetime" },
|
||||
{ key: "endTime", label: "结束时间", type: "datetime" },
|
||||
{ key: "travelMode", label: "出行方式", type: "comma_separated_enum", multiple: true, enumLabels: { AirPlane: "飞机", Train: "火车" } },
|
||||
{ key: "roundTrip", label: "是否往返", type: "string", enumLabels: { Y: "往返", N: "单程" } },
|
||||
],
|
||||
}],
|
||||
},
|
||||
{ key: "customFieldValues", userVisible: false, type: "array" },
|
||||
];
|
||||
const optionCalls: Array<{ kind: string; query: string }> = [];
|
||||
const xft = {
|
||||
async listSubmissionTemplates() { return [template]; },
|
||||
async getSubmissionSchema() {
|
||||
return { template, fields, inputContract: { values: { standardFields } }, diagnostics: {} };
|
||||
},
|
||||
async getSubmissionOptions(_user: unknown, _template: unknown, kind: string, query: string) {
|
||||
optionCalls.push({ kind, query });
|
||||
const labels: Record<string, string> = {
|
||||
"440300": "深圳市", "310100": "上海市", MEAL: "餐饮费",
|
||||
};
|
||||
return {
|
||||
kind,
|
||||
items: labels[query] ? [{ code: query, label: labels[query] }] : [],
|
||||
diagnostics: {},
|
||||
};
|
||||
},
|
||||
async validateSubmissionIdentity() { return { validated: true, diagnostics: {} }; },
|
||||
};
|
||||
const service = new UnifiedApprovalService(config(), {} as ApprovalService, xft as never);
|
||||
const user = { userId: "user-1" };
|
||||
const templateId = String((await service.listSubmissionTemplates(user, "xft", 10)).items[0]?.templateId);
|
||||
const prepared = await service.prepareSubmission(user, {
|
||||
templateId,
|
||||
values: {
|
||||
reimAmount: "100.00",
|
||||
reimAmountCurrencyCode: "CNY",
|
||||
expenseList: [{
|
||||
expTypeCode: "MEAL", currencyCode: "CNY", originalCurrencyReimAmt: "100.00", payCityCode: "440300",
|
||||
}],
|
||||
businessTrip: { businessTripDetails: [{
|
||||
departCityCode: "440300", destinationCityCode: "310100",
|
||||
beginTime: "2026-08-20 09:00", endTime: "2026-08-21 18:00",
|
||||
travelMode: "AirPlane,Train", roundTrip: "Y",
|
||||
}] },
|
||||
customFieldValues: [
|
||||
{ fieldNumber: "C-TOPIC", fieldValue: "PROCUREMENT" },
|
||||
{ fieldNumber: "C-TABLE", fieldValue: JSON.stringify([{ sequence: 1, columnValues: [{ titleCode: "T-TYPE", value: "MARKETING" }] }]) },
|
||||
],
|
||||
},
|
||||
});
|
||||
assert.equal(prepared.status, "confirmation_required");
|
||||
const summary = JSON.stringify(prepared.preview.formSummary);
|
||||
for (const label of ["深圳市", "上海市", "餐饮费", "人民币", "飞机", "火车", "往返", "采购事项", "市场活动"]) {
|
||||
assert.match(summary, new RegExp(label));
|
||||
}
|
||||
for (const internal of ["440300", "310100", "MEAL", "C-TOPIC", "PROCUREMENT", "CNY", "T-TYPE", "MARKETING"]) {
|
||||
assert.doesNotMatch(summary, new RegExp(internal));
|
||||
}
|
||||
assert.deepEqual(optionCalls, [
|
||||
{ kind: "expense_type", query: "MEAL" },
|
||||
{ kind: "city", query: "440300" },
|
||||
{ kind: "city", query: "310100" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("Xft preparation fails before confirmation when a selected code has no readable mapping", async () => {
|
||||
const template: XftSubmissionTemplate = {
|
||||
eventSeqId: "event-expense", eventNumber: "XFT-EXPENSE", eventName: "费用申请单",
|
||||
eventType: "EXPENSE_APPLY", kind: "expense_apply",
|
||||
};
|
||||
const xft = {
|
||||
async listSubmissionTemplates() { return [template]; },
|
||||
async getSubmissionSchema() {
|
||||
return {
|
||||
template,
|
||||
fields: [],
|
||||
inputContract: { values: { standardFields: [{
|
||||
key: "applyExpenses", label: "事前申请费用列表", type: "array", itemFields: [
|
||||
{ key: "expenseNumber", label: "费用类型", type: "string", optionSource: { kind: "expense_type" } },
|
||||
{ key: "amount", label: "申请金额", type: "decimal" },
|
||||
],
|
||||
}] } },
|
||||
diagnostics: {},
|
||||
};
|
||||
},
|
||||
async getSubmissionOptions() { return { kind: "expense_type", items: [], diagnostics: {} }; },
|
||||
async validateSubmissionIdentity() { return { validated: true, diagnostics: {} }; },
|
||||
};
|
||||
const service = new UnifiedApprovalService(config(), {} as ApprovalService, xft as never);
|
||||
const user = { userId: "user-1" };
|
||||
const templateId = String((await service.listSubmissionTemplates(user, "xft", 10)).items[0]?.templateId);
|
||||
await assert.rejects(
|
||||
service.prepareSubmission(user, {
|
||||
templateId, values: { applyExpenses: [{ expenseNumber: "UNKNOWN", amount: "10.00" }] },
|
||||
}),
|
||||
/无法映射为业务名称.*重新查询可用名称/,
|
||||
);
|
||||
});
|
||||
|
||||
test("Xft use-car application is explicitly client-only and cannot be prepared", async () => {
|
||||
const template: XftSubmissionTemplate = {
|
||||
eventSeqId: "event-car", eventNumber: "XFT-CAR", eventName: "用车申请单",
|
||||
eventType: "USE_CAR_APPLY", kind: "use_car_apply",
|
||||
};
|
||||
const xft = {
|
||||
async listSubmissionTemplates() { return [template]; },
|
||||
async getSubmissionSchema() {
|
||||
return { template, fields: [], inputContract: { values: { standardFields: [] } }, diagnostics: {} };
|
||||
},
|
||||
};
|
||||
const service = new UnifiedApprovalService(config(), {} as ApprovalService, xft as never);
|
||||
const user = { userId: "user-1" };
|
||||
const templateId = String((await service.listSubmissionTemplates(user, "xft", 10)).items[0]?.templateId);
|
||||
const schema = await service.getSubmissionSchema(user, templateId);
|
||||
assert.equal(schema.submissionCapability.status, "client_only");
|
||||
assert.match(String(schema.submissionCapability.reason), /用车信息对象.*未提供可验证/);
|
||||
await assert.rejects(
|
||||
service.prepareSubmission(user, { templateId, values: { useCarInfo: { purpose: "客户拜访" } } }),
|
||||
/用车申请.*薪福通客户端/,
|
||||
);
|
||||
});
|
||||
|
||||
test("Xft preparation reports standard and live custom required fields together", async () => {
|
||||
const template: XftSubmissionTemplate = {
|
||||
eventSeqId: "1", eventNumber: "XFT-E", eventName: "招待费用申请单",
|
||||
|
||||
Reference in New Issue
Block a user