const { resolvePaymentBatchCreationReadiness, paymentReadinessError } = require("../../../src/services/paymentBatchReadiness"); function facts() { return { entityType: "reimbursement", entityIds: ["synthetic-item"], paymentChannel: "bank", snapshots: [{ entityId: "synthetic-item", entityCode: "SYN-0014", status: "approved", departmentId: "department", legalEntityId: "company", snapshotJson: { payee: { bankInstitutionCode: "CCB", bankAccountEncrypted: "synthetic-present" } } }], departmentReadiness: new Map([["department", { enabled: true }]]), accountReadiness: new Map([["company", { paymentAccount: { id: "account" }, paymentAccountSnapshot: { available: true } }]]), reservedEntityIds: new Set(), reviewerIds: ["independent-reviewer"], }; } describe("shared payment creation readiness", () => { it("accepts a complete synthetic bank payment without success noise", () => { expect(resolvePaymentBatchCreationReadiness(facts())).toEqual({ ready: true, blockers: [] }); }); it.each([ ["source_not_found", (input) => { input.snapshots = []; }], ["source_status_invalid", (input) => { input.snapshots[0].status = "reviewing"; }], ["source_status_invalid", (input) => { input.snapshots[0].snapshotJson.paidAt = "synthetic-paid"; }], ["department_unavailable", (input) => { input.departmentReadiness.clear(); }], ["active_batch_exists", (input) => { input.reservedEntityIds.add("synthetic-item"); }], ["legal_entity_required", (input) => { input.snapshots[0].legalEntityId = null; }], ["payee_bank_required", (input) => { input.snapshots[0].snapshotJson.payee.bankInstitutionCode = null; }], ["payee_account_required", (input) => { input.snapshots[0].snapshotJson.payee.bankAccountEncrypted = null; }], ["payee_bank_unsupported", (input) => { input.snapshots[0].snapshotJson.payee.bankInstitutionCode = "OTHER"; }], ["payee_account_unavailable", (input) => { input.snapshots[0].snapshotJson.payee.bankAccountUnavailable = true; }], ["payment_account_required", (input) => { input.accountReadiness.clear(); }], ["payment_account_unavailable", (input) => { input.accountReadiness.get("company").paymentAccountSnapshot = null; }], ["reviewer_required", (input) => { input.reviewerIds = []; }], ])("returns precise safe blocker for %s", (reason, change) => { const input = facts(); change(input); const result = resolvePaymentBatchCreationReadiness(input); expect(result.ready).toBe(false); expect(result.blockers.map((blocker) => blocker.reasonCode)).toEqual([`payment_batch.${reason}`]); for (const blocker of result.blockers) expect(Object.keys(blocker).every((key) => ["reasonCode", "entityId", "entityCode"].includes(key))).toBe(true); const error = paymentReadinessError(result, input.departmentReadiness); expect(error.code).toBe("VALID_001"); expect(error.statusCode).toBe(reason === "source_not_found" ? 404 : 409); expect(error.details.reasonCode).toBe(`payment_batch.${reason}`); }); it("recomputes all three synthetic 0014 omissions immediately after correction", () => { const input = facts(); input.snapshots[0].legalEntityId = null; input.snapshots[0].snapshotJson.payee = {}; expect(resolvePaymentBatchCreationReadiness(input).blockers.map((blocker) => blocker.reasonCode)).toEqual([ "payment_batch.legal_entity_required", "payment_batch.payee_bank_required", "payment_batch.payee_account_required", ]); input.snapshots = facts().snapshots; expect(resolvePaymentBatchCreationReadiness(input)).toEqual({ ready: true, blockers: [] }); }); it("does not infer combination readiness from individually valid rows", () => { const input = facts(); input.snapshots.push({ ...input.snapshots[0], entityId: "second-item", legalEntityId: "second-company" }); input.accountReadiness.set("second-company", input.accountReadiness.get("company")); expect(resolvePaymentBatchCreationReadiness({ ...input, entityIds: ["second-item"] }).ready).toBe(true); expect(resolvePaymentBatchCreationReadiness(input).ready).toBe(true); input.entityIds.push("second-item"); expect(resolvePaymentBatchCreationReadiness(input)).toEqual({ ready: false, blockers: [{ reasonCode: "payment_batch.multiple_legal_entities" }] }); }); });