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
// recent point always equals team.balance by construction, regardless
// of undocumented history.
const descendingMovements = [...movements].sort((a, b) =>
a.date > b.date ? -1 : a.date < b.date ? 1 : 0,
);
const signedMovements = movements.map((m) => ({
date: m.date,
amount: this.signedFlowAmount(m),
}));
const currentBalance = Number(team.balance);
let futureSum = 0;
let movementIndex = 0;
const balanceHistory = [...months]
.reverse()
.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 rawBalances = this.reconstructBackward(months, currentBalance, signedMovements);
const balanceHistory = months.map((month, index) => ({
month,
balance: this.round(rawBalances[index]),
}));
const monthlyFlow = months.map((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;
}
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[] {
const activePlayers = players.filter((p) => p.active);
const totals = months.map(() => 0);
@@ -380,28 +392,12 @@ export class TeamsService {
currentBalance: number,
transactions: Transaction[],
): number[] {
const descendingMovements = transactions
.map((t) => ({
const signedMovements = transactions.map((t) => ({
date: t.date,
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;
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();
return this.reconstructBackward(months, currentBalance, signedMovements);
}
private round(value: number): number {