-- Settlement scope: which tab a payment settles. -- -- `split_payments` has carried from/to/amount/date since it was written and -- nothing else. That is the whole reason a per-trip balance has never been -- computable — `getTripAnalytics` says so in a comment where the figure should -- be: "split_payments carries no trip attribution, so a payment cannot be -- assigned to a trip. Settlement is a property of the whole relationship." -- -- It is also the reason the Shared page silently drops payments the moment a -- tag filter is applied (`getParticipantBalances`): with one global payments -- pool there is no honest way to show a filtered balance, so it showed gross -- splits under the same label instead. A tag is a view; a scope is a ledger. -- -- The scope is a *trip*, not a new `settlement_contexts` table. `trips` already -- has owner_id, dates and an archived flag, and `transaction_overrides.trip_id` -- already decides which transactions belong to it. A second grouping beside it -- would be two unsynchronised scopes over the same rows — a trip could hold a -- mix of contexts and a context could span trips, with no invariant saying -- which one governs. -- -- NULL means the ongoing household tab. That tab never closes, which is why -- this is nullable rather than defaulted to some "general" row: absence is the -- honest representation of "not attached to a trip", and it keeps every -- existing payment correct without a backfill. ALTER TABLE split_payments ADD COLUMN IF NOT EXISTS trip_id integer REFERENCES trips(id) ON DELETE SET NULL; COMMENT ON COLUMN split_payments.trip_id IS 'The trip this payment settles. NULL = the ongoing household tab.'; CREATE INDEX IF NOT EXISTS idx_split_payments_trip ON split_payments (trip_id) WHERE trip_id IS NOT NULL; -- `settled` answers a different question and the two must not be collapsed: -- trip_id is *which tab*, settled is *is this obligation still live*. A -- pre-2026 historical split is settled with no tab; a Europe split becomes -- settled when Europe's payment lands; a household split stays unsettled and -- open indefinitely. -- -- Nothing writes `settled` today. The comment in queries.ts claims -- /api/splits/settle does — that route does not exist, and the column is false -- on all 1,279 rows, which is why every trip has always reported 100% -- unsettled including trips paid in full. COMMENT ON COLUMN transaction_splits.settled IS 'Obligation discharged. Excluded from owed figures; still counted in spend analytics.';