补齐报销待组批财务入口 [09-04-reimbursement-state-gap-finance-view][R1]
这个提交包含在:
@@ -392,6 +392,7 @@ const PROCESSING_STAGE_DEFINITIONS = Object.freeze([
|
||||
{ key: "submitted", label: "申请提交" },
|
||||
{ key: "manager_approval", label: "主管审批" },
|
||||
{ key: "finance_review", label: "财务复核" },
|
||||
PAYMENT_PROCESSING_STAGE_DEFINITIONS.created,
|
||||
PAYMENT_PROCESSING_STAGE_DEFINITIONS.review,
|
||||
PAYMENT_PROCESSING_STAGE_DEFINITIONS.payment,
|
||||
]);
|
||||
@@ -566,13 +567,16 @@ function buildProcessingProgress({ reimbursement, workflowInstance, paymentBatch
|
||||
const batchTargetHref = batch && canReadPaymentBatch
|
||||
? `/payment-batches?id=${encodeURIComponent(batch.id)}`
|
||||
: null;
|
||||
const batchCreatedStage = stageByKey.get("payment_batch_created");
|
||||
batchCreatedStage.label = "组建付款批次";
|
||||
const batchReviewStage = stageByKey.get("payment_batch_review");
|
||||
const paymentStage = stageByKey.get("payment_and_receipt");
|
||||
|
||||
if (!batch) {
|
||||
Object.assign(batchReviewStage, reimbursement.status === "approved"
|
||||
? { state: "current", result: "待安排", detail: "待财务安排付款" }
|
||||
: { detail: "待财务安排付款" });
|
||||
Object.assign(batchCreatedStage, reimbursement.status === "approved"
|
||||
? { state: "current", result: "待组批", detail: "待财务组建付款批次" }
|
||||
: { detail: "待财务组建付款批次" });
|
||||
batchReviewStage.detail = "待付款批次创建并提交复核";
|
||||
paymentStage.detail = "待付款复核后由财务登记,非审批环节";
|
||||
} else {
|
||||
const semantics = buildPaymentProcessingSemantics({
|
||||
@@ -587,11 +591,13 @@ function buildProcessingProgress({ reimbursement, workflowInstance, paymentBatch
|
||||
paymentOrderExportCount: batch.payment_order_export_count ?? 0,
|
||||
});
|
||||
const actorIds = {
|
||||
creator: batch.created_by,
|
||||
reviewer: batch.reviewed_by,
|
||||
executor: batch.executed_by,
|
||||
canceller: batch.cancelled_by,
|
||||
};
|
||||
for (const [target, source] of [
|
||||
[batchCreatedStage, semantics.created],
|
||||
[batchReviewStage, semantics.review],
|
||||
[paymentStage, semantics.payment],
|
||||
]) {
|
||||
@@ -630,6 +636,7 @@ function buildProcessingProgress({ reimbursement, workflowInstance, paymentBatch
|
||||
...(approvalStages.length > 0
|
||||
? approvalStages
|
||||
: [stageByKey.get("manager_approval"), stageByKey.get("finance_review")]),
|
||||
stageByKey.get("payment_batch_created"),
|
||||
stageByKey.get("payment_batch_review"),
|
||||
stageByKey.get("payment_and_receipt"),
|
||||
],
|
||||
|
||||
@@ -444,7 +444,8 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
expect.objectContaining({ key: "submitted", state: "completed", 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_batch_created", label: "组建付款批次", state: "pending", actorName: null }),
|
||||
expect.objectContaining({ key: "payment_batch_review", state: "pending", actorName: null, detail: "待付款批次创建并提交复核" }),
|
||||
expect.objectContaining({ key: "payment_and_receipt", state: "pending", actorName: null }),
|
||||
]);
|
||||
|
||||
@@ -516,6 +517,7 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
expect.objectContaining({ key: "submitted", state: "current", actorName: "张三", detail: "待申请人提交" }),
|
||||
expect.objectContaining({ key: "manager_approval", state: "pending", actorName: null }),
|
||||
expect.objectContaining({ key: "finance_review", state: "pending", actorName: null }),
|
||||
expect.objectContaining({ key: "payment_batch_created", state: "pending", actorName: null }),
|
||||
expect.objectContaining({ key: "payment_batch_review", state: "pending", actorName: null }),
|
||||
expect.objectContaining({ key: "payment_and_receipt", state: "pending", actorName: null }),
|
||||
]);
|
||||
@@ -856,9 +858,10 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
const stages = response.json().data.processingProgress.stages;
|
||||
expect(stages[3]).toMatchObject({ state: reviewState, targetHref: null });
|
||||
expect(stages[4]).toMatchObject({ state: paymentState, detail, targetHref: null });
|
||||
if (status === "draft") expect(stages[3].actorName).toBe(null);
|
||||
expect(stages[3]).toMatchObject({ key: "payment_batch_created", state: "completed", targetHref: null });
|
||||
expect(stages[4]).toMatchObject({ state: reviewState, targetHref: null });
|
||||
expect(stages[5]).toMatchObject({ state: paymentState, detail, targetHref: null });
|
||||
if (status === "draft") expect(stages[4].actorName).toBe(null);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
@@ -895,7 +898,7 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
headers: { authorization: "Bearer test" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.processingProgress.stages[3]).toMatchObject({
|
||||
expect(response.json().data.processingProgress.stages[4]).toMatchObject({
|
||||
detail: "待非制单人复核付款",
|
||||
actorName: null,
|
||||
targetHref: "/payment-batches?id=batch-1",
|
||||
@@ -906,12 +909,12 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
|
||||
it("attaches the payment-batch link to exactly one status-owning stage", async () => {
|
||||
const cases = [
|
||||
{ status: "draft", reviewedAt: null, targetIndex: 3 },
|
||||
{ status: "reviewed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 4 },
|
||||
{ status: "executed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 4 },
|
||||
{ status: "failed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 4 },
|
||||
{ status: "cancelled", reviewedAt: null, targetIndex: 3 },
|
||||
{ status: "cancelled", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 4 },
|
||||
{ status: "draft", reviewedAt: null, targetIndex: 4 },
|
||||
{ status: "reviewed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 5 },
|
||||
{ status: "executed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 5 },
|
||||
{ status: "failed", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 5 },
|
||||
{ status: "cancelled", reviewedAt: null, targetIndex: 4 },
|
||||
{ status: "cancelled", reviewedAt: new Date("2026-04-11T08:00:00.000Z"), targetIndex: 5 },
|
||||
];
|
||||
|
||||
for (const item of cases) {
|
||||
@@ -967,10 +970,11 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.processingProgress.stages[3]).toMatchObject({
|
||||
key: "payment_batch_created",
|
||||
state: "current",
|
||||
actorName: null,
|
||||
result: "待安排",
|
||||
detail: "待财务安排付款",
|
||||
result: "待组批",
|
||||
detail: "待财务组建付款批次",
|
||||
});
|
||||
|
||||
await app.close();
|
||||
@@ -991,7 +995,7 @@ describe("GET /api/reimbursements/:id detail enhancements", () => {
|
||||
headers: { authorization: "Bearer test" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.processingProgress.stages[4]).toMatchObject({
|
||||
expect(response.json().data.processingProgress.stages[5]).toMatchObject({
|
||||
state: "completed",
|
||||
actorName: "实际付款人",
|
||||
occurredAt: "2026-04-12T08:00:00.000Z",
|
||||
|
||||
在新工单中引用
屏蔽一个用户