Files
splitt-the-bill/shared/utils/balances.ts
T
anbraten cbe1b42f6e
tinyedge/deploy Deployed by TinyEdge
🐛 add env to useDb and apply prettier
2026-08-07 13:28:26 +02:00

71 lines
2.2 KiB
TypeScript

import type { Expense, Participant, ParticipantId, Settlement } from '../types';
import { computeSplit } from './split';
export function calculateBalances(
participants: Participant[],
expenses: Expense[],
): { netBalances: Record<ParticipantId, number>; settlements: Settlement[] } {
const netBalances: Record<ParticipantId, number> = {};
for (const participant of participants) {
netBalances[participant.id] = 0;
}
// A settlement payment (fromId pays toId) is folded in as a regular
// expense: payerId = fromId, participantIds = [toId]. The math is
// identical to a one-person split, so no separate handling is needed here.
for (const expense of expenses) {
netBalances[expense.payerId] =
(netBalances[expense.payerId] ?? 0) + expense.amountCents;
const shares = computeSplit(expense.amountCents, expense.participantIds);
for (const [participantId, share] of Object.entries(shares)) {
const id = Number(participantId);
netBalances[id] = (netBalances[id] ?? 0) - share;
}
}
const settlements = simplifyDebts(netBalances);
return { netBalances, settlements };
}
function simplifyDebts(
netBalances: Record<ParticipantId, number>,
): Settlement[] {
const creditors: { id: ParticipantId; amount: number }[] = [];
const debtors: { id: ParticipantId; amount: number }[] = [];
for (const [id, amount] of Object.entries(netBalances)) {
if (amount > 0) creditors.push({ id: Number(id), amount });
else if (amount < 0) debtors.push({ id: Number(id), amount: -amount });
}
creditors.sort((a, b) => b.amount - a.amount);
debtors.sort((a, b) => b.amount - a.amount);
const settlements: Settlement[] = [];
let ci = 0;
let di = 0;
while (ci < creditors.length && di < debtors.length) {
const creditor = creditors[ci]!;
const debtor = debtors[di]!;
const amount = Math.min(creditor.amount, debtor.amount);
if (amount > 0) {
settlements.push({
fromId: debtor.id,
toId: creditor.id,
amountCents: amount,
});
}
creditor.amount -= amount;
debtor.amount -= amount;
if (creditor.amount === 0) ci++;
if (debtor.amount === 0) di++;
}
return settlements;
}