feat(bank): multi-bank chequera — required bankAccountId, per-account scoping
The office keeps more than one operating account (Utilities banks in MXN, Seguros in USD), but bank_transactions was a single implicit MXN register by design. Adds Bank/BankAccount and makes every read and write in the module scoped to exactly one account. Schema: - Bank / BankAccount. Currency is fixed per account and BankTransaction has no currency column of its own — a movement inherits its account's, the way a real bank account doesn't mix currencies. - BankTransaction.bankAccountId, required. A movement with no known account isn't reconcilable against a statement. - @@index([bankAccountId, transactionDate]): every read now filters by account and orders/groups by date. Migration: - backfill_bank_accounts.py seeds Scotiabank + "Utilities — Scotiabank (MXN)" and backfills all 22,669 existing rows onto it, then promotes the column to NOT NULL and attaches the FK. Standalone because prisma db push cannot add a required column to a populated table. Idempotent; re-running once a second account exists does not re-point rows. - run_all.py runs it (both modes) before transform_bank.py, which now resolves the account by label and fails fast if it is missing. API: - ?bankAccountId= required on list/stats/facets/summary — not optional with an "all accounts" default, since summing an MXN and a USD register repeats the currency-collapsing mistake the billing module exists to prevent. Missing is 400, unknown is 404. - facets() had no account clause at all and summary() has two raw-SQL rollups; all three are now parameterised. Scoping only one of summary's queries would leave the year list and its drill-down describing different books. - New bank/accounts + bank/banks sub-resource under a MANAGER bank:manage-accounts ability. currency is absent from the update DTO: booked movements are denominated in it, so editing would re-denominate history. Capture into a closed account is rejected. Web: - /banco gains an account picker (remembered per browser) and reads every figure in the selected account's currency; the "single currency (MXN)" doc-comment and the hardcoded MXN formatting are gone. - New /banco/cuentas for banks and accounts. Accounts are closed, never deleted — the FK is required, so deleting one would destroy its register. - /inicio's chequera card names the account it is reading instead of implying a single register. Verified against dev + browser: a second USD account showed full read/write isolation from the MXN register, whose totals were unchanged (22,669 movements, net 1,014,266.97). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -381,9 +381,18 @@ for what's actually next.
|
||||
insurance/servicios/fideicomiso split the migration comment implied. A
|
||||
classifier would invent data, so `categoryId` stays null and the module does
|
||||
not filter on it. Register is browsable by date/payee/amount/cheque instead.
|
||||
(c) **Single currency (MXN).** `bank_transactions` has no currency column and
|
||||
every `amountInWords` is spelled out in PESOS — so, unlike the customer
|
||||
ledger, everything here is one currency and not split per-currency.
|
||||
(c) ~~**Single currency (MXN).**~~ **SUPERSEDED 2026-07-27 by the multi-bank
|
||||
chequera** (step 11, `docs/RECEIPT_CAPTURE_SPEC.md` §3). The office keeps
|
||||
more than one register, so `bank_transactions` now carries a **required**
|
||||
`bankAccountId` and every read in the module is scoped to exactly one
|
||||
`BankAccount`, whose `currency` the movements inherit — there is still no
|
||||
currency column on the movement itself, because a real bank account doesn't
|
||||
mix currencies. All 22,669 migrated rows are the Utilities/Scotiabank MXN
|
||||
account (backfilled by `migration/backfill_bank_accounts.py`, which
|
||||
`run_all.py` runs before `transform_bank.py`), which is why every
|
||||
`amountInWords` is still spelled out in PESOS. There is deliberately no
|
||||
"all accounts" option: summing an MXN and a USD register would repeat the
|
||||
currency-collapsing mistake the billing module warns against.
|
||||
(d) **The "acumulado" is net movement since the register opened, not a bank
|
||||
balance** — SCOTHIA carries no opening balance (its `ban` table holds only the
|
||||
bank's name), so the running total starts at 0 in 2013. Labelled as such in
|
||||
@@ -391,9 +400,18 @@ for what's actually next.
|
||||
(e) Sign convention (from `transform_bank.py`): positive = ingreso,
|
||||
negative = egreso, exactly zero = a cancelled/void cheque (787 of 791 say
|
||||
CANCELADO/VOID) — voids are excluded from both the income and expense sides.
|
||||
(f) **Multi-account since 2026-07-27.** `/banco` opens on an account picker
|
||||
(the last account is remembered per browser) and reads every figure in that
|
||||
account's currency; `/banco/cuentas` manages banks and accounts under a new
|
||||
MANAGER `bank:manage-accounts` ability. Accounts are never deleted — the
|
||||
`bankAccountId` FK is required, so a used account can only be *closed*
|
||||
(`active: false`), which hides it from new captures but keeps its history
|
||||
readable. An account's currency is immutable after creation, since its
|
||||
booked movements are denominated in it.
|
||||
- Full pipeline reproducible in one command: `run_all.py --env <env>` runs customers →
|
||||
properties → policies → transactions → prune → bank → blobs in order (all idempotent);
|
||||
add `--stage` to re-extract from the Access files first. Verified end-to-end against dev.
|
||||
properties → policies → transactions → prune → bank accounts → bank → blobs in order
|
||||
(all idempotent); add `--stage` to re-extract from the Access files first. Verified
|
||||
end-to-end against dev.
|
||||
|
||||
5. **Infra** — **DONE.** Dev MySQL deployed to the cubex Swarm via the Portainer API as stack
|
||||
`jorgecuadros-dev-db` (MySQL 8.4, `192.168.4.212:3307`, node `cubex` labeled
|
||||
|
||||
Reference in New Issue
Block a user