-- 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%';