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:
@@ -0,0 +1,35 @@
|
||||
-- How a transaction was paid for, so cash can be told apart from everything else.
|
||||
--
|
||||
-- The problem this solves: getPendingReconciliations treats every unreconciled
|
||||
-- manual transaction as awaiting a matching statement row. A cash purchase never
|
||||
-- appears on a statement, so it sits in the queue forever and is offered matches
|
||||
-- within 3 days and 1% on amount. Accepting one is silently destructive --
|
||||
-- reconciled manual rows are excluded from every query, so the cash spend
|
||||
-- disappears while the card transaction it matched claims to be that same spend.
|
||||
--
|
||||
-- NULL means unknown, which is treated as reconcilable -- the existing behaviour
|
||||
-- for every row already in the table.
|
||||
--
|
||||
-- Only 'cash' is excluded from reconciliation. A bank transfer DOES appear on a
|
||||
-- statement now that transaction accounts are being imported, so it stays a
|
||||
-- reconciliation candidate.
|
||||
|
||||
ALTER TABLE transactions
|
||||
ADD COLUMN IF NOT EXISTS payment_method TEXT;
|
||||
|
||||
ALTER TABLE transactions DROP CONSTRAINT IF EXISTS transactions_payment_method_check;
|
||||
ALTER TABLE transactions ADD CONSTRAINT transactions_payment_method_check
|
||||
CHECK (payment_method IS NULL
|
||||
OR payment_method IN ('card', 'cash', 'bank_transfer', 'other'));
|
||||
|
||||
-- Partial index: the reconciliation query filters on this, and cash is expected
|
||||
-- to stay a small minority of rows.
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_payment_method
|
||||
ON transactions (payment_method) WHERE payment_method IS NOT NULL;
|
||||
|
||||
-- Backfill the one row that is unambiguously cash. Statement-linked rows are
|
||||
-- left NULL: they came from a statement, so by definition they are not cash.
|
||||
UPDATE transactions
|
||||
SET payment_method = 'cash'
|
||||
WHERE statement_id IS NULL
|
||||
AND description ILIKE '%cash transaction%';
|
||||
@@ -175,6 +175,7 @@ model transactions {
|
||||
row_index Int?
|
||||
merchant_normalized String?
|
||||
amount_aud Decimal? @db.Decimal(12, 2)
|
||||
payment_method String? // card | cash | bank_transfer | other; NULL = unknown (migration 0016)
|
||||
owner_id Int?
|
||||
reconciled_with_id Int?
|
||||
principal_amount Decimal? @db.Decimal(12, 2)
|
||||
|
||||
Reference in New Issue
Block a user