fix: 统一报销付款状态与进度(09-04-reimbursement-state-gap-finance-view)
这个提交包含在:
@@ -23,11 +23,22 @@ module.exports = async function verifyPaymentExperience({
|
||||
account_number_encrypted: encryptStringOrThrow(app.config, `6217${"0".repeat(8)}1234`),
|
||||
} });
|
||||
await prisma.featureFlag.create({ data: { flag_key: "payment_batch_v1", override_mode: "force_on", rollout_type: "global", department_ids: [] } });
|
||||
const readProgress = async (actor) => {
|
||||
const response = await fetch(`${origin}/api/reimbursements/${form.id}`, {
|
||||
headers: { authorization: `Bearer ${tokens.get(actor.id)}` }, signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
return (await response.json()).data.processingProgress;
|
||||
};
|
||||
assert.equal((await readProgress(approvers.finance)).stages.find((stage) => stage.key === "payment_batch_created").state, "current");
|
||||
const created = await post("/payment-batches", {
|
||||
entityType: "reimbursement", entityIds: [form.id], defaultPaymentChannel: "bank",
|
||||
}, approvers.finance.id);
|
||||
assert.equal(created.status, 201, `create payment batch: ${created.body.error?.details?.reasonCode ?? created.status}`);
|
||||
const batch = created.body.data.item;
|
||||
const reviewStage = (await readProgress(approvers.finance)).stages.find((stage) => stage.key === "payment_batch_review");
|
||||
assert.equal(reviewStage.state, "current");
|
||||
assert.equal(reviewStage.detail, "待非制单人复核付款");
|
||||
const download = (actor) => fetch(`${origin}/api/payment-batches/${batch.id}/payment-order`, {
|
||||
method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${tokens.get(actor.id)}` },
|
||||
body: "{}", signal: AbortSignal.timeout(15000),
|
||||
@@ -35,6 +46,7 @@ module.exports = async function verifyPaymentExperience({
|
||||
assert.equal((await download(approvers.finance)).status, 409);
|
||||
assert.equal((await post(`/payment-batches/${batch.id}/review`, {}, approvers.finance.id)).status, 403);
|
||||
assert.equal((await post(`/payment-batches/${batch.id}/review`, {}, approvers.ceo.id)).status, 200);
|
||||
assert.equal((await readProgress(approvers.finance)).stages.find((stage) => stage.key === "payment_and_receipt").state, "current");
|
||||
assert.equal((await download(approvers.ceo)).status, 403);
|
||||
assert.equal((await download(employee)).status, 403);
|
||||
for (const count of [1, 2]) {
|
||||
@@ -50,7 +62,10 @@ module.exports = async function verifyPaymentExperience({
|
||||
const source = await prisma.reimbursementForm.findUnique({ where: { id: form.id } });
|
||||
assert.equal(source.status, "approved");
|
||||
assert.equal(source.paid_at, null);
|
||||
assert.equal((await readProgress(approvers.finance)).stages.find((stage) => stage.key === "payment_and_receipt").detail,
|
||||
"已生成银行制单,等待付款完成并上传回单,非审批环节");
|
||||
}
|
||||
assert.equal((await readProgress(employee)).stages.some((stage) => stage.targetHref), false);
|
||||
const audits = await prisma.auditLog.findMany({ where: { target_id: batch.id, action: { in: ["PAYMENT_ORDER_EXPORTED", "PAYMENT_ORDER_REEXPORTED"] } } });
|
||||
assert.equal(audits.length, 2);
|
||||
for (const audit of audits) {
|
||||
@@ -70,7 +85,13 @@ module.exports = async function verifyPaymentExperience({
|
||||
const login = await context.request.post(`${uiOrigin}/api/auth/login`, { headers: { "x-device-fingerprint": fingerprint }, data: { identifier: approvers.finance.email, password: loginPassword } });
|
||||
assert.equal(login.status(), 200);
|
||||
const page = await context.newPage();
|
||||
await page.goto(`${uiOrigin}/payment-batches?id=${batch.id}`);
|
||||
await page.goto(`${uiOrigin}${mobile ? "/mobile" : ""}/reimbursements/${form.id}`);
|
||||
const sourceActions = page.getByTestId(mobile ? "mobile-reimbursement-payment-actions" : "reimbursement-action-panel");
|
||||
await expect(sourceActions).toContainText("已生成银行制单");
|
||||
await expect(page.getByText("去组建付款批次", { exact: true })).toHaveCount(0);
|
||||
const sourceLink = page.getByRole("link", { name: "查看所在付款批次", exact: true });
|
||||
await expect(sourceLink).toHaveCount(1);
|
||||
await sourceLink.click();
|
||||
const actions = page.getByTestId(mobile ? "payment-batch-mobile-actions" : "payment-batch-action-panel");
|
||||
await expect(actions.getByRole("button", { name: "上传银行回单" })).toBeVisible();
|
||||
await actions.getByText("更多", { exact: true }).click();
|
||||
@@ -86,12 +107,22 @@ module.exports = async function verifyPaymentExperience({
|
||||
assert.equal(readFileSync(await downloaded.path()).subarray(0, 8).toString("hex"), "d0cf11e0a1b11ae1");
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect(page.getByTestId("payment-batch-processing-panel")).toBeVisible();
|
||||
await page.goBack();
|
||||
await expect(sourceActions).toContainText("已生成银行制单");
|
||||
await expect(page.getByText("去组建付款批次", { exact: true })).toHaveCount(0);
|
||||
await expect(sourceLink).toHaveCount(1);
|
||||
const sourceProgress = page.getByTestId(mobile ? "mobile-reimbursement-processing-progress" : "reimbursement-workflow-progress");
|
||||
await expect(sourceProgress).toHaveCount(1);
|
||||
const actionBounds = await sourceActions.boundingBox();
|
||||
const progressBounds = await sourceProgress.boundingBox();
|
||||
assert.ok(progressBounds.y > actionBounds.y + actionBounds.height);
|
||||
if (!mobile) assert.ok(Math.abs(progressBounds.x - actionBounds.x) < 35);
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth), true);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
}
|
||||
passed.push("payment: real authenticated desktop/mobile browser downloads Chinese BIFF8 with no reason input; repeat warning and receipt action retained");
|
||||
passed.push("payment: real authenticated desktop/mobile reimbursement-to-batch download and return agree on stage, unique link, action-before-progress; no reason input; repeat warning retained");
|
||||
}
|
||||
passed.push("payment: real finance create, CEO review, unauthorized export refused, Chinese BIFF8 first/re-download without reason, audited and still unpaid");
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户