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:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user