feat(shared): replace settle buttons with payment ledger

- New split_payments table records actual payments between participants
- Balance = total split obligations - total payments (splits never marked settled)
- Record Payment modal per participant: direction toggle, amount pre-filled with balance, date, notes
- Payment history inline on each balance card with +/- display and delete
- Per-transaction Settle button removed; Action column removed from shared table
- Splits always show the true cost breakdown regardless of payment state
This commit is contained in:
2026-03-14 21:09:00 +11:00
parent 5206388958
commit 85e7801407
6 changed files with 383 additions and 94 deletions
+22 -12
View File
@@ -276,16 +276,17 @@ export interface ParticipantBalance {
export async function getParticipantBalances(ownerId: number) {
return queryRaw<ParticipantBalance>(`
SELECT p.id, p.name,
COALESCE(SUM(combined.signed_amount), 0)::numeric(12,2) as total_owed,
COALESCE(SUM(combined.unsettled_count), 0)::int as unsettled_count
COALESCE(SUM(splits.signed_amount), 0)::numeric(12,2)
- COALESCE(payments.net_paid, 0)::numeric(12,2) AS total_owed,
COALESCE(SUM(splits.split_count), 0)::int AS unsettled_count
FROM participants p
-- All split obligations (settled flag ignored — payments are the source of truth)
LEFT JOIN (
-- They owe me: their splits on transactions I own
SELECT ts.participant_id AS pid,
CASE WHEN ts.settled = false
THEN (CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN t.amount ELSE -t.amount END) * ts.share_percent / 100
ELSE 0 END AS signed_amount,
CASE WHEN ts.settled = false AND t.transaction_type IN ('debit', 'fee', 'interest') THEN 1 ELSE 0 END AS unsettled_count
(CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN t.amount ELSE -t.amount END) * ts.share_percent / 100 AS signed_amount,
1 AS split_count
FROM transaction_splits ts
JOIN transactions t ON t.id = ts.transaction_id
LEFT JOIN statements s ON s.id = t.statement_id
@@ -295,17 +296,26 @@ export async function getParticipantBalances(ownerId: number) {
-- I owe them: my splits on transactions they own
SELECT COALESCE(t.owner_id, s.owner_id) AS pid,
CASE WHEN ts.settled = false
THEN -(CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN t.amount ELSE -t.amount END) * ts.share_percent / 100
ELSE 0 END AS signed_amount,
0 AS unsettled_count
-((CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN t.amount ELSE -t.amount END) * ts.share_percent / 100) AS signed_amount,
0 AS split_count
FROM transaction_splits ts
JOIN transactions t ON t.id = ts.transaction_id
LEFT JOIN statements s ON s.id = t.statement_id
WHERE ts.participant_id = $1 AND COALESCE(t.owner_id, s.owner_id) != $1
) combined ON combined.pid = p.id
) splits ON splits.pid = p.id
-- Net payments: positive = they paid me, negative = I paid them
LEFT JOIN (
SELECT
CASE WHEN sp.from_participant_id != $1 THEN sp.from_participant_id ELSE sp.to_participant_id END AS pid,
SUM(CASE WHEN sp.to_participant_id = $1 THEN sp.amount ELSE -sp.amount END) AS net_paid
FROM split_payments sp
WHERE sp.from_participant_id = $1 OR sp.to_participant_id = $1
GROUP BY pid
) payments ON payments.pid = p.id
WHERE p.id != $1
GROUP BY p.id, p.name
GROUP BY p.id, p.name, payments.net_paid
ORDER BY p.name
`, [ownerId]);
}