This commit is contained in:
+37
-30
@@ -1,63 +1,70 @@
|
||||
import type { Expense, Participant, ParticipantId, Settlement } from '../types'
|
||||
import { computeSplit } from './split'
|
||||
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> = {}
|
||||
): { netBalances: Record<ParticipantId, number>; settlements: Settlement[] } {
|
||||
const netBalances: Record<ParticipantId, number> = {};
|
||||
for (const participant of participants) {
|
||||
netBalances[participant.id] = 0
|
||||
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)
|
||||
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 id = Number(participantId);
|
||||
netBalances[id] = (netBalances[id] ?? 0) - share;
|
||||
}
|
||||
}
|
||||
|
||||
const settlements = simplifyDebts(netBalances)
|
||||
const settlements = simplifyDebts(netBalances);
|
||||
|
||||
return { netBalances, settlements }
|
||||
return { netBalances, settlements };
|
||||
}
|
||||
|
||||
function simplifyDebts(netBalances: Record<ParticipantId, number>): Settlement[] {
|
||||
const creditors: { id: ParticipantId, amount: number }[] = []
|
||||
const debtors: { id: ParticipantId, amount: number }[] = []
|
||||
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 })
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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 })
|
||||
settlements.push({
|
||||
fromId: debtor.id,
|
||||
toId: creditor.id,
|
||||
amountCents: amount,
|
||||
});
|
||||
}
|
||||
|
||||
creditor.amount -= amount
|
||||
debtor.amount -= amount
|
||||
creditor.amount -= amount;
|
||||
debtor.amount -= amount;
|
||||
|
||||
if (creditor.amount === 0) ci++
|
||||
if (debtor.amount === 0) di++
|
||||
if (creditor.amount === 0) ci++;
|
||||
if (debtor.amount === 0) di++;
|
||||
}
|
||||
|
||||
return settlements
|
||||
return settlements;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user