修复报销进度审批节点覆盖(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-08-01 13:38:05 +08:00
父节点 07ac5415c6
当前提交 f33b3569c6
共修改 7 个文件,包含 386 行新增和 45 行删除
@@ -320,8 +320,8 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
});
expect(response.json().data.processingProgress.stages).toEqual([
expect.objectContaining({ key: "submitted", state: "completed", actorName: "李四", result: "已提交" }),
expect.objectContaining({ key: "manager_approval", state: "completed", actorName: "王经理", result: "通过" }),
expect.objectContaining({ key: "finance_review", state: "current", actorName: "财务专员", result: "处理中" }),
expect.objectContaining({ key: "approval_manager-task", label: "主管审批", state: "completed", actorName: "王经理", result: "通过" }),
expect.objectContaining({ key: "approval_finance-task", label: "财务审批", state: "current", actorName: "财务专员", result: "处理中" }),
expect.objectContaining({ key: "payment_batch_review", state: "pending", actorName: null, detail: "待财务创建付款批次" }),
expect.objectContaining({ key: "payment_and_receipt", state: "pending", actorName: null }),
]);
@@ -360,6 +360,243 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
await app.close();
});
it("keeps a third approval task and uses the role label when its configured name is only an ordinal", async () => {
const { app } = await buildApp({
latestWorkflowInstance: {
id: PENDING_INSTANCE_ID,
current_step_id: "ceo_approve",
current_status: "pending",
created_at: new Date("2026-04-10T10:00:00.000Z"),
started_at: new Date("2026-04-10T10:00:00.000Z"),
context_data: {
workflow_steps_snapshot: [
{ id: "manager_approve", name: "主管审批" },
{ id: "finance_approve", name: "财务复核" },
{ id: "ceo_approve", name: "步骤3" },
],
},
workflow_definition: {
definition_json: {
steps: [
{ id: "manager_approve", name: "主管审批" },
{ id: "finance_approve", name: "财务复核" },
{ id: "ceo_approve", name: "步骤3" },
],
},
},
workflow_tasks: [
{
id: "manager-task",
step_id: "manager_approve",
step_name: "主管审批",
assignee_role: "manager",
status: "completed",
outcome: "approved",
comment: null,
created_at: new Date("2026-04-10T10:00:00.000Z"),
completed_at: new Date("2026-04-10T11:00:00.000Z"),
assignee_employee: { name: "主管" },
completer_employee: { name: "主管" },
},
{
id: "finance-task",
step_id: "finance_approve",
step_name: "财务复核",
assignee_role: "finance",
status: "completed",
outcome: "approved",
comment: null,
created_at: new Date("2026-04-10T11:00:00.000Z"),
completed_at: new Date("2026-04-10T12:00:00.000Z"),
assignee_employee: { name: "财务" },
completer_employee: { name: "财务" },
},
{
id: "ceo-task",
step_id: "ceo_approve",
step_name: "步骤3",
assignee_role: "ceo",
status: "pending",
outcome: null,
comment: null,
created_at: new Date("2026-04-10T12:00:00.000Z"),
completed_at: null,
assignee_employee: { name: "负责人" },
completer_employee: null,
},
],
},
});
const response = await app.inject({
method: "GET",
url: `/api/reimbursements/${REIMBURSEMENT_ID}`,
headers: { authorization: "Bearer test" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.processingProgress.stages.slice(1, 4)).toEqual([
expect.objectContaining({ key: "approval_manager-task", label: "主管审批", state: "completed" }),
expect.objectContaining({ key: "approval_finance-task", label: "财务复核", state: "completed" }),
expect.objectContaining({ key: "approval_ceo-task", label: "负责人复核", state: "current", actorName: "负责人" }),
]);
await app.close();
});
it("appends future snapshot approvals that do not have workflow tasks yet", async () => {
const { app } = await buildApp({
latestWorkflowInstance: {
id: PENDING_INSTANCE_ID,
current_step_id: "manager_approve",
current_status: "pending",
created_at: new Date("2026-04-10T10:00:00.000Z"),
started_at: new Date("2026-04-10T10:00:00.000Z"),
context_data: {
workflow_steps_snapshot: [
{ id: "manager_approve", name: "主管审批", type: "approval", assignee_rule: { type: "department_manager" } },
{ id: "finance_approve", name: "财务复核", type: "approval", assignee_rule: { type: "role", value: "finance" } },
{ id: "ceo_approve", name: "步骤3", type: "approval", assignee_rule: { type: "role", value: "ceo" } },
],
},
workflow_definition: { definition_json: { steps: [] } },
workflow_tasks: [{
id: "manager-task",
step_id: "manager_approve",
step_name: "主管审批",
assignee_role: "manager",
status: "pending",
outcome: null,
comment: null,
created_at: new Date("2026-04-10T10:00:00.000Z"),
completed_at: null,
assignee_employee: { name: "主管" },
completer_employee: null,
}],
},
});
const response = await app.inject({
method: "GET",
url: `/api/reimbursements/${REIMBURSEMENT_ID}`,
headers: { authorization: "Bearer test" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.processingProgress.stages.slice(1, 4)).toEqual([
expect.objectContaining({ key: "approval_manager-task", label: "主管审批", state: "current", actorName: "主管" }),
expect.objectContaining({ key: "approval_pending_2", label: "财务复核", state: "pending", actorName: null }),
expect.objectContaining({ key: "approval_pending_3", label: "负责人复核", state: "pending", actorName: null }),
]);
await app.close();
});
it("keeps multiple tasks with the same role as separate ordered approval stages", async () => {
const { app } = await buildApp({
latestWorkflowInstance: {
id: PENDING_INSTANCE_ID,
current_step_id: "finance_final",
current_status: "pending",
created_at: new Date("2026-04-10T10:00:00.000Z"),
started_at: new Date("2026-04-10T10:00:00.000Z"),
context_data: {
workflow_steps_snapshot: [
{ id: "finance_first", name: "财务初审" },
{ id: "finance_final", name: "财务终审" },
],
},
workflow_definition: {
definition_json: {
steps: [
{ id: "finance_first", name: "财务初审" },
{ id: "finance_final", name: "财务终审" },
],
},
},
workflow_tasks: [
{
id: "finance-first-task",
step_id: "finance_first",
step_name: "finance_first",
assignee_role: "finance",
status: "completed",
outcome: "approved",
comment: "初审通过",
created_at: new Date("2026-04-10T10:00:00.000Z"),
completed_at: new Date("2026-04-10T11:00:00.000Z"),
assignee_employee: { name: "初审人" },
completer_employee: { name: "初审人" },
},
{
id: "finance-final-task",
step_id: "finance_final",
step_name: "财务终审",
assignee_role: "finance",
status: "pending",
outcome: null,
comment: null,
created_at: new Date("2026-04-10T11:00:00.000Z"),
completed_at: null,
assignee_employee: { name: "终审人" },
completer_employee: null,
},
],
},
});
const response = await app.inject({
method: "GET",
url: `/api/reimbursements/${REIMBURSEMENT_ID}`,
headers: { authorization: "Bearer test" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.processingProgress.stages.slice(1, 3)).toEqual([
expect.objectContaining({ key: "approval_finance-first-task", label: "财务复核", actorName: "初审人", state: "completed" }),
expect.objectContaining({ key: "approval_finance-final-task", label: "财务终审", actorName: "终审人", state: "current" }),
]);
await app.close();
});
it("falls back to pending manager and finance placeholders when an instance has no tasks yet", async () => {
const { app } = await buildApp({
latestWorkflowInstance: {
id: PENDING_INSTANCE_ID,
current_step_id: null,
current_status: "pending",
created_at: new Date("2026-04-10T10:00:00.000Z"),
started_at: new Date("2026-04-10T10:00:00.000Z"),
context_data: {
workflow_steps_snapshot: [
{ id: "manager_approve", name: "主管审批" },
{ id: "finance_approve", name: "财务复核" },
],
},
workflow_definition: {
definition_json: {
steps: [
{ id: "manager_approve", name: "主管审批" },
{ id: "finance_approve", name: "财务复核" },
],
},
},
workflow_tasks: [],
},
});
const response = await app.inject({
method: "GET",
url: `/api/reimbursements/${REIMBURSEMENT_ID}`,
headers: { authorization: "Bearer test" },
});
expect(response.statusCode).toBe(200);
expect(response.json().data.processingProgress.stages.slice(1, 3)).toEqual([
expect.objectContaining({ key: "approval_pending_1", label: "主管审批", state: "pending", actorName: null }),
expect.objectContaining({ key: "approval_pending_2", label: "财务复核", state: "pending", actorName: null }),
]);
await app.close();
});
it.each([
["draft", "current", "pending", "待所在付款批次复核通过"],
["reviewed", "completed", "current", "所在付款批次已复核,待执行付款"],