实现报销费用政策与票据明细门禁(08-29-reimbursement-expense-policy-invoice-guard)

这个提交包含在:
2026-08-29 23:47:22 +08:00
父节点 9e2aea5f70
当前提交 6e999d5bf9
共修改 34 个文件,包含 1458 行新增和 92 行删除
@@ -30,6 +30,7 @@ const TRAIN_TYPE_ID = "22222222-2222-4222-8222-222222222222";
const BUS_TYPE_ID = "33333333-3333-4333-8333-333333333333";
const TAXI_TYPE_ID = "44444444-4444-4444-8444-444444444444";
const TRANSPORT_TYPE_ID = "55555555-5555-4555-8555-555555555555";
const MEAL_ALLOWANCE_TYPE_ID = "66666666-6666-4666-8666-666666666666";
function cloneDate(value) {
return value ? new Date(value) : value;
@@ -130,6 +131,7 @@ async function buildApp(options = {}) {
{ id: BUS_TYPE_ID, code: "travel_bus", name: "大巴车票", category: "travel", enabled: true },
{ id: TAXI_TYPE_ID, code: "travel_taxi", name: "打车费", category: "travel", enabled: true },
{ id: TRANSPORT_TYPE_ID, code: "transport", name: "legacy-交通费", category: "travel", enabled: true },
{ id: MEAL_ALLOWANCE_TYPE_ID, code: "travel_meal", name: "伙食补助费", category: "travel", enabled: true, requires_invoice: false },
],
travelRequests: (options.travelRequests ?? [buildTravelRequest()]).map((item) => cloneTravelRequest(item)),
auditLogs: [],
@@ -195,6 +197,9 @@ async function buildApp(options = {}) {
if (where?.enabled === true && !item.enabled) {
return false;
}
if (where?.requires_invoice === true && item.requires_invoice !== true) {
return false;
}
if (ids && !ids.includes(item.id)) {
return false;
}
@@ -464,6 +469,34 @@ describe("reimbursement create enhancements", () => {
await app.close();
});
it("keeps invoice-free allowance types available to reimbursement templates", async () => {
const { app, state } = await buildApp({
templates: [buildTemplate({
items: [{
expenseTypeId: MEAL_ALLOWANCE_TYPE_ID,
description: "伙食补助",
defaultAmount: "300.00",
}],
})],
});
const response = await app.inject({
method: "POST",
url: "/api/reimbursements",
payload: { templateId: TEMPLATE_ID, reimbursementDate: "2026-04-09" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.items).toEqual([
expect.objectContaining({
expenseTypeId: MEAL_ALLOWANCE_TYPE_ID,
amount: "300.00",
}),
]);
expect(state.reimbursementItems).toHaveLength(1);
await app.close();
});
it("rejects templateId and linkedTravelRequestId when both are provided", async () => {
const { app } = await buildApp();