const XLSX = require("xlsx"); const { encryptStringOrThrow } = require("../../../src/services/crypto"); const { HEADERS, PaymentOrderXlsError, buildCustomerSerial, generatePaymentOrderXls, } = require("../../../src/services/paymentOrderXls"); const FIELD_ENCRYPTION_KEY = Buffer.alloc(32, 7).toString("base64"); const config = { fieldEncryptionKey: FIELD_ENCRYPTION_KEY }; const encrypt = (value) => encryptStringOrThrow(config, value); function buildBatch(overrides = {}) { return { batch_no: "PB-20260802-002", payment_account_snapshot_json: { bankInstitutionCode: "CCB", accountName: "合成测试公司", accountNumberEncrypted: encrypt("6217000000000012345"), }, items: [ { id: "b", entity_code: "BX-20260802-0002", amount: "2.00", snapshot_json: { payee: { name: "乙", bankName: "中国建设银行", bankInstitutionCode: "CCB", bankAccountEncrypted: encrypt("6222000000000056789") } }, }, { id: "a", entity_code: "BX-20260802-0001", amount: "1.20", snapshot_json: { payee: { name: "甲", bankName: "中国建设银行", bankInstitutionCode: "CCB", bankAccountEncrypted: encrypt("6222000000000012345") } }, }, ], ...overrides, }; } describe("payment order BIFF8 generator", () => { it("writes the fixed workbook contract with typed string identifiers and stable ordering", () => { const first = generatePaymentOrderXls(config, buildBatch()); const second = generatePaymentOrderXls(config, buildBatch()); const workbook = XLSX.read(first.buffer, { type: "buffer", cellNF: true }); const sheet = workbook.Sheets.Sheet1; expect(first.buffer.subarray(0, 8).toString("hex")).toBe("d0cf11e0a1b11ae1"); expect(workbook.SheetNames).toEqual(["Sheet1", "Sheet2", "Sheet3"]); expect(XLSX.utils.sheet_to_json(sheet, { header: 1 })[0]).toEqual(HEADERS); expect(sheet.B2).toMatchObject({ t: "s", v: "6217000000000012345" }); expect(sheet.D2).toMatchObject({ t: "s", v: "01" }); expect(sheet.E2).toMatchObject({ t: "s", v: "6222000000000012345" }); expect(sheet.I2).toMatchObject({ t: "s", v: "PB-20260802-002-001" }); expect(sheet.J2).toMatchObject({ t: "n", v: 1.2 }); expect(sheet.M2).toMatchObject({ t: "s", v: "0" }); expect(first.serials).toEqual(second.serials); expect(first.totalAmount).toBe("3.20"); }); it("fails the whole export and reports every blocking payee issue", () => { const batch = buildBatch(); batch.items[1].snapshot_json.payee.bankInstitutionCode = "OTHER"; batch.items[0].snapshot_json.payee.bankAccountEncrypted = null; try { generatePaymentOrderXls(config, batch); throw new Error("expected payment order generation to fail"); } catch (error) { expect(error).toBeInstanceOf(PaymentOrderXlsError); expect(error.code).toBe("payee_bank_not_supported"); expect(error.details.blockers).toEqual(expect.arrayContaining([ { code: "payee_bank_not_supported", entityCode: "BX-20260802-0001" }, { code: "payee_account_unavailable", entityCode: "BX-20260802-0002" }, ])); } }); it("validates customer serial length and character set without truncation", () => { expect(buildCustomerSerial("PB-20260802-002", 1)).toBe("PB-20260802-002-001"); expect(() => buildCustomerSerial("pb invalid", 1)).toThrow(PaymentOrderXlsError); expect(() => buildCustomerSerial("PB-123456789012345678901234567890", 1)).toThrow(PaymentOrderXlsError); }); });