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
+11 -5
View File
@@ -277,7 +277,7 @@ export default function TripDetailPage({ params }: { params: Promise<{ id: strin
<table className="w-full text-sm">
<thead>
<tr className="border-b border-zinc-800">
{["Person", "Total Owed", "Settled", "Unsettled"].map((h) => (
{["Person", "Share of this trip"].map((h) => (
<th
key={h}
className={`px-5 py-2.5 text-xs text-zinc-500 font-medium ${h === "Person" ? "text-left" : "text-right"}`}
@@ -292,14 +292,20 @@ export default function TripDetailPage({ params }: { params: Promise<{ id: strin
<tr key={p.participant_id} className="border-b border-zinc-800/50 last:border-0">
<td className="px-5 py-3 font-medium">{p.name}</td>
<td className="px-5 py-3 text-right tabular-nums font-mono">${Number(p.owed).toFixed(2)}</td>
<td className="px-5 py-3 text-right tabular-nums font-mono text-emerald-500">${Number(p.settled).toFixed(2)}</td>
<td className={`px-5 py-3 text-right tabular-nums font-mono ${p.unsettled > 0 ? "text-amber-400" : "text-zinc-600"}`}>
${Number(p.unsettled).toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
{/* Settled/unsettled was reported from transaction_splits.settled,
which nothing sets — so every trip showed 100% unsettled forever,
including ones already paid in full. Settlement is tracked across
the whole relationship, not per trip: payments carry no trip
attribution, so a per-trip figure cannot be computed. */}
<p className="px-5 py-2.5 text-xs text-zinc-500 border-t border-zinc-800">
Settlement is tracked across all shared expenses, not per trip
see <Link href="/shared" className="text-zinc-400 hover:text-zinc-200 underline">Shared</Link> for
what is actually owed.
</p>
</div>
)}