fix: 退回报销支持留痕删除(09-04-reimbursement-state-gap-finance-view)

这个提交包含在:
2026-09-10 16:38:37 +08:00
父节点 e5685f18b2
当前提交 73fcd5a65a
共修改 27 个文件,包含 696 行新增和 94 行删除
@@ -234,6 +234,7 @@ async function buildApp({
const prisma = {
$transaction: vi.fn(async (callback) => callback(tx)),
reimbursementForm: {
updateMany: vi.fn(async () => ({ count: 1 })),
findUnique: vi.fn(async ({ where }) => {
if (where.id !== REIMBURSEMENT_ID) {
return null;
@@ -1083,7 +1084,7 @@ describe("POST /api/reimbursements/:id/link-project authorization", () => {
});
expect(response.statusCode, response.body).toBe(403);
expect(prisma.reimbursementForm.update).not.toHaveBeenCalled();
expect(prisma.reimbursementForm.updateMany).not.toHaveBeenCalled();
await app.close();
});
@@ -1101,7 +1102,7 @@ describe("POST /api/reimbursements/:id/link-project authorization", () => {
});
expect(response.statusCode, response.body).toBe(409);
expect(prisma.reimbursementForm.update).not.toHaveBeenCalled();
expect(prisma.reimbursementForm.updateMany).not.toHaveBeenCalled();
await app.close();
});
@@ -1119,12 +1120,33 @@ describe("POST /api/reimbursements/:id/link-project authorization", () => {
});
expect(response.statusCode, response.body).toBe(200);
expect(prisma.reimbursementForm.update).toHaveBeenCalledTimes(1);
expect(prisma.reimbursementForm.updateMany).toHaveBeenCalledWith({
where: { id: REIMBURSEMENT_ID, deleted_at: null, status: { in: ["draft", "rejected"] } },
data: { project_id: projectId, updated_by: SUBMITTER_ID },
});
await app.close();
});
});
describe("reimbursement maintenance authorization", () => {
it("marks deleted historical detail and terminates next-action projections", async () => {
const { app } = await buildApp({
employeeId: SUBMITTER_ID,
permissionCodes: ["expense.view_own"],
reimbursement: buildReimbursementRecord({ status: "cancelled", deleted_at: new Date("2026-09-10T01:00:00Z") }),
});
try {
const response = await app.inject({ method: "GET", url: `/api/reimbursements/${REIMBURSEMENT_ID}`, headers: { authorization: "Bearer test" } });
expect(response.statusCode).toBe(200);
const detail = response.json().data;
expect(detail.deletedAt).toBe("2026-09-10T01:00:00.000Z");
expect(detail.processingProgress.stages.some((stage) => ["pending", "current"].includes(stage.state))).toBe(false);
expect(detail.processingProgress.stages.at(-1)).toMatchObject({ key: "deleted", result: "已删除", state: "terminated" });
} finally {
await app.close();
}
});
const itemId = "77777777-7777-4777-8777-777777777777";
const expenseTypeId = "88888888-8888-4888-8888-888888888888";