aa5867c8ea41ae63e3668c45522f21d931aef913
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aa5867c8ea |
fix(statements): an archive is history below the year start, not nothing
|
||
|
|
c6feae9522 |
fix(customers): the customer file's balances are the statement's balances
The /clientes/:id ledger card is titled "Estado de cuenta" and links straight to
the statement, but its per-line tiles came from a groupBy with `voidedAt: null`
and nothing else — no balance floor, no source exclusion, no outstanding rule.
It was a raw lifetime sum, double-counting the pre-cutover history that each
BALANCE FORWARD row already absorbs, and the card said so in its own footnote
rather than being fixed.
Importing prior periods turned that from wrong into badly wrong. Every closed
year is now also held as its own tagged copy, so an unfloored sum adds each one
a second time on top of the opening balance that contains it. NUMid 501 read
-7,119.29 before the archives landed and -15,270.59 after, against a true
-10,715.29 — the gap being exactly the 2024 and 2025 closing balances.
The tiles now take the same three rules the statement takes, and agree with it
for all 600 customers sampled.
Two places needed the archives excluded explicitly, because the balance floor
does not do it:
- A customer whose newest BALANCE FORWARD lives inside an archive floors at
that archive's own January 1, so every row of it clears the floor. One
customer, 28 rows.
- The archives are not cleanly bounded. datos2@2024 carries rows dated 2022,
2023, 2025 and one in 2026; datos2@2025 two more. Those clear any floor and
land in the current year next to the live ledger's own copy of them.
That second point is a defect in
|
||
|
|
93f817158e |
feat(statements): a year selector, reading each closed year from its archive
The statement has been pinned to the calendar year in progress since
|
||
|
|
d173c9e9a0 |
fix(billing): stop double-counting history a BALANCE FORWARD already carries
BALANCE FORWARD rows are not movements. Access materialized one per customer per year, dated Jan 1, holding the closing balance of everything before it — that is what let the portal keep each year in its own table and still show a correct running balance from one year's rows. The platform imported those rows AND the real pre-cutover history they summarize, and every balance aggregate summed the lot. NUMid 501 read -10,469.29 on the receivables worklist against -14,065.29 on the customer's own statement and on the legacy portal; the gap was two cash receipts from 2009 and 2012 that the 2026 opening balance had already absorbed. The scale settles what it is: summed the old way the whole book came to +20,605,447.86 MXN — the office owing its customers 20.6 million pesos. Floored, it is -56,855.90. A receivables ledger cannot be 20M in credit. Adds BALANCE_FLOOR_JOIN + NOT_SUPERSEDED and applies them to balances() (page and count queries, which must agree), to stats()'s per-currency and per-domain figures, and to the owing/in-credit split. The four stats() aggregates moved from Prisma groupBy to raw SQL because groupBy cannot express a per-customer floor. statement() takes the same floor as a scalar, which is also what stops FEE ANUAL and fee15 leaking in. Those are not in STATEMENT_EXCLUDED_SOURCE_TABLES — that list reproduces legacy's DATOS2-only `datosfreak` — and they were putting 2,092 pre-cutover fee rows across 1,062 customers into the statement, skewing it by -5,129,764 against the number those customers have been quoted for years. Dating rather than source is the right test: a fee row *after* the opening balance is a real charge and still counts. movements() is deliberately left alone. It is a browser over captured rows — "how much water did we capture in April" — and staff need the historical rows visible, so it keeps totalling everything, the same asymmetry NOT_OUTSTANDING already has. stats() now separates the two questions it was mixing: movements, ledgerCustomers, crossLineCustomers and the date range stay unfloored inventory; everything under byCurrency/byDomain is a balance and is floored. BillingService had no tests. Adds 13 covering the floor's failure modes — it fails silently, so MIN-vs-MAX, `>` vs `>=`, the NULL branch for customers with no opening balance, and the join/predicate alias pairing are each pinned, plus the 501 arithmetic as a regression. Verified through the real service against the live ledger: balances() and statement() both return -14,065.29 for 501, matching the portal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |