完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)
汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
这个提交包含在:
@@ -739,3 +739,85 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (const mobile of [false, true]) {
|
||||
for (const action of ["approve", "return", "transfer", "already_processed", "failed"] as const) {
|
||||
test(`财务审批原地进度 ${mobile ? "手机" : "桌面"} ${action}`, async ({ page }) => {
|
||||
const reimbursementId = "synthetic-finance-progress";
|
||||
let changed = false;
|
||||
let readsAfterAction = 0;
|
||||
let writes = 0;
|
||||
const expectedResult = action === "return" ? "已退回修改" : action === "transfer" ? "已转审" : "财务复核通过";
|
||||
const source = {
|
||||
id: reimbursementId, formNumber: "BX-20260917-0003", status: "reviewing",
|
||||
reimbursementDate: "2026-09-17", totalAmount: "100.00", description: "合成审批进度验证",
|
||||
owner: { id: "synthetic-owner", employeeNo: "SYN-1", name: "合成申请人" },
|
||||
submitter: { id: "synthetic-owner", employeeNo: "SYN-1", name: "合成申请人" },
|
||||
items: [{ id: "synthetic-item", expenseTypeId: "synthetic-type", expenseDate: "2026-09-17",
|
||||
amount: "100.00", description: "合成明细", assetPriorApprovalBasis: null,
|
||||
expenseType: { id: "synthetic-type", code: "synthetic", name: "合成费用", category: "office", enabled: true, requiresInvoice: false } }],
|
||||
invoices: [], attachments: [],
|
||||
};
|
||||
await mockWorkflowConfirmApis(page);
|
||||
await page.route("**/api/me", (route) => fulfillJson(route, { ...mockMe, roleCodes: action === "transfer" ? ["finance", "manager"] : ["finance"],
|
||||
permissions: ["expense.approve", "expense.transfer", "expense.view_all"] }));
|
||||
await page.route(`**/api/reimbursements/${reimbursementId}`, async (route) => {
|
||||
if (changed) readsAfterAction++;
|
||||
await fulfillJson(route, { ...source, status: changed && action !== "transfer" ? (action === "return" ? "draft" : "approved") : "reviewing",
|
||||
processingProgress: { stages: [{ key: "synthetic-finance-task", label: "财务复核",
|
||||
state: changed ? (action === "return" ? "exception" : action === "transfer" ? "current" : "completed") : "current", actorName: "合成财务", occurredAt: null,
|
||||
result: changed ? expectedResult : "等待财务复核", comment: null, detail: null, targetHref: null }] } });
|
||||
});
|
||||
await page.route(`**/api/workflow/tasks/${pendingTask.id}`, (route) => fulfillJson(route, {
|
||||
...pendingTaskDetail, task: { ...pendingTaskDetail.task, stepName: "财务复核", assigneeRole: "finance",
|
||||
status: changed ? "completed" : "pending" },
|
||||
workflowInstance: { ...pendingTaskDetail.workflowInstance, entityType: "reimbursement",
|
||||
contextData: { standardViolations: [], budgetCheck: null, budgetCheckStatus: "available", parseStatus: "ready" } },
|
||||
reimbursement: source, leaveRequest: null, history: [], allowedActions: changed ? [] : ["approve", "return", "transfer"],
|
||||
}));
|
||||
await page.route(`**/api/workflow/tasks/${pendingTask.id}/*`, async (route) => {
|
||||
if (route.request().method() !== "POST") return route.fallback();
|
||||
writes++;
|
||||
changed = action !== "failed";
|
||||
if (action === "already_processed" || action === "failed") {
|
||||
await route.fulfill({ status: action === "failed" ? 500 : 409, contentType: "application/json",
|
||||
body: JSON.stringify({ requestId: "synthetic-progress", error: { code: action === "failed" ? "SYS_001" : "VALID_001",
|
||||
message: "合成失败", details: action === "already_processed" ? { task: "already_processed" } : {} } }) });
|
||||
} else await fulfillJson(route, { ok: true });
|
||||
});
|
||||
await page.setViewportSize(mobile ? { width: 390, height: 844 } : { width: 1280, height: 900 });
|
||||
await login(page, MANAGER);
|
||||
await page.goto(`${mobile ? "/mobile/approvals" : "/workflow/tasks"}/${pendingTask.id}`);
|
||||
const progress = page.getByTestId("reimbursement-approval-processing-progress");
|
||||
await expect(progress).toContainText("等待财务复核");
|
||||
const documentMarker = await page.evaluate(() => {
|
||||
const marker = crypto.randomUUID(); document.documentElement.dataset.syntheticDocument = marker; return marker;
|
||||
});
|
||||
if (action === "transfer") {
|
||||
await page.getByTestId(mobile ? "mobile-approvals-transfer" : "workflow-task-transfer-menu-item").click();
|
||||
await page.getByTestId(mobile ? "mobile-approvals-transfer-to" : "workflow-task-transfer-approver").selectOption("employee-transfer-1");
|
||||
await page.getByTestId(mobile ? "mobile-approvals-transfer-reason" : "workflow-task-transfer-reason").fill("合成转审理由用于验证处理进度刷新");
|
||||
await page.getByTestId(mobile ? "mobile-approvals-transfer-submit" : "workflow-task-transfer").click();
|
||||
await page.getByTestId("workflow-transfer-confirm-dialog").getByRole("button", { name: "确认转审", exact: true }).click();
|
||||
} else {
|
||||
if (action === "return") {
|
||||
await page.getByTestId(mobile ? "mobile-approvals-return" : "workflow-task-return-toggle").click();
|
||||
await page.getByTestId(mobile ? "mobile-approvals-comment" : "workflow-task-return-comment").fill("请补充合成资料");
|
||||
await page.getByTestId(mobile ? "mobile-approvals-return-submit" : "workflow-task-return").click();
|
||||
} else await page.getByTestId(mobile ? "mobile-approvals-approve" : "workflow-task-approve").click();
|
||||
await page.getByTestId("workflow-action-confirm-submit").click();
|
||||
}
|
||||
await expect.poll(() => writes).toBe(1);
|
||||
if (action === "failed") {
|
||||
await expect(page.getByTestId(mobile ? "mobile-approvals-approve" : "workflow-task-approve")).toBeEnabled();
|
||||
await expect(progress).toContainText("等待财务复核");
|
||||
expect(readsAfterAction).toBe(0);
|
||||
} else {
|
||||
await expect(progress).toContainText(expectedResult);
|
||||
await expect(progress.locator("li")).toHaveAttribute("data-state", action === "return" ? "exception" : action === "transfer" ? "current" : "completed");
|
||||
expect(readsAfterAction).toBeGreaterThan(0);
|
||||
}
|
||||
expect(await page.evaluate(() => document.documentElement.dataset.syntheticDocument)).toBe(documentMarker);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户