文件
hyb-oa/server/tests/unit/services/paymentReceiptMatching.test.js
wangsli 8d08d46d16
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
fix: 增加回单核对与付款登记门禁(09-08-reimbursement-payment-closeout)
2026-09-15 07:21:55 +08:00

116 行
5.1 KiB
JavaScript

const {
normalizeBankSlip,
matchBankSlip,
parseReceiptMinorUnits,
} = require("../../../src/services/paymentReceiptMatching");
function fields(overrides = {}) {
return Object.entries({
小写金额: "1,234.56", 币别: "人民币", 付款人账号: "000011112222",
收款人账号: "000033334444", 单据状态: "交易成功", 凭证号: "SYN-0001",
...overrides,
}).map(([Name, Value]) => ({ Name, Value }));
}
function candidates() {
return [{ id: "synthetic-item", amount: "1234.56", currency: "CNY",
payerAccount: "000011112222", payeeAccount: "000033334444" }];
}
describe("bank receipt normalization", () => {
test.each([
["1,234.56", "123456"], ["123.45", "12345"], ["0.01", "1"],
["1234", "123400"], ["¥ 1234.56 元", "123456"], ["1,23.45", null],
["123.456", null], ["-1.00", null], ["1e3", null], ["", null],
["已付123.45", null], ["1 234.56", null],
])("parses exact money without guessing %s", (input, expected) => {
expect(parseReceiptMinorUnits(input)).toBe(expected);
});
test("normalizes actual provider field aliases using synthetic values", () => {
const result = normalizeBankSlip(fields());
expect(result).toMatchObject({ amount: "123456", currency: "CNY",
payerAccount: "000011112222", payeeAccount: "000033334444",
transactionStatus: "success", reference: "SYN-0001", issues: [] });
});
test("does not use fees or uppercase text as transaction amount", () => {
const result = normalizeBankSlip(fields({ 小写金额: "", 手续费: "1234.56", 大写金额: "壹仟贰佰叁拾肆元伍角陆分" }));
expect(result.amount).toBeNull();
});
test("conflicting aliases are not silently resolved", () => {
const result = normalizeBankSlip(fields({ 交易金额: "999.00" }));
expect(result.amount).toBeNull();
expect(result.issues).toContain("amount_ambiguous");
});
test("repeated semantic fields may represent multiple receipts on one page", () => {
const result = normalizeBankSlip([...fields(), ...fields()]);
expect(result.issues).toContain("amount_ambiguous");
});
test.each(["已受理", "交易成功处理中", "未成功", "", "已打印"])("does not infer payment success from %s", (status) => {
expect(normalizeBankSlip(fields({ 单据状态: status })).transactionStatus).not.toBe("success");
});
test("masked accounts stay unresolved", () => {
expect(normalizeBankSlip(fields({ 收款人账号: "****4444" })).payeeAccount).toBeNull();
});
test("invalid response does not become an empty successful receipt", () => {
expect(normalizeBankSlip(null).issues).toContain("response_invalid");
});
});
describe("bank receipt candidate matching", () => {
test("matches one exact transaction but does not declare bank verification", () => {
expect(matchBankSlip(normalizeBankSlip(fields()), candidates()))
.toEqual({ status: "matched", itemId: "synthetic-item", reasons: [], bankVerified: false });
});
test.each([
[{ 小写金额: "1234.55" }, "amount_mismatch"],
[{ 收款人账号: "999933334444" }, "payee_account_mismatch"],
[{ 付款人账号: "999911112222" }, "payer_account_mismatch"],
[{ 币别: "USD" }, "currency_mismatch"],
])("blocks a known mismatch", (overrides, reason) => {
const result = matchBankSlip(normalizeBankSlip(fields(overrides)), candidates());
expect(result.status).toBe("mismatch");
expect(result.reasons).toContain(reason);
});
test.each(["交易失败", "已撤销", "已冲正", "已退汇"])("blocks %s", (status) => {
expect(matchBankSlip(normalizeBankSlip(fields({ 单据状态: status })), candidates()).status).toBe("mismatch");
});
test.each(["已受理", "未知", ""])("requires review for status %s", (status) => {
expect(matchBankSlip(normalizeBankSlip(fields({ 单据状态: status })), candidates()).status).toBe("needs_review");
});
test("requires a transaction reference for downstream duplicate protection", () => {
expect(matchBankSlip(normalizeBankSlip(fields({ 凭证号: "" })), candidates()).reasons).toContain("reference_unresolved");
});
test("same-account same-amount items require disambiguation", () => {
expect(matchBankSlip(normalizeBankSlip(fields()), [...candidates(), { ...candidates()[0], id: "second-item" }]).status)
.toBe("needs_review");
});
test("does not match receipt against combined batch total", () => {
const items = [ { ...candidates()[0], amount: "600.00" }, { ...candidates()[0], id: "second-item", amount: "634.56" } ];
expect(matchBankSlip(normalizeBankSlip(fields()), items).status).toBe("mismatch");
});
test("does not match missing expected accounts", () => {
expect(matchBankSlip(normalizeBankSlip(fields()), [{ ...candidates()[0], payerAccount: null }]).status)
.toBe("needs_review");
});
test("does not return receipt values in public matching results", () => {
const result = matchBankSlip(normalizeBankSlip(fields()), candidates());
expect(JSON.stringify(result)).not.toContain("000011112222");
expect(JSON.stringify(result)).not.toContain("SYN-0001");
});
});