修复 R125 发票删除后重传查重
这个提交包含在:
@@ -0,0 +1,93 @@
|
||||
const {
|
||||
INVOICE_UNAVAILABLE_REASONS,
|
||||
classifyInvoiceConflict,
|
||||
resolveUploadDedupDecision,
|
||||
} = require("../../src/services/invoiceGovernance");
|
||||
|
||||
function buildInvoice(overrides = {}) {
|
||||
return {
|
||||
id: overrides.id ?? "invoice-1",
|
||||
owner_employee_id: overrides.owner_employee_id ?? "employee-1",
|
||||
uploaded_by_employee_id: overrides.uploaded_by_employee_id ?? "employee-1",
|
||||
status: overrides.status ?? "available",
|
||||
archived_reason: overrides.archived_reason ?? null,
|
||||
linked_reimbursement_id: overrides.linked_reimbursement_id ?? null,
|
||||
linked_item_id: overrides.linked_item_id ?? null,
|
||||
travel_order_id: overrides.travel_order_id ?? null,
|
||||
file_sha256: overrides.file_sha256 ?? "sha256-a",
|
||||
invoice_code: overrides.invoice_code ?? null,
|
||||
invoice_number: overrides.invoice_number ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildTx(conflicts) {
|
||||
return {
|
||||
invoicePool: {
|
||||
findMany: async () => conflicts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("invoiceGovernance dedup", () => {
|
||||
it("ignores manually archived invoices when re-uploading the same file", async () => {
|
||||
const archivedManual = buildInvoice({
|
||||
status: "archived",
|
||||
archived_reason: "manual",
|
||||
});
|
||||
|
||||
expect(classifyInvoiceConflict(archivedManual, "employee-1")).toBeNull();
|
||||
|
||||
const decision = await resolveUploadDedupDecision(buildTx([archivedManual]), {
|
||||
fileSha256: "sha256-a",
|
||||
ownerEmployeeId: "employee-1",
|
||||
});
|
||||
|
||||
expect(decision).toEqual({ action: "create" });
|
||||
});
|
||||
|
||||
it("blocks invoices that have already completed reimbursement", async () => {
|
||||
const usedComplete = buildInvoice({
|
||||
status: "archived",
|
||||
archived_reason: "used_complete",
|
||||
linked_reimbursement_id: "reimbursement-1",
|
||||
});
|
||||
|
||||
expect(classifyInvoiceConflict(usedComplete, "employee-1")).toBe(INVOICE_UNAVAILABLE_REASONS.usedByCompany);
|
||||
|
||||
const decision = await resolveUploadDedupDecision(buildTx([usedComplete]), {
|
||||
fileSha256: "sha256-a",
|
||||
ownerEmployeeId: "employee-1",
|
||||
});
|
||||
|
||||
expect(decision.action).toBe("blocked");
|
||||
expect(decision.reason).toBe(INVOICE_UNAVAILABLE_REASONS.usedByCompany);
|
||||
});
|
||||
|
||||
it("reuses the owner's active available invoice", async () => {
|
||||
const available = buildInvoice({ status: "available" });
|
||||
|
||||
const decision = await resolveUploadDedupDecision(buildTx([available]), {
|
||||
fileSha256: "sha256-a",
|
||||
ownerEmployeeId: "employee-1",
|
||||
});
|
||||
|
||||
expect(decision.action).toBe("reuse_existing");
|
||||
expect(decision.reason).toBe(INVOICE_UNAVAILABLE_REASONS.duplicateAvailable);
|
||||
expect(decision.invoice.id).toBe("invoice-1");
|
||||
});
|
||||
|
||||
it("blocks another owner's active invoice in the same company", async () => {
|
||||
const otherOwner = buildInvoice({
|
||||
owner_employee_id: "employee-2",
|
||||
status: "available",
|
||||
});
|
||||
|
||||
const decision = await resolveUploadDedupDecision(buildTx([otherOwner]), {
|
||||
fileSha256: "sha256-a",
|
||||
ownerEmployeeId: "employee-1",
|
||||
});
|
||||
|
||||
expect(decision.action).toBe("blocked");
|
||||
expect(decision.reason).toBe(INVOICE_UNAVAILABLE_REASONS.duplicateOtherOwner);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户