refactor: extract shared backward-reconstruction logic

Extract the month-by-month backward-walk algorithm (used for both
team-level cash balance and player-level debt history) into a single
shared private helper `reconstructBackward()`. This eliminates code
duplication while preserving behavior:

- Team-level balanceHistory: maps movements through signedFlowAmount(),
  rounds each point, calls the shared helper
- Player-level balance history: maps transactions using type.id rule,
  skips rounding (only rounds at final merge to avoid compounding
  errors), calls the shared helper

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-03 10:58:48 +02:00
parent 1b6ce57fbf
commit d8f13d674d

View File

@@ -299,26 +299,16 @@ export class TeamsService {
// reconstruct earlier month-end balances — this guarantees the most // reconstruct earlier month-end balances — this guarantees the most
// recent point always equals team.balance by construction, regardless // recent point always equals team.balance by construction, regardless
// of undocumented history. // of undocumented history.
const descendingMovements = [...movements].sort((a, b) => const signedMovements = movements.map((m) => ({
a.date > b.date ? -1 : a.date < b.date ? 1 : 0, date: m.date,
); amount: this.signedFlowAmount(m),
}));
const currentBalance = Number(team.balance); const currentBalance = Number(team.balance);
let futureSum = 0; const rawBalances = this.reconstructBackward(months, currentBalance, signedMovements);
let movementIndex = 0; const balanceHistory = months.map((month, index) => ({
const balanceHistory = [...months] month,
.reverse() balance: this.round(rawBalances[index]),
.map((month) => { }));
while (
movementIndex < descendingMovements.length &&
descendingMovements[movementIndex].date.slice(0, 7) > month
) {
futureSum += this.signedFlowAmount(descendingMovements[movementIndex]);
movementIndex++;
}
return { month, balance: this.round(currentBalance - futureSum) };
})
.reverse();
const monthlyFlow = months.map((month) => { const monthlyFlow = months.map((month) => {
const monthMovements = movements.filter((m) => m.date.slice(0, 7) === month); const monthMovements = movements.filter((m) => m.date.slice(0, 7) === month);
@@ -354,6 +344,28 @@ export class TeamsService {
return movement.type === 'expense' ? -movement.amount : movement.amount; return movement.type === 'expense' ? -movement.amount : movement.amount;
} }
private reconstructBackward(
months: string[],
currentValue: number,
signedMovements: { date: string; amount: number }[],
): number[] {
const descending = [...signedMovements].sort((a, b) =>
a.date > b.date ? -1 : a.date < b.date ? 1 : 0,
);
let futureSum = 0;
let index = 0;
return [...months]
.reverse()
.map((month) => {
while (index < descending.length && descending[index].date.slice(0, 7) > month) {
futureSum += descending[index].amount;
index++;
}
return currentValue - futureSum;
})
.reverse();
}
private reconstructOutstandingHistory(months: string[], players: Player[]): number[] { private reconstructOutstandingHistory(months: string[], players: Player[]): number[] {
const activePlayers = players.filter((p) => p.active); const activePlayers = players.filter((p) => p.active);
const totals = months.map(() => 0); const totals = months.map(() => 0);
@@ -380,28 +392,12 @@ export class TeamsService {
currentBalance: number, currentBalance: number,
transactions: Transaction[], transactions: Transaction[],
): number[] { ): number[] {
const descendingMovements = transactions const signedMovements = transactions.map((t) => ({
.map((t) => ({
date: t.date, date: t.date,
amount: t.type && t.type.id > 10 ? -Number(t.amount) : Number(t.amount), amount: t.type && t.type.id > 10 ? -Number(t.amount) : Number(t.amount),
})) }));
.sort((a, b) => (a.date > b.date ? -1 : a.date < b.date ? 1 : 0));
let futureSum = 0; return this.reconstructBackward(months, currentBalance, signedMovements);
let movementIndex = 0;
return [...months]
.reverse()
.map((month) => {
while (
movementIndex < descendingMovements.length &&
descendingMovements[movementIndex].date.slice(0, 7) > month
) {
futureSum += descendingMovements[movementIndex].amount;
movementIndex++;
}
return currentBalance - futureSum;
})
.reverse();
} }
private round(value: number): number { private round(value: number): number {