import { Prisma } from "@jorgecuadros/database"; import { BALANCE_FLOOR_JOIN, BALANCE_FORWARD_TYPE, BillingService, NOT_SUPERSEDED, } from "./billing.service"; /** * The balance floor drops rows a later BALANCE FORWARD already accounts for. * * It is worth testing because it fails silently: nothing throws, the numbers are * just wrong, and they were wrong for years — the whole book read +20.6M MXN in * credit because every customer's pre-cutover history was counted twice, once * inside their opening balance and once as itself. */ describe("balance floor", () => { describe("SQL fragments", () => { it("binds the type name rather than interpolating it", () => { // A literal would be a second place to edit if the label ever changes, // and this string reaches SQL from a module constant. expect(BALANCE_FLOOR_JOIN.values).toEqual([BALANCE_FORWARD_TYPE]); }); it("keys the floor to the row's own customer", () => { // Without this the derived table cross-joins and every customer inherits // the earliest BALANCE FORWARD in the book. expect(BALANCE_FLOOR_JOIN.sql).toContain( "bfloor ON bfloor.customerId = t.customerId", ); }); it("takes the most recent opening balance, not the first", () => { // A customer accumulates one BALANCE FORWARD per year. MIN would floor at // the oldest and leave every intervening year double-counted. expect(BALANCE_FLOOR_JOIN.sql).toContain("MAX(bf.transactionDate)"); expect(BALANCE_FLOOR_JOIN.sql).not.toContain("MIN(bf.transactionDate)"); }); it("ignores voided opening balances when locating the floor", () => { expect(BALANCE_FLOOR_JOIN.sql).toContain("bf.voidedAt IS NULL"); }); it("is inclusive of the opening balance row itself", () => { // `>` instead of `>=` would drop the carried balance and understate every // customer by exactly that amount. expect(NOT_SUPERSEDED.sql).toContain("t.transactionDate >= bfloor.floorDate"); expect(NOT_SUPERSEDED.sql).not.toMatch(/transactionDate\s*>\s*bfloor/); }); it("leaves customers with no opening balance untouched", () => { // NULL comparisons are never true, so without the explicit IS NULL branch // a customer who has no BALANCE FORWARD row loses their entire ledger. expect(NOT_SUPERSEDED.sql).toContain("bfloor.floorDate IS NULL"); }); it("only ever references the alias the join defines", () => { // The predicate is useless without the join; pairing them wrongly is a // runtime "unknown column", so keep the alias identical in both. const aliases = NOT_SUPERSEDED.sql.match(/bfloor\.\w+/g) ?? []; expect(aliases.length).toBeGreaterThan(0); for (const ref of aliases) { expect(BALANCE_FLOOR_JOIN.sql).toContain(ref.split(".")[1]); } }); }); describe("statement()", () => { /** * One customer means one floor date, so the statement uses a scalar lookup * instead of the join. Asserting on the `where` Prisma is handed is the only * way to see it without a database. */ function serviceWith(floor: Date | null) { const findMany = jest.fn().mockResolvedValue([]); const prisma = { customer: { findUnique: jest.fn().mockResolvedValue({ id: "c1", name: "CUADROS, JORGE H.", preferredCurrency: "USD", _count: { properties: 0, policies: 0 }, }), }, transaction: { findFirst: jest .fn() .mockResolvedValue(floor ? { transactionDate: floor } : null), findMany, }, }; return { service: new BillingService(prisma as never), prisma, findMany, }; } it("looks the floor up from the customer's newest opening balance", async () => { const { service, prisma } = serviceWith(new Date("2026-01-01T00:00:00Z")); await service.statement("c1"); expect(prisma.transaction.findFirst).toHaveBeenCalledWith( expect.objectContaining({ where: { customerId: "c1", voidedAt: null, type: { nameEn: BALANCE_FORWARD_TYPE }, }, orderBy: { transactionDate: "desc" }, select: { transactionDate: true }, }), ); }); /** * statement() issues two findMany calls: first the period discovery (which * years this customer has an archive for), then the statement rows. Select * the rows query by its shape so adding another lookup later moves nothing * here — the previous version indexed call 0 and broke the moment period * support landed. */ function rowsQuery(findMany: jest.Mock) { const call = findMany.mock.calls.find((c) => c[0]?.orderBy); if (!call) throw new Error("statement() issued no ordered rows query"); return call[0]; } it("bounds the statement at the floor, inclusive", async () => { const floor = new Date("2026-01-01T00:00:00Z"); const { service, findMany } = serviceWith(floor); await service.statement("c1"); expect(rowsQuery(findMany).where).toMatchObject({ customerId: "c1", transactionDate: { gte: floor }, }); }); it("applies no date bound when the customer has no opening balance", async () => { const { service, findMany } = serviceWith(null); await service.statement("c1"); expect(rowsQuery(findMany).where).not.toHaveProperty("transactionDate"); }); it("keeps the source-table exclusion alongside the floor", async () => { // The two guards answer different questions — one reproduces legacy's // DATOS2-only materialization, the other drops superseded history — and // dropping either one changes the customer's balance. const { service, findMany } = serviceWith(new Date("2026-01-01T00:00:00Z")); await service.statement("c1"); const where = rowsQuery(findMany).where; expect(where.OR).toEqual([ { legacySourceTable: null }, { legacySourceTable: { notIn: expect.arrayContaining(["EFECTIVO"]) } }, ]); // Imported periods are windowed rather than excluded: history below the // year start (the only carry a floored-by-archive customer has), never // at or above it (those rows sit inside the next BALANCE FORWARD). expect(where.AND).toEqual([ { OR: [ { legacySourceTable: null }, { legacySourceTable: { not: { startsWith: "datos2@" } } }, { transactionDate: { lt: expect.any(Date) } }, ], }, ]); }); }); describe("regression: NUMid 501", () => { /** * The arithmetic that exposed the bug, pinned so it cannot silently return. * Figures measured against the live ledger on 2026-08-05. */ const openingBalance = new Prisma.Decimal("-6732.29"); const activitySinceOpening = new Prisma.Decimal("-7333.00"); const preCutoverCashAlreadyInOpening = new Prisma.Decimal("3596.00"); it("matches the legacy portal once superseded rows are dropped", () => { expect(openingBalance.plus(activitySinceOpening).toFixed(2)).toBe( "-14065.29", ); }); it("reproduces the wrong figure when they are not", () => { expect( openingBalance .plus(activitySinceOpening) .plus(preCutoverCashAlreadyInOpening) .toFixed(2), ).toBe("-10469.29"); }); }); });