LDF 收口部署财务回单闭环

这个提交包含在:
2026-05-17 10:41:35 +08:00
父节点 ab5ab4deed
当前提交 426a96816a
共修改 5 个文件,包含 69 行新增和 14 行删除
@@ -47,6 +47,13 @@ type PaymentBatch = {
};
};
type ReceiptAttachment = {
id: string;
originalName: string;
mimeType: string;
fileSize: number;
};
type FinanceLedgerResponse = {
total: number;
items: Array<{
@@ -304,6 +311,25 @@ async function createPaymentBatch(api: APIRequestContext, input: { reimbursement
return data.item;
}
async function uploadPaymentBatchReceipt(api: APIRequestContext, batchId: string, marker: string) {
const receiptName = `${markerPrefix.toLowerCase()}-bank-receipt-${marker.slice(-8)}.pdf`;
const response = await api.post(`/api/payment-batches/${batchId}/receipt-attachments`, {
multipart: {
file: {
name: receiptName,
mimeType: "application/pdf",
buffer: Buffer.from(`%PDF-1.4\n${issueId} bank receipt ${marker}\n%%EOF\n`, "utf8"),
},
},
});
const data = await parseData<{ items: ReceiptAttachment[] }>(response, "upload payment batch receipt");
const item = data.items[0];
if (!item?.id) {
throw new Error("receipt_upload_missing_item");
}
return item;
}
async function expectPostRejected(api: APIRequestContext, pathName: string, data: unknown, label: string) {
const response = await api.post(pathName, { data });
expect(response.ok(), `${label} should be rejected`).toBe(false);
@@ -448,6 +474,23 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => {
})).item;
expect(batch.reviewRequirement.completedReviewCount).toBeGreaterThanOrEqual(1);
const missingReceiptExecuteStatus = await expectPostRejected(
finance.api,
`/api/payment-batches/${batch.id}/execute`,
{},
"bank payment execute without receipt",
);
expect(missingReceiptExecuteStatus).toBe(409);
const receipt = await uploadPaymentBatchReceipt(finance.api, batch.id, marker);
const receiptList = await apiGetData<{ items: ReceiptAttachment[] }>(
finance.api,
`/api/payment-batches/${batch.id}/receipt-attachments`,
);
expect(receiptList.items.some((item) => item.id === receipt.id)).toBe(true);
const receiptDownload = await finance.api.get(`/api/payment-batches/${batch.id}/receipt-attachments/${receipt.id}/file`);
expect(receiptDownload.status(), "bank receipt download should succeed").toBe(200);
batch = (await apiPostData<{ item: PaymentBatch }>(finance.api, `/api/payment-batches/${batch.id}/execute`, {})).item;
expect(batch.status).toBe("executed");
@@ -472,6 +515,9 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => {
);
await expectAuditAction(admin.api, "PAYMENT_BATCH_CREATED", (item) => item.targetId === batch.id);
await expectAuditAction(admin.api, "PAYMENT_BATCH_REVIEWED", (item) => item.targetId === batch.id);
await expectAuditAction(admin.api, "PAYMENT_BATCH_RECEIPT_UPLOADED", (item) =>
item.targetId === batch.id && Array.isArray(item.extra?.attachmentIds),
);
await expectAuditAction(admin.api, "PAYMENT_BATCH_EXECUTED", (item) => item.targetId === batch.id);
await expectAuditAction(admin.api, "REIMBURSEMENT_PAYMENT_MARKED", (item) => item.targetId === reimbursement.id);
await expectAuditAction(admin.api, "EXPORT_AP_OPERATIONS", (item) => item.actorId === finance.me.id);
@@ -479,6 +525,9 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => {
summary.stages.payment = {
batchId: batch.id,
batchStatus: batch.status,
receiptAttachmentId: receipt.id,
missingReceiptExecuteStatus,
receiptDownloadStatus: receiptDownload.status(),
reimbursementStatus: paid.status,
invoiceStatus: invoice.status,
ledgerPaymentStatus: ledgerItem?.payment.status ?? "missing",