fix(trips): stop reporting a settlement breakdown that cannot be computed
ci / lint-test (push) Successful in 35s

The trip view showed Total Owed / Settled / Unsettled per participant, with the
last two derived from transaction_splits.settled. Nothing sets that flag - its
only writer was /api/splits/settle, which no UI calls - so it is false on all 673
splits and every trip reported 100% unsettled, including trips already paid in
full. Molina has paid $20,782.79 against $19,556.07 of splits and the Europe trip
still showed her entire share outstanding.

A correct per-trip figure is not computable either: split_payments records only
from, to, amount and date, so a payment cannot be attributed to a trip. The trip
view now shows each participant's share and points at Shared for what is actually
owed, which is where settlement genuinely lives.

Also removes /api/splits/settle. It was unreachable from the UI but live on its
URL, and a single call with participant_id would mark every one of that person's
splits settled - writing a flag nothing reads. Settlement will be reintroduced
against settlement contexts (docs/shared-expenses-design.md).

getParticipantBalances is deliberately untouched: it computes splits minus
payments, which is coherent. Excluding settled splits there while still
subtracting the payments that settled them would double-count.
This commit is contained in:
2026-07-26 16:13:20 +10:00
parent ba87ff86e7
commit 3f04cbd5e7
3 changed files with 22 additions and 56 deletions
-46
View File
@@ -1,46 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { queryRaw } from "@/lib/db";
import { getCurrentUser } from "@/lib/auth";
// A split may be settled by the transaction's effective owner or by the
// participant the split belongs to.
const SCOPE = `
AND EXISTS (
SELECT 1 FROM transactions t
LEFT JOIN statements s ON s.id = t.statement_id
WHERE t.id = transaction_splits.transaction_id
AND (COALESCE(t.owner_id, s.owner_id) = $2 OR transaction_splits.participant_id = $2)
)`;
export async function POST(req: NextRequest) {
const user = await getCurrentUser(req);
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
const body = await req.json();
const { participant_id, split_ids } = body as {
participant_id?: number;
split_ids?: number[];
};
if (participant_id) {
const rows = await queryRaw<{ id: number }>(
`UPDATE transaction_splits SET settled = true, settled_at = NOW()
WHERE participant_id = $1 AND settled = false ${SCOPE}
RETURNING id`,
[participant_id, user.id]
);
return NextResponse.json({ settled: rows.length });
}
if (split_ids?.length) {
const rows = await queryRaw<{ id: number }>(
`UPDATE transaction_splits SET settled = true, settled_at = NOW()
WHERE id = ANY($1::int[]) AND settled = false ${SCOPE}
RETURNING id`,
[split_ids, user.id]
);
return NextResponse.json({ settled: rows.length });
}
return NextResponse.json({ error: "participant_id or split_ids required" }, { status: 400 });
}