// Integration tests for the AP bill-payments + check-printing + aging +
// health API (Task 3 of docs/superpowers/plans/2026-08-01-ap.md): pay-bills
// flows (partial, full, multi-bill, overpay-to-credit, apply-credit-later),
// void with trial-balance cross-checks and printed-check consumption English,
// the open-bills picker, the aging report (cross-checked against ap_aging()
// called directly), check queue/print/settings flows (manual uniqueness,
// sequential assignment, collision pre-check, concurrent print race), the
// dashboard's overdueBills/billsDueSoon health items, RLS isolation, and
// English validation errors. Same patterns as tests/server/ar-payments.test.ts
// and tests/server/ap-bills.test.ts (createTestDb, app.inject, the seeded
// chart of accounts).

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createTestDb, type TestDb } from "../helpers/db.js";
// Where the database is, resolved once for the whole run: your ADMIN_URL /
// DATABASE_URL if you set one, otherwise the PostgreSQL Balanced Sum installed on
// this machine. Every file in this directory used to carry its own copy of that
// decision, hardcoded to the Docker container that no longer exists.
import { dbUrl } from "../helpers/test-db-admin.js";
import { buildApp } from "../../server/src/app.js";
import type { FastifyInstance } from "fastify";

async function buildTestApp(dbName: string): Promise<FastifyInstance> {
  return buildApp({
    databaseUrl: await dbUrl(dbName, "app", "app"),
    adminDatabaseUrl: await dbUrl(dbName),
  });
}

function post(app: FastifyInstance, companyId: string, url: string, payload?: unknown) {
  return app.inject({
    method: "POST", url, headers: { "x-company-id": companyId }, payload: payload ?? {},
  });
}
function put(app: FastifyInstance, companyId: string, url: string, payload?: unknown) {
  return app.inject({
    method: "PUT", url, headers: { "x-company-id": companyId }, payload: payload ?? {},
  });
}
function get(app: FastifyInstance, companyId: string, url: string) {
  return app.inject({ method: "GET", url, headers: { "x-company-id": companyId } });
}

let db: TestDb;
let app: FastifyInstance;

beforeAll(async () => {
  db = await createTestDb();
  app = await buildTestApp(db.dbName);
}, 60_000);

afterAll(async () => {
  await app.close();
  await db.close();
});

// tests/helpers/db.ts's seeded chart (both companies get an identical copy):
// 1000 Cash (asset), 1100 Accounts Receivable (asset), 1200 Due from Affiliate
// (asset), 2000 Accounts Payable (liability), 2200 Due to Affiliate
// (liability), 3000 Opening Equity (equity), 4000 Revenue (income),
// 6000 Operating Expense (expense).

async function createVendor(name: string, extra: Record<string, unknown> = {}): Promise<string> {
  const res = await post(app, db.companyA, "/api/c/vendors", { name, ...extra });
  expect(res.statusCode).toBe(201);
  return res.json().id;
}

/** Creates a draft bill with one line and immediately issues it. Returns the
 *  bill id and its total. */
async function createIssuedBill(
  vendorId: string, unitPrice: string, extra: Record<string, unknown> = {},
): Promise<{ billId: string; total: string }> {
  const create = await post(app, db.companyA, "/api/c/bills", {
    vendorId,
    billDate: "2024-06-01",
    dueDate: "2024-06-01",
    lines: [{ unitPrice, categoryAccountId: String(db.accounts["6000"]!.A) }],
    ...extra,
  });
  expect(create.statusCode).toBe(201);
  const billId = create.json().billId;
  const issue = await post(app, db.companyA, `/api/c/bills/${billId}/issue`);
  expect(issue.statusCode).toBe(200);
  return { billId, total: create.json().total };
}

async function tbBalance(companyId: string, code: string): Promise<string> {
  const res = await get(app, companyId, "/api/c/reports/trial-balance");
  return res.json().rows.find((r: { code: string }) => r.code === code)?.balance ?? "0.0000";
}

async function billDetail(billId: string) {
  const res = await get(app, db.companyA, `/api/c/bills/${billId}`);
  return res.json();
}

const CASH_A = () => String(db.accounts["1000"]!.A);

// ---------------------------------------------------------------------------
// pay bills — electronic, partial then full, ledger cross-checks
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — electronic, partial then full", () => {
  it("partial payment leaves the bill open with the right applied/open split, A/P down, cash down", async () => {
    const vendorId = await createVendor("Partial Flow Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "1000.00");

    const cashBefore = await tbBalance(db.companyA, "1000");
    const apBefore = await tbBalance(db.companyA, "2000");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "400.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "400.00" }],
    });
    expect(res.statusCode).toBe(201);
    const body = res.json();
    expect(typeof body.paymentId).toBe("string");
    expect(body.summary).toBe("Paid $400.00 to Partial Flow Vendor Co electronically.");

    // Cash down 400 (asset, normal debit — a credit lowers its raw balance).
    // A/P's raw (debit-positive) balance is NEGATIVE while a bill is
    // outstanding (it was CREDITED when the bill was issued); paying it down
    // DEBITS A/P, which moves that raw balance UP (less negative) by 400.
    expect(await tbBalance(db.companyA, "1000")).toBe(addMoney(cashBefore, "-400.0000"));
    expect(await tbBalance(db.companyA, "2000")).toBe(addMoney(apBefore, "400.0000"));

    const detail = await billDetail(billId);
    expect(detail.applied).toBe("400.0000");
    expect(detail.open).toBe("600.0000");
    expect(detail.state).toBe("partial");

    // Second payment pays it off in full.
    const cashMid = await tbBalance(db.companyA, "1000");
    const apMid = await tbBalance(db.companyA, "2000");
    const res2 = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-20", amount: "600.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "600.00" }],
    });
    expect(res2.statusCode).toBe(201);

    expect(await tbBalance(db.companyA, "1000")).toBe(addMoney(cashMid, "-600.0000"));
    expect(await tbBalance(db.companyA, "2000")).toBe(addMoney(apMid, "600.0000"));

    const detail2 = await billDetail(billId);
    expect(detail2.open).toBe("0.0000");
    expect(detail2.applied).toBe("1000.0000");
    expect(detail2.state).toBe("paid");
  });
});

// ---------------------------------------------------------------------------
// pay bills — check method: manual number, queued-to-print, summary English
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — check method", () => {
  it("a manual check number pays the bill and reads the exact summary sentence", async () => {
    const vendorId = await createVendor("Cascade Supply Co");
    const { billId } = await createIssuedBill(vendorId, "500.00");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "500.00",
      moneyAccountId: CASH_A(), method: "check", checkNo: "1024",
      applications: [{ billId, amount: "500.00" }],
    });
    expect(res.statusCode).toBe(201);
    expect(res.json().summary).toBe("Paid $500.00 to Cascade Supply Co by check #1024 from Cash.");

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${res.json().paymentId}`);
    expect(detail.json()).toMatchObject({
      method: "check", checkNo: "1024", checkStatus: "manual", printedOn: null,
    });
  });

  it("a check queued to print reads the queued-to-print summary and has no check number yet", async () => {
    const vendorId = await createVendor("Queued Check Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "250.00");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "250.00",
      moneyAccountId: CASH_A(), method: "check", toPrint: true,
      applications: [{ billId, amount: "250.00" }],
    });
    expect(res.statusCode).toBe(201);
    expect(res.json().summary).toBe("Paid $250.00 to Queued Check Vendor Co by a check queued to print.");

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${res.json().paymentId}`);
    expect(detail.json()).toMatchObject({ method: "check", checkNo: null, checkStatus: "to_print" });
  });

  it("409s in English, NAMING the number, on a duplicate manual check number for the same account", async () => {
    const vendorId = await createVendor("Dup Check No Vendor Co");
    const b1 = await createIssuedBill(vendorId, "10.00");
    const b2 = await createIssuedBill(vendorId, "20.00");

    const first = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "check", checkNo: "5000",
      applications: [{ billId: b1.billId, amount: "10.00" }],
    });
    expect(first.statusCode).toBe(201);

    const second = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-11", amount: "20.00",
      moneyAccountId: CASH_A(), method: "check", checkNo: "5000",
      applications: [{ billId: b2.billId, amount: "20.00" }],
    });
    expect(second.statusCode).toBe(409);
    expect(second.json().error).toBe(
      "Check #5000 has already been used on this account — pick another number or update the next check number in Settings.",
    );

    // The failed attempt didn't leave anything behind — the bill it tried to
    // pay is still fully open.
    expect((await billDetail(b2.billId)).open).toBe("20.0000");
  });

  it("400s in English for a check number of 0 (floor of 1, like every other counter)", async () => {
    const vendorId = await createVendor("Zero Check No Vendor Co");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "check", checkNo: "0", applications: [],
    });
    expect(res.statusCode).toBe(400);
    expect(res.json().error).toMatch(/1 or more/i);
  });

  it.each([
    [{ method: "check" }, /check number|queue it for printing/i],
    [{ method: "check", checkNo: "1", toPrint: true }, /not both/i],
    [{ method: "electronic", checkNo: "1" }, /can't have a check number/i],
    [{ method: "electronic", toPrint: true }, /can't have a check number/i],
  ])("400s in English for an invalid method/check combination %#", async (extra, matcher) => {
    const vendorId = await createVendor("Method Validation Vendor Co " + JSON.stringify(extra));
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), applications: [], ...extra,
    });
    expect(res.statusCode).toBe(400);
    expect(res.json().error).toMatch(matcher);
  });
});

// ---------------------------------------------------------------------------
// multi-bill single payment
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — multi-bill single payment", () => {
  it("applies one payment across two bills, paying both off exactly", async () => {
    const vendorId = await createVendor("Multi Bill Vendor Co");
    const b1 = await createIssuedBill(vendorId, "500.00");
    const b2 = await createIssuedBill(vendorId, "1000.00");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-15", amount: "1500.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [
        { billId: b1.billId, amount: "500.00" },
        { billId: b2.billId, amount: "1000.00" },
      ],
    });
    expect(res.statusCode).toBe(201);

    expect((await billDetail(b1.billId)).open).toBe("0.0000");
    expect((await billDetail(b2.billId)).open).toBe("0.0000");
  });
});

// ---------------------------------------------------------------------------
// overpay becomes vendor credit, applied later
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — overpay becomes credit, applied later", () => {
  it("overpaying a bill keeps the remainder as unapplied vendor credit", async () => {
    const vendorId = await createVendor("Overpay Credit Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "300.00");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "500.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "300.00" }],
    });
    expect(res.statusCode).toBe(201);
    const paymentId = res.json().paymentId;
    expect(res.json().summary).toBe(
      "Paid $500.00 to Overpay Credit Vendor Co electronically. $200.00 kept as vendor credit.",
    );

    const picker = await get(app, db.companyA, `/api/c/vendors/${vendorId}/open-bills`);
    expect(picker.json().unappliedCredit).toBe("200.0000");
    expect(picker.json().bills).toEqual([]); // the only bill is now fully paid

    // Apply the remaining credit to a second, later bill.
    const b2 = await createIssuedBill(vendorId, "150.00");
    const applyRes = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/apply`, {
      applications: [{ billId: b2.billId, amount: "150.00" }],
    });
    expect(applyRes.statusCode).toBe(200);
    expect(applyRes.json().summary).toMatch(/^Applied credit to bill #.+ \(\$150\.00\)\. \$50\.00 credit still available\.$/);

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${paymentId}`);
    expect(detail.json().applied).toBe("450.0000");
    expect(detail.json().unapplied).toBe("50.0000");
    expect((await billDetail(b2.billId)).open).toBe("0.0000");

    const pickerAfter = await get(app, db.companyA, `/api/c/vendors/${vendorId}/open-bills`);
    expect(pickerAfter.json().unappliedCredit).toBe("50.0000");
  });

  it("a payment with no applications at all reads as a pure credit", async () => {
    const vendorId = await createVendor("Pure Credit Vendor Co");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "75.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    expect(res.statusCode).toBe(201);
    expect(res.json().summary).toBe(
      "Paid $75.00 to Pure Credit Vendor Co electronically. $75.00 kept as vendor credit.",
    );
  });
});

// ---------------------------------------------------------------------------
// void — restores bills, restores the ledger, printed-check consumption note
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments/:id/void", () => {
  it("reopens the bill it covered and restores the trial balance", async () => {
    const vendorId = await createVendor("Void Restore Payment Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "700.00");

    const cashAfterIssue = await tbBalance(db.companyA, "1000");
    const apAfterIssue = await tbBalance(db.companyA, "2000");

    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "700.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "700.00" }],
    });
    const paymentId = payRes.json().paymentId;
    expect((await billDetail(billId)).open).toBe("0.0000");

    const voidRes = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`, { date: "2024-06-15" });
    expect(voidRes.statusCode).toBe(200);
    expect(voidRes.json().summary).toBe("Payment voided — the bills it covered are open again.");

    expect((await billDetail(billId)).open).toBe("700.0000");
    expect(await tbBalance(db.companyA, "1000")).toBe(cashAfterIssue);
    expect(await tbBalance(db.companyA, "2000")).toBe(apAfterIssue);

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${paymentId}`);
    expect(detail.json().status).toBe("void");
    expect(detail.json().applied).toBe("700.0000");
    expect(detail.json().unapplied).toBe("0.0000");
  });

  it("a voided pure-credit payment reads differently and its credit disappears", async () => {
    const vendorId = await createVendor("Void Pure Credit Vendor Co");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "100.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    const paymentId = payRes.json().paymentId;

    const voidRes = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);
    expect(voidRes.statusCode).toBe(200);
    expect(voidRes.json().summary).toBe("Payment voided — the vendor credit it created no longer exists.");

    const picker = await get(app, db.companyA, `/api/c/vendors/${vendorId}/open-bills`);
    expect(picker.json().unappliedCredit).toBe("0.0000");
  });

  it("voiding a MANUAL check payment notes that the number is consumed", async () => {
    const vendorId = await createVendor("Void Manual Check Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "40.00");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "40.00",
      moneyAccountId: CASH_A(), method: "check", checkNo: "7001",
      applications: [{ billId, amount: "40.00" }],
    });
    const paymentId = payRes.json().paymentId;

    const voidRes = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);
    expect(voidRes.statusCode).toBe(200);
    expect(voidRes.json().summary).toMatch(/Check #7001 is now void and won't be reused\.$/);

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${paymentId}`);
    expect(detail.json().checkStatus).toBe("void");
    expect(detail.json().checkNo).toBe("7001"); // the number itself stays put, never cleared
  });

  it("voiding a to-print (never assigned) check payment does NOT mention a check number", async () => {
    const vendorId = await createVendor("Void Unassigned Check Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "60.00");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "60.00",
      moneyAccountId: CASH_A(), method: "check", toPrint: true,
      applications: [{ billId, amount: "60.00" }],
    });
    const paymentId = payRes.json().paymentId;

    const voidRes = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);
    expect(voidRes.statusCode).toBe(200);
    expect(voidRes.json().summary).not.toMatch(/check #/i);
  });

  it("409s in English when voiding an already-void payment", async () => {
    const vendorId = await createVendor("Double Void Vendor Co");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    const paymentId = payRes.json().paymentId;
    await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);

    const res = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);
    expect(res.statusCode).toBe(409);
    expect(res.json().error).toMatch(/already been voided/i);
  });

  it("404s for a made-up payment id", async () => {
    const res = await post(app, db.companyA, "/api/c/bill-payments/999999999/void");
    expect(res.statusCode).toBe(404);
  });
});

// ---------------------------------------------------------------------------
// GET list / detail
// ---------------------------------------------------------------------------

describe("GET /api/c/bill-payments — list and detail", () => {
  it("list includes date, vendor, amount, applied/unapplied, method, status, and an entry link", async () => {
    const vendorId = await createVendor("List Shape Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "200.00");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "200.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "200.00" }],
    });
    const paymentId = payRes.json().paymentId;

    const list = await get(app, db.companyA, `/api/c/bill-payments?vendorId=${vendorId}`);
    expect(list.statusCode).toBe(200);
    const row = list.json().payments.find((p: { paymentId: string }) => p.paymentId === paymentId);
    expect(row).toMatchObject({
      date: "2024-06-10", vendorId, vendorName: "List Shape Vendor Co",
      amount: "200.0000", applied: "200.0000", unapplied: "0.0000",
      status: "active", method: "electronic",
    });
    expect(typeof row.postedEntryId).toBe("string");
  });

  it("detail includes applications with bill links and an entry link", async () => {
    const vendorId = await createVendor("Detail Shape Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "80.00", { refNo: "INV-DETAIL-1" });
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "80.00", memo: "wire",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "80.00" }],
    });
    const paymentId = payRes.json().paymentId;

    const detail = await get(app, db.companyA, `/api/c/bill-payments/${paymentId}`);
    expect(detail.statusCode).toBe(200);
    const body = detail.json();
    expect(body.memo).toBe("wire");
    expect(body.moneyAccountName).toBe("Cash");
    expect(body.applications).toEqual([
      expect.objectContaining({ billId, refNo: "INV-DETAIL-1", amount: "80.0000" }),
    ]);
    expect(typeof body.postedEntryId).toBe("string");
  });

  it("404s for a made-up payment id", async () => {
    const res = await get(app, db.companyA, "/api/c/bill-payments/999999999");
    expect(res.statusCode).toBe(404);
  });
});

// ---------------------------------------------------------------------------
// open-bills picker
// ---------------------------------------------------------------------------

describe("GET /api/c/vendors/:id/open-bills", () => {
  it("lists only open bills with money owed (incl. document_only), excludes paid ones", async () => {
    const vendorId = await createVendor("Picker Shape Vendor Co");
    const open1 = await createIssuedBill(vendorId, "100.00");
    const openDocOnly = await createIssuedBill(vendorId, "50.00", { documentOnly: true });
    const paid = await createIssuedBill(vendorId, "40.00");
    await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "40.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId: paid.billId, amount: "40.00" }],
    });

    const res = await get(app, db.companyA, `/api/c/vendors/${vendorId}/open-bills`);
    expect(res.statusCode).toBe(200);
    const ids = res.json().bills.map((b: { billId: string }) => b.billId);
    expect(ids).toContain(open1.billId);
    expect(ids).toContain(openDocOnly.billId);
    expect(ids).not.toContain(paid.billId);

    const row = res.json().bills.find((b: { billId: string }) => b.billId === open1.billId);
    expect(row).toMatchObject({ billDate: "2024-06-01", dueDate: "2024-06-01", total: "100.0000", open: "100.0000" });
    expect(typeof row.overdue).toBe("boolean");
    expect(typeof res.json().unappliedCredit).toBe("string");
  });

  it("404s for a made-up vendor id", async () => {
    const res = await get(app, db.companyA, "/api/c/vendors/999999999/open-bills");
    expect(res.statusCode).toBe(404);
  });
});

// ---------------------------------------------------------------------------
// aging — bucket math cross-checked against ap_aging() directly
// ---------------------------------------------------------------------------

describe("GET /api/c/reports/ap-aging", () => {
  it("buckets by due date relative to asOf, includes a credit row, matches hand-computed amounts", async () => {
    const vendorId = await createVendor("Aging Bucket Vendor Co");
    const asOf = "2024-06-01";
    await createIssuedBill(vendorId, "100.00", { dueDate: "2024-06-10" }); // current
    await createIssuedBill(vendorId, "200.00", { dueDate: "2024-05-20" }); // 1-30
    await createIssuedBill(vendorId, "300.00", { dueDate: "2024-04-20" }); // 31-60
    await createIssuedBill(vendorId, "400.00", { dueDate: "2024-03-20" }); // 61-90
    await createIssuedBill(vendorId, "500.00", { dueDate: "2024-01-01" }); // 90+
    await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-05-01", amount: "50.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });

    const res = await get(app, db.companyA, `/api/c/reports/ap-aging?asOf=${asOf}`);
    expect(res.statusCode).toBe(200);
    expect(res.json().asOf).toBe(asOf);
    const row = res.json().vendors.find((v: { vendorId: string }) => v.vendorId === vendorId);
    expect(row.buckets).toEqual({
      current: "100.0000", d1_30: "200.0000", d31_60: "300.0000",
      d61_90: "400.0000", d90_plus: "500.0000",
    });
    expect(row.credits).toBe("50.0000");
    expect(row.total).toBe("1500.0000");

    const direct = await db.admin.query<{ grand: string; credits: string }>(
      `SELECT coalesce(sum(open_amount) FILTER (WHERE row_type = 'bill'), 0)::numeric(19,4) AS grand,
              coalesce(sum(open_amount) FILTER (WHERE row_type = 'credit'), 0)::numeric(19,4) AS credits
         FROM ap_aging($1, $2::date)`,
      [db.companyA, asOf],
    );
    expect(res.json().totals.grand).toBe(direct.rows[0]!.grand);
    expect(res.json().totals.credits).toBe(direct.rows[0]!.credits);
  });

  it("defaults asOf to today when omitted", async () => {
    const res = await get(app, db.companyA, "/api/c/reports/ap-aging");
    expect(res.statusCode).toBe(200);
    expect(typeof res.json().asOf).toBe("string");
  });
});

// ---------------------------------------------------------------------------
// concurrency (API level) — mirrors ar-payments.test.ts's identical race test.
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — concurrency", () => {
  it("two concurrent payments applying 60+60 to a 100 bill: one 201, one English 422, final open 40.00", async () => {
    const vendorId = await createVendor("API Concurrency Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "100.00");

    const attempt = () => post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "60.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "60.00" }],
    });

    const [r1, r2] = await Promise.all([attempt(), attempt()]);
    const statuses = [r1.statusCode, r2.statusCode].sort();
    expect(statuses).toEqual([201, 422]);

    const failed = r1.statusCode === 422 ? r1 : r2;
    expect(failed.json().error).toMatch(/more than this bill's balance/i);

    expect((await billDetail(billId)).open).toBe("40.0000");
  });
});

// ---------------------------------------------------------------------------
// RLS isolation
// ---------------------------------------------------------------------------

describe("RLS isolation", () => {
  it("company B cannot see, void, or reach company A's bill payments", async () => {
    const vendorId = await createVendor("RLS Payment Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "60.00");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "60.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "60.00" }],
    });
    const paymentId = payRes.json().paymentId;

    const detail = await get(app, db.companyB, `/api/c/bill-payments/${paymentId}`);
    expect(detail.statusCode).toBe(404);

    const list = await get(app, db.companyB, "/api/c/bill-payments");
    const ids = list.json().payments.map((p: { paymentId: string }) => p.paymentId);
    expect(ids).not.toContain(paymentId);

    const voidRes = await post(app, db.companyB, `/api/c/bill-payments/${paymentId}/void`);
    expect(voidRes.statusCode).toBe(404);
  });
});

// ---------------------------------------------------------------------------
// validation — English 400/422s
// ---------------------------------------------------------------------------

describe("POST /api/c/bill-payments — validation", () => {
  it.each(["12.345", "-5", "abc", "0", "007"])("400s for an invalid amount %s", async (amount) => {
    const vendorId = await createVendor("Bad Amount Vendor Co " + amount);
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount,
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    expect(res.statusCode).toBe(400);
  });

  it("422s in English when the money account is not an asset account", async () => {
    const vendorId = await createVendor("Bad Money Account Vendor Co");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: String(db.accounts["6000"]!.A), method: "electronic", applications: [],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/money account/i);
  });

  it("404s in English for an unknown vendorId", async () => {
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId: "999999999", paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    expect(res.statusCode).toBe(404);
    expect(res.json().error).toMatch(/vendor/i);
  });

  it("422s with the DB's own English when an application's bill belongs to a different vendor", async () => {
    const vendorA = await createVendor("Cross Vendor A");
    const vendorB = await createVendor("Cross Vendor B");
    const { billId } = await createIssuedBill(vendorB, "50.00");

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId: vendorA, paymentDate: "2024-06-10", amount: "50.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "50.00" }],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toBe("That bill doesn't belong to this vendor.");
  });

  it("422s in English when only a draft (unissued) bill is targeted", async () => {
    const vendorId = await createVendor("Draft Target Vendor Co");
    const create = await post(app, db.companyA, "/api/c/bills", {
      vendorId, billDate: "2024-06-01",
      lines: [{ unitPrice: "10.00", categoryAccountId: String(db.accounts["6000"]!.A) }],
    });
    const billId = create.json().billId;

    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "10.00" }],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/issued, unpaid bill/i);
  });

  it("422s in English when an application would over-apply the bill", async () => {
    const vendorId = await createVendor("Bill Overapply API Vendor Co");
    const { billId } = await createIssuedBill(vendorId, "50.00");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "100.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId, amount: "100.00" }],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/more than this bill's balance/i);
  });

  it("422s in English when applications sum to more than the payment's own amount", async () => {
    const vendorId = await createVendor("Payment Overapply API Vendor Co");
    const b1 = await createIssuedBill(vendorId, "1000.00");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "50.00",
      moneyAccountId: CASH_A(), method: "electronic",
      applications: [{ billId: b1.billId, amount: "100.00" }],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/more than this payment covers/i);
  });

  it("400s for an apply request with no applications", async () => {
    const vendorId = await createVendor("Empty Apply Vendor Co");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    const res = await post(app, db.companyA, `/api/c/bill-payments/${payRes.json().paymentId}/apply`, {
      applications: [],
    });
    expect(res.statusCode).toBe(400);
    expect(res.json().error).toMatch(/at least one bill/i);
  });

  it("409s in English when applying credit from a voided payment", async () => {
    const vendorId = await createVendor("Apply After Void Vendor Co");
    const payRes = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic", applications: [],
    });
    const paymentId = payRes.json().paymentId;
    await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/void`);

    const { billId } = await createIssuedBill(vendorId, "10.00");
    const res = await post(app, db.companyA, `/api/c/bill-payments/${paymentId}/apply`, {
      applications: [{ billId, amount: "10.00" }],
    });
    expect(res.statusCode).toBe(409);
    expect(res.json().error).toMatch(/voided and can't be used/i);
  });

  // Regression coverage (blank-text normalization): a whitespace-only memo
  // stores NULL, not a literal empty string (mirrors ar-payments.test.ts's
  // identical AR regression test).
  it("a whitespace-only memo stores NULL, not a literal empty string", async () => {
    const vendorId = await createVendor("Whitespace Memo Vendor Co");
    const res = await post(app, db.companyA, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: CASH_A(), method: "electronic", memo: "   ", applications: [],
    });
    expect(res.statusCode).toBe(201);
    const detail = await get(app, db.companyA, `/api/c/bill-payments/${res.json().paymentId}`);
    expect(detail.json().memo).toBeNull();
  });
});

// ---------------------------------------------------------------------------
// checks — queue, print run (sequential numbers, collision pre-check,
// concurrent race), settings roundtrip. Isolated on company B's own money
// account (2200 Due to Affiliate is a liability, so 1000 Cash on company B
// is used — a fresh vendor/bill fixture per test keeps these independent of
// every company-A test above).
// ---------------------------------------------------------------------------

describe("checks — queue, print, settings", () => {
  async function createVendorB(name: string): Promise<string> {
    const res = await post(app, db.companyB, "/api/c/vendors", { name });
    expect(res.statusCode).toBe(201);
    return res.json().id;
  }

  async function createIssuedBillB(vendorId: string, unitPrice: string): Promise<string> {
    const create = await post(app, db.companyB, "/api/c/bills", {
      vendorId, billDate: "2024-06-01", dueDate: "2024-06-01",
      lines: [{ unitPrice, categoryAccountId: String(db.accounts["6000"]!.B) }],
    });
    expect(create.statusCode).toBe(201);
    const billId = create.json().billId;
    const issue = await post(app, db.companyB, `/api/c/bills/${billId}/issue`);
    expect(issue.statusCode).toBe(200);
    return billId;
  }

  async function payToPrintB(vendorId: string, billId: string, amount: string): Promise<string> {
    const res = await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount,
      moneyAccountId: String(db.accounts["1000"]!.B), method: "check", toPrint: true,
      applications: [{ billId, amount }],
    });
    expect(res.statusCode).toBe(201);
    return res.json().paymentId;
  }

  it("queue lists to-print payments for the account and reports the next check number", async () => {
    const vendorId = await createVendorB("Queue Shape Vendor Co");
    const b1 = await createIssuedBillB(vendorId, "111.00");
    const p1 = await payToPrintB(vendorId, b1, "111.00");

    const res = await get(app, db.companyB, `/api/c/checks/queue?accountId=${db.accounts["1000"]!.B}`);
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(typeof body.nextCheckNo).toBe("string");
    const row = body.payments.find((p: { paymentId: string }) => p.paymentId === p1);
    expect(row).toMatchObject({ vendorId, vendorName: "Queue Shape Vendor Co", amount: "111.0000" });
  });

  it("print run assigns sequential numbers in (date, id) order and returns everything the print page needs", async () => {
    const vendorId = await createVendorB("Print Run Vendor Co");
    const bOlder = await createIssuedBillB(vendorId, "50.00");
    const bNewer = await createIssuedBillB(vendorId, "75.00");
    // Queued out of date order on purpose — assignment must still follow
    // (payment_date, id), not array order.
    const pNewer = await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-07-02", amount: "75.00",
      moneyAccountId: String(db.accounts["1000"]!.B), method: "check", toPrint: true,
      applications: [{ billId: bNewer, amount: "75.00" }],
    }).then((r) => r.json().paymentId);
    const pOlder = await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-07-01", amount: "50.00",
      moneyAccountId: String(db.accounts["1000"]!.B), method: "check", toPrint: true,
      applications: [{ billId: bOlder, amount: "50.00" }],
    }).then((r) => r.json().paymentId);

    const settingsBefore = await get(app, db.companyB, "/api/c/settings/checks");
    const account = settingsBefore.json().accounts.find((a: { accountId: string }) => a.accountId === String(db.accounts["1000"]!.B));
    const startNo = BigInt(account.nextCheckNo);

    const res = await post(app, db.companyB, "/api/c/checks/print", {
      accountId: String(db.accounts["1000"]!.B),
      paymentIds: [pNewer, pOlder], // out of date order in the request
      format: "voucher", offsetX: 1.5, offsetY: -2,
    });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.format).toBe("voucher");
    expect(body.offsets).toEqual({ x: "1.50", y: "-2.00" });
    expect(body.assignments).toHaveLength(2);

    // pOlder (2024-07-01) must get the LOWER number than pNewer (2024-07-02).
    const olderAssignment = body.assignments.find((a: { paymentId: string }) => a.paymentId === pOlder);
    const newerAssignment = body.assignments.find((a: { paymentId: string }) => a.paymentId === pNewer);
    expect(BigInt(olderAssignment.checkNo)).toBe(startNo);
    expect(BigInt(newerAssignment.checkNo)).toBe(startNo + 1n);
    expect(olderAssignment).toMatchObject({
      vendorName: "Print Run Vendor Co", amount: "50.0000", date: "2024-07-01",
      amountWords: "Fifty and 00/100 dollars",
    });

    // Format/offsets persisted to company_settings — a subsequent GET reflects them.
    const settingsAfter = await get(app, db.companyB, "/api/c/settings/checks");
    expect(settingsAfter.json().format).toBe("voucher");
    expect(settingsAfter.json().offsetX).toBe("1.50");
    expect(settingsAfter.json().offsetY).toBe("-2.00");

    // The account's next check number advanced past both assignments.
    const accountAfter = settingsAfter.json().accounts.find(
      (a: { accountId: string }) => a.accountId === String(db.accounts["1000"]!.B));
    expect(BigInt(accountAfter.nextCheckNo)).toBe(startNo + 2n);

    // The payments themselves flipped to 'printed'.
    const detailOlder = await get(app, db.companyB, `/api/c/bill-payments/${pOlder}`);
    expect(detailOlder.json()).toMatchObject({ checkStatus: "printed", checkNo: startNo.toString() });
    expect(typeof detailOlder.json().printedOn).toBe("string");
  });

  it("409s in English (naming the number) when the assigned range would collide with a manual check number", async () => {
    const vendorId = await createVendorB("Collision Vendor Co");
    const accountId = String(db.accounts["1000"]!.B);

    // Push the counter's next number up to a known value first, then hand-
    // write a manual check for exactly that number on the SAME account, so
    // assign_check_numbers' very first assignment would collide with it.
    const putRes = await put(app, db.companyB, "/api/c/settings/checks", {
      accounts: [{ accountId, nextCheckNo: 9000 }],
    });
    expect(putRes.statusCode).toBe(200);

    const bManual = await createIssuedBillB(vendorId, "10.00");
    await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "10.00",
      moneyAccountId: accountId, method: "check", checkNo: "9000",
      applications: [{ billId: bManual, amount: "10.00" }],
    }).then((r) => expect(r.statusCode).toBe(201));

    const bQueued = await createIssuedBillB(vendorId, "20.00");
    const pQueued = await payToPrintB(vendorId, bQueued, "20.00");

    const res = await post(app, db.companyB, "/api/c/checks/print", {
      accountId, paymentIds: [pQueued],
    });
    expect(res.statusCode).toBe(409);
    expect(res.json().error).toMatch(/9000/);
    expect(res.json().error).toMatch(/set the next check number/i);

    // Nothing was assigned — the payment is still queued to print.
    const detail = await get(app, db.companyB, `/api/c/bill-payments/${pQueued}`);
    expect(detail.json().checkStatus).toBe("to_print");
  });

  it("settings roundtrip: format/offsets and a per-account next check number", async () => {
    const accountId = String(db.accounts["1000"]!.B);
    const putRes = await put(app, db.companyB, "/api/c/settings/checks", {
      format: "three_per_page", offsetX: 0.25, offsetY: 0,
      accounts: [{ accountId, nextCheckNo: 55000 }],
    });
    expect(putRes.statusCode).toBe(200);
    const body = putRes.json();
    expect(body.format).toBe("three_per_page");
    expect(body.offsetX).toBe("0.25");
    const account = body.accounts.find((a: { accountId: string }) => a.accountId === accountId);
    expect(account.nextCheckNo).toBe("55000");

    const getRes = await get(app, db.companyB, "/api/c/settings/checks");
    expect(getRes.json().format).toBe("three_per_page");
    const accountGet = getRes.json().accounts.find((a: { accountId: string }) => a.accountId === accountId);
    expect(accountGet.nextCheckNo).toBe("55000");
  });

  it("422s in English when setting a next check number that doesn't exceed the highest used", async () => {
    const vendorId = await createVendorB("Too Low Next Check Vendor Co");
    const accountId = String(db.accounts["1000"]!.B);
    const bill = await createIssuedBillB(vendorId, "30.00");
    await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: "2024-06-10", amount: "30.00",
      moneyAccountId: accountId, method: "check", checkNo: "77000",
      applications: [{ billId: bill, amount: "30.00" }],
    }).then((r) => expect(r.statusCode).toBe(201));

    const res = await put(app, db.companyB, "/api/c/settings/checks", {
      accounts: [{ accountId, nextCheckNo: 77000 }],
    });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/greater than the highest one already used/i);
  });

  // API-level concurrent print race: two POST /checks/print calls targeting
  // the EXACT SAME payment id must not both succeed — assign_check_numbers'
  // atomic per-payment lock+status-check means whichever call runs second
  // finds the payment already flipped to 'printed' and fails cleanly in
  // English, never a raw 500 or a duplicate check number.
  describe("concurrent print run (API level)", () => {
    it("targeting the same payment: one succeeds, the other fails in English, no duplicate number", async () => {
      const vendorId = await createVendorB("Concurrent Print Vendor Co");
      const bill = await createIssuedBillB(vendorId, "90.00");
      const paymentId = await payToPrintB(vendorId, bill, "90.00");
      const accountId = String(db.accounts["1000"]!.B);

      const attempt = () => post(app, db.companyB, "/api/c/checks/print", {
        accountId, paymentIds: [paymentId],
      });

      const [r1, r2] = await Promise.all([attempt(), attempt()]);
      const statuses = [r1.statusCode, r2.statusCode].sort();
      expect(statuses[0]).toBe(200);
      expect(statuses[1]).not.toBe(500);
      expect(statuses[1]).toBeGreaterThanOrEqual(400);

      const winner = r1.statusCode === 200 ? r1 : r2;
      expect(winner.json().assignments).toHaveLength(1);

      const detail = await get(app, db.companyB, `/api/c/bill-payments/${paymentId}`);
      expect(detail.json().checkStatus).toBe("printed");
      expect(detail.json().checkNo).toBe(winner.json().assignments[0].checkNo);
    });
  });
});

// ---------------------------------------------------------------------------
// dashboard health — overdueBills / billsDueSoon, isolated on company B to
// avoid interference from every other test in this file (all of which use
// company A) — mirrors ar-payments.test.ts's identical oldestReceivable
// isolation rationale. Uses its own vendor/bill fixtures separate from the
// "checks" describe block above (also company B) via distinct vendor names.
// ---------------------------------------------------------------------------

describe("GET /api/c/reports/dashboard — health.overdueBills / billsDueSoon", () => {
  async function createVendorB(name: string): Promise<string> {
    const res = await post(app, db.companyB, "/api/c/vendors", { name });
    expect(res.statusCode).toBe(201);
    return res.json().id;
  }

  async function createIssuedBillB(
    vendorId: string, unitPrice: string, dueDate: string,
  ): Promise<{ billId: string; total: string }> {
    const create = await post(app, db.companyB, "/api/c/bills", {
      vendorId, billDate: "2024-01-01", dueDate,
      lines: [{ unitPrice, categoryAccountId: String(db.accounts["6000"]!.B) }],
    });
    expect(create.statusCode).toBe(201);
    const billId = create.json().billId;
    const issue = await post(app, db.companyB, `/api/c/bills/${billId}/issue`);
    expect(issue.statusCode).toBe(200);
    return { billId, total: create.json().total };
  }

  it("counts and totals overdue bills and bills due within 7 days, excluding paid/void/draft/document_only-not-issued", async () => {
    const vendorId = await createVendorB("Health Bills Vendor Co");
    const today = await db.admin.query<{ today: string }>("SELECT current_date::text AS today");
    const asDate = (offsetDays: number): string => {
      const d = new Date(`${today.rows[0]!.today}T00:00:00Z`);
      d.setUTCDate(d.getUTCDate() + offsetDays);
      return d.toISOString().slice(0, 10);
    };

    const overdue1 = await createIssuedBillB(vendorId, "100.00", asDate(-10));
    const overdue2 = await createIssuedBillB(vendorId, "50.00", asDate(-1));
    const dueSoon = await createIssuedBillB(vendorId, "75.00", asDate(3));
    const dueFar = await createIssuedBillB(vendorId, "200.00", asDate(30));

    // Paid off in full — must not count toward overdue even though its due
    // date is in the past.
    const paidOverdue = await createIssuedBillB(vendorId, "10.00", asDate(-5));
    await post(app, db.companyB, "/api/c/bill-payments", {
      vendorId, paymentDate: today.rows[0]!.today, amount: "10.00",
      moneyAccountId: String(db.accounts["1000"]!.B), method: "electronic",
      applications: [{ billId: paidOverdue.billId, amount: "10.00" }],
    });

    const res = await get(app, db.companyB, "/api/c/reports/dashboard");
    expect(res.statusCode).toBe(200);
    const health = res.json().health;

    // These are exact-total assertions scoped to what THIS test created —
    // company B's dashboard totals reflect every bill any test in this file
    // has created for company B, so this test intentionally uses due dates
    // relative to "today" that no other company-B fixture in this file
    // shares, and sums only the two overdue / one due-soon bill above.
    expect(health.overdueBills.count).toBeGreaterThanOrEqual(2);
    expect(health.billsDueSoon.count).toBeGreaterThanOrEqual(1);

    // Cross-check against a from-scratch SQL computation for exactly this
    // vendor's bills, never trusting the API's own arithmetic against itself.
    const direct = await db.admin.query<{ overdue_total: string; due_soon_total: string }>(
      `SELECT
         coalesce(sum(bill_open_balance($1, b.id)) FILTER (WHERE b.due_date < current_date), 0)::numeric(19,4)
           AS overdue_total,
         coalesce(sum(bill_open_balance($1, b.id))
           FILTER (WHERE b.due_date >= current_date AND b.due_date <= current_date + 7), 0)::numeric(19,4)
           AS due_soon_total
         FROM bills b
        WHERE b.company_id = $1 AND b.vendor_id = $2 AND b.status = 'open'
          AND bill_open_balance($1, b.id) > 0`,
      [db.companyB, vendorId],
    );
    expect(direct.rows[0]!.overdue_total).toBe(addMoney(overdue1.total, overdue2.total));
    expect(direct.rows[0]!.due_soon_total).toBe(dueSoon.total);
    void dueFar; // included in the fixture only to prove it's NOT counted (implicitly, via the exact sums above)
  });

  it("is zero when there are no open bills at all", async () => {
    // A brand-new company (created inline, not the shared companyB fixture)
    // guarantees a truly empty slate.
    const comps = await db.admin.query<{ id: string }>(
      "INSERT INTO companies (name) VALUES ('Empty Bills Health Co') RETURNING id",
    );
    const companyId = comps.rows[0]!.id;
    const users = await db.admin.query<{ id: string }>(
      "INSERT INTO users (email, display_name) VALUES ('empty-bills-health@example.com', 'Owner') RETURNING id",
    );
    await db.admin.query(
      "INSERT INTO user_companies (user_id, company_id, role) VALUES ($1, $2, 'owner')",
      [users.rows[0]!.id, companyId],
    );

    const res = await get(app, companyId, "/api/c/reports/dashboard");
    expect(res.statusCode).toBe(200);
    expect(res.json().health.overdueBills).toEqual({ count: 0, total: "0.0000" });
    expect(res.json().health.billsDueSoon).toEqual({ count: 0, total: "0.0000" });
  });
});

// ---------------------------------------------------------------------------
// money-string helper (test-only) — plain integer-cents arithmetic, never a
// parsed JS float, same technique as ar-payments.test.ts's identical helper.
// ---------------------------------------------------------------------------
function addMoney(a: string, b: string): string {
  const toMicros = (s: string): bigint => {
    const negative = s.startsWith("-");
    const unsigned = negative ? s.slice(1) : s;
    const [wholeRaw, fracRaw = ""] = unsigned.split(".");
    const frac = (fracRaw + "0000").slice(0, 4);
    const v = BigInt(wholeRaw || "0") * 10000n + BigInt(frac || "0");
    return negative ? -v : v;
  };
  const sum = toMicros(a) + toMicros(b);
  const negative = sum < 0n;
  const abs = negative ? -sum : sum;
  const whole = abs / 10000n;
  const fracPart = (abs % 10000n).toString().padStart(4, "0");
  return `${negative ? "-" : ""}${whole}.${fracPart}`;
}
