feat(cash): mark how a transaction was paid, exclude cash from reconciliation
ci / lint-test (push) Successful in 41s
ci / lint-test (push) Successful in 41s
getPendingReconciliations treated every unreconciled manual transaction as awaiting a matching statement row. Cash never appears on a statement, so a cash entry sat in the queue indefinitely being offered matches within 3 days and 1% on amount - and accepting one is silently destructive: reconciled manual rows are filtered out of every query, so the cash spend disappears while the card transaction it matched claims to be that same spend. Migration 0016 adds transactions.payment_method (card | cash | bank_transfer | other, NULL = unknown) with a CHECK constraint and a partial index. The notCash() fragment excludes cash from both halves of the reconciliation query - the pending list and the candidate match subquery, which aliases the manual row as m. Only cash is excluded. Bank transfers do appear on a statement now that transaction accounts are imported, and NULL means unknown, so both stay candidates and every pre-existing row behaves exactly as before. ATM withdrawals deliberately stay categorised as spend rather than transfers. Treating them as transfers is only correct if every cash purchase is logged; with partial logging it silently deletes the unlogged remainder from spend.
This commit is contained in:
@@ -81,7 +81,24 @@ docker exec postgres-personal psql -U personal -d personal < prisma/migrations/<
|
||||
### Key Tables
|
||||
|
||||
- `statements` — one row per billing period per bank account
|
||||
- `transactions` — line items; `statement_id` is nullable (NULL = manual entry); `reconciled_with_id` links a manual tx to its matched statement tx
|
||||
- `transactions` — line items; `statement_id` is nullable (NULL = manual entry); `reconciled_with_id` links a manual tx to its matched statement tx; `payment_method` (migration 0016) is `card | cash | bank_transfer | other`, NULL = unknown
|
||||
|
||||
### Cash and reconciliation
|
||||
|
||||
`payment_method = 'cash'` excludes a transaction from reconciliation via the
|
||||
`notCash()` fragment in `queries.ts`. Cash never appears on a statement, so
|
||||
without it a cash entry sits in the pending queue forever being offered matches
|
||||
within 3 days and 1% on amount — and accepting one is silently destructive:
|
||||
reconciled manual rows are filtered out of every query, so the cash spend
|
||||
disappears while the card transaction it matched claims to be that same spend.
|
||||
|
||||
Only cash is excluded. Bank transfers *do* appear on a statement now that
|
||||
transaction accounts are imported, and NULL means unknown — both stay
|
||||
candidates, preserving the behaviour of every pre-existing row.
|
||||
|
||||
ATM withdrawals stay categorised as spend rather than `transfers`. Treating them
|
||||
as transfers only works if every cash purchase is logged; with partial logging
|
||||
it silently deletes the unlogged remainder from spend totals.
|
||||
- `transaction_overrides` — user corrections to AI-extracted data (category, merchant, notes)
|
||||
- `transaction_splits` — shared expense tracking (participant, share_percent, settled)
|
||||
- `split_payments` — recorded cash settlements between participants
|
||||
|
||||
Reference in New Issue
Block a user