const { NOTIFICATION_TYPES } = require("../../../src/constants/notificationTypes"); const workflowRoutes = require("../../../src/routes/workflow"); describe("workflow travel helpers", () => { const { buildApprovedCopy, buildOverdueTaskCopy, buildPendingTaskCopy, buildRejectedCopy, buildRemindedTaskCopy, buildReturnedCopy, buildWorkflowTaskSummaryPayload, calculateWorkflowTaskDueAt, isWorkflowTaskOverdue, buildWorkflowEntityMeta, resolveWorkflowOutcomeNotificationType, } = workflowRoutes.__testables; it("builds travel entity meta with deep-link detail path", () => { const meta = buildWorkflowEntityMeta( "travel_request", { request_no: "CL-20260310-0001", estimated_amount: "4200.00", applicant: { name: "张差旅", }, }, "11111111-1111-4111-8111-111111111111", ); expect(meta).toEqual( expect.objectContaining({ label: "差旅申请", reference: "CL-20260310-0001", targetType: "travel_request", detailPath: "/travel-requests?id=11111111-1111-4111-8111-111111111111", }), ); const pendingCopy = buildPendingTaskCopy(meta); expect(pendingCopy).toEqual({ title: "【待办】差旅申请 CL-20260310-0001 待审批", body: "申请人:张差旅\n预估金额:4200.00\n单号:CL-20260310-0001\n动作:请及时处理审批任务。", }); }); it("builds structured workflow outcome copies for leave and reimbursement", () => { const leaveMeta = buildWorkflowEntityMeta( "leave", { start_date: new Date("2026-03-25T00:00:00.000Z"), end_date: new Date("2026-03-26T00:00:00.000Z"), requested_days: "1.5", leave_type: { name: "年假", }, owner: { name: "王请假", }, }, "leave-1", ); expect(buildRejectedCopy(leaveMeta, "请补充交接安排")).toEqual({ title: "【已驳回】请假单 2026-03-25 ~ 2026-03-26", body: "申请人:王请假\n假种:年假\n天数:1.5 天\n起止日期:2026-03-25 ~ 2026-03-26\n结果:已驳回:请补充交接安排", }); const reimbursementMeta = buildWorkflowEntityMeta( "reimbursement", { form_number: "BX-20260325-001", total_amount: "880.00", owner: { name: "刘报销", }, }, "reimbursement-1", ); expect(buildApprovedCopy(reimbursementMeta)).toEqual({ title: "【已批准】报销单 BX-20260325-001", body: "申请人:刘报销\n金额:880.00\n单号:BX-20260325-001\n结果:已审批通过,等待财务付款。", }); expect(buildReturnedCopy(reimbursementMeta, "请补发票")).toEqual({ title: "【已退回】报销单 BX-20260325-001", body: "申请人:刘报销\n金额:880.00\n单号:BX-20260325-001\n结果:已退回:请补发票", }); expect(buildRemindedTaskCopy(reimbursementMeta)).toEqual({ title: "【催办】报销单 BX-20260325-001 待审批", body: "申请人:刘报销\n金额:880.00\n单号:BX-20260325-001\n动作:申请人已催办,请及时处理审批任务。", }); expect(buildOverdueTaskCopy(reimbursementMeta)).toEqual({ title: "【超时提醒】报销单 BX-20260325-001 待审批", body: "申请人:刘报销\n金额:880.00\n单号:BX-20260325-001\n动作:该审批已超时,请尽快处理。", }); }); it("resolves approved and rejected notification types for travel workflows", () => { expect(resolveWorkflowOutcomeNotificationType("travel_request", "approved")).toBe( NOTIFICATION_TYPES.travelRequestApproved, ); expect(resolveWorkflowOutcomeNotificationType("travel_request", "rejected")).toBe( NOTIFICATION_TYPES.travelRequestRejected, ); expect(resolveWorkflowOutcomeNotificationType("travel_request", "returned")).toBeNull(); }); it("builds pending task productivity summary with overdue buckets", () => { const now = new Date("2026-05-12T08:00:00.000Z"); const tasks = [ { id: "task-1", status: "pending", step_name: "经理审批", assignee_role: "manager", created_at: new Date("2026-05-11T07:00:00.000Z"), due_date: null, workflow_instance: { entity_type: "reimbursement" }, }, { id: "task-2", status: "pending", step_name: "负责人本人申请兜底:finance", assignee_role: "finance", created_at: new Date("2026-05-12T07:00:00.000Z"), due_date: new Date("2026-05-12T07:30:00.000Z"), workflow_instance: { entity_type: "purchase_request" }, }, ]; expect(calculateWorkflowTaskDueAt(tasks[0], now).toISOString()).toBe("2026-05-12T07:00:00.000Z"); expect(isWorkflowTaskOverdue(tasks[0], now)).toBe(true); const summary = buildWorkflowTaskSummaryPayload(tasks, "mine", now); expect(summary).toEqual( expect.objectContaining({ scope: "mine", pendingTotal: 2, overdueTotal: 2, earliestPendingAt: "2026-05-11T07:00:00.000Z", }), ); expect(summary.byEntityType).toEqual( expect.arrayContaining([ { key: "reimbursement", pendingTotal: 1, overdueTotal: 1 }, { key: "purchase_request", pendingTotal: 1, overdueTotal: 1 }, ]), ); expect(summary.byStep).toEqual( expect.arrayContaining([ { key: "财务复核", pendingTotal: 1, overdueTotal: 1 }, ]), ); }); });