From ccdfdd10bfb7c6389524e97c7c041ce2af1671a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:31:33 +0000 Subject: [PATCH] fix(frontend): Correct running balance calculation in transactions table Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com> --- frontend/src/components/TransactionsTable.tsx | 25 ++- .../unit/test_running_balance_calculation.py | 192 ++++++++++++++++++ 2 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 tests/unit/test_running_balance_calculation.py diff --git a/frontend/src/components/TransactionsTable.tsx b/frontend/src/components/TransactionsTable.tsx index fb1242d..4800725 100644 --- a/frontend/src/components/TransactionsTable.tsx +++ b/frontend/src/components/TransactionsTable.tsx @@ -192,9 +192,9 @@ export default function TransactionsTable() { const runningBalances: { [key: string]: number } = {}; const accountBalanceMap = new Map(); - // Create a map of account current balances + // Create a map of account current balances - use interimAvailable as the most current balances.forEach((balance) => { - if (balance.balance_type === "expected") { + if (balance.balance_type === "interimAvailable") { accountBalanceMap.set(balance.account_id, balance.balance_amount); } }); @@ -211,20 +211,25 @@ export default function TransactionsTable() { // Calculate running balance for each account transactionsByAccount.forEach((accountTransactions, accountId) => { const currentBalance = accountBalanceMap.get(accountId) || 0; - let runningBalance = currentBalance; - // Sort transactions by date (newest first) to work backwards + // Sort transactions by date (oldest first) for forward calculation const sortedTransactions = [...accountTransactions].sort( (a, b) => - new Date(b.transaction_date).getTime() - - new Date(a.transaction_date).getTime(), + new Date(a.transaction_date).getTime() - + new Date(b.transaction_date).getTime(), ); - // Calculate running balance by working backwards from current balance + // Calculate the starting balance by working backwards from current balance + let startingBalance = currentBalance; + for (let i = sortedTransactions.length - 1; i >= 0; i--) { + startingBalance -= sortedTransactions[i].transaction_value; + } + + // Now calculate running balances going forward chronologically + let runningBalance = startingBalance; sortedTransactions.forEach((txn) => { - runningBalances[`${txn.account_id}-${txn.transaction_id}`] = - runningBalance; - runningBalance -= txn.transaction_value; + runningBalance += txn.transaction_value; + runningBalances[`${txn.account_id}-${txn.transaction_id}`] = runningBalance; }); }); diff --git a/tests/unit/test_running_balance_calculation.py b/tests/unit/test_running_balance_calculation.py new file mode 100644 index 0000000..57f9953 --- /dev/null +++ b/tests/unit/test_running_balance_calculation.py @@ -0,0 +1,192 @@ +"""Tests for running balance calculation logic in frontend. + +This test validates the running balance calculation algorithm to ensure +it correctly computes account balances after each transaction. +""" + +import pytest + + +class TestRunningBalanceCalculation: + """Test running balance calculation logic.""" + + def test_running_balance_calculation_logic(self): + """Test the logic behind running balance calculation. + + This test validates the algorithm used in the frontend TransactionsTable + component to ensure running balances are calculated correctly. + """ + # Sample test data similar to what the frontend receives + transactions = [ + { + "transaction_id": "txn-1", + "account_id": "account-001", + "transaction_value": -100.00, + "transaction_date": "2025-01-01T10:00:00Z", + "description": "Initial expense" + }, + { + "transaction_id": "txn-2", + "account_id": "account-001", + "transaction_value": 500.00, + "transaction_date": "2025-01-02T10:00:00Z", + "description": "Salary" + }, + { + "transaction_id": "txn-3", + "account_id": "account-001", + "transaction_value": -50.00, + "transaction_date": "2025-01-03T10:00:00Z", + "description": "Shopping" + } + ] + + balances = [ + { + "account_id": "account-001", + "balance_amount": 350.00, # Final balance after all transactions + "balance_type": "interimAvailable" + } + ] + + # Implement the corrected algorithm from the frontend + running_balances = self._calculate_running_balances(transactions, balances) + + # Expected running balances: + # - After txn-1 (-100): balance should be -100.00 + # - After txn-2 (+500): balance should be 400.00 (previous + 500) + # - After txn-3 (-50): balance should be 350.00 (previous - 50) + + assert running_balances["account-001-txn-1"] == -100.00, "First transaction running balance incorrect" + assert running_balances["account-001-txn-2"] == 400.00, "Second transaction running balance incorrect" + assert running_balances["account-001-txn-3"] == 350.00, "Third transaction running balance incorrect" + + # Final balance should match current account balance + final_calculated_balance = running_balances["account-001-txn-3"] + assert final_calculated_balance == balances[0]["balance_amount"], "Final balance doesn't match current account balance" + + def test_running_balance_multiple_accounts(self): + """Test running balance calculation with multiple accounts.""" + transactions = [ + { + "transaction_id": "txn-1", + "account_id": "account-001", + "transaction_value": -50.00, + "transaction_date": "2025-01-01T10:00:00Z", + "description": "Account 1 expense" + }, + { + "transaction_id": "txn-2", + "account_id": "account-002", + "transaction_value": -25.00, + "transaction_date": "2025-01-01T11:00:00Z", + "description": "Account 2 expense" + }, + { + "transaction_id": "txn-3", + "account_id": "account-001", + "transaction_value": 100.00, + "transaction_date": "2025-01-02T10:00:00Z", + "description": "Account 1 income" + } + ] + + balances = [ + { + "account_id": "account-001", + "balance_amount": 50.00, + "balance_type": "interimAvailable" + }, + { + "account_id": "account-002", + "balance_amount": 75.00, + "balance_type": "interimAvailable" + } + ] + + running_balances = self._calculate_running_balances(transactions, balances) + + # Account 1: starts at 0, -50 = -50, +100 = 50 + assert running_balances["account-001-txn-1"] == -50.00 + assert running_balances["account-001-txn-3"] == 50.00 + + # Account 2: starts at 100, -25 = 75 + assert running_balances["account-002-txn-2"] == 75.00 + + def test_running_balance_empty_transactions(self): + """Test running balance calculation with no transactions.""" + transactions = [] + balances = [ + { + "account_id": "account-001", + "balance_amount": 100.00, + "balance_type": "interimAvailable" + } + ] + + running_balances = self._calculate_running_balances(transactions, balances) + assert running_balances == {} + + def test_running_balance_no_balances(self): + """Test running balance calculation with no balance data.""" + transactions = [ + { + "transaction_id": "txn-1", + "account_id": "account-001", + "transaction_value": -50.00, + "transaction_date": "2025-01-01T10:00:00Z", + "description": "Expense" + } + ] + balances = [] + + running_balances = self._calculate_running_balances(transactions, balances) + + # When no balance data available, current balance is 0 + # Working backwards: starting_balance = 0 - (-50) = 50 + # Going forward: running_balance = 50 + (-50) = 0 + assert running_balances["account-001-txn-1"] == 0.00 + + def _calculate_running_balances(self, transactions, balances): + """ + Implementation of the corrected running balance calculation algorithm. + This mirrors the logic implemented in the frontend TransactionsTable component. + """ + running_balances = {} + account_balance_map = {} + + # Create a map of account current balances - use interimAvailable as the most current + for balance in balances: + if balance["balance_type"] == "interimAvailable": + account_balance_map[balance["account_id"]] = balance["balance_amount"] + + # Group transactions by account + transactions_by_account = {} + for txn in transactions: + account_id = txn["account_id"] + if account_id not in transactions_by_account: + transactions_by_account[account_id] = [] + transactions_by_account[account_id].append(txn) + + # Calculate running balance for each account + for account_id, account_transactions in transactions_by_account.items(): + current_balance = account_balance_map.get(account_id, 0) + + # Sort transactions by date (oldest first) for forward calculation + sorted_transactions = sorted( + account_transactions, + key=lambda x: x["transaction_date"] + ) + + # Calculate the starting balance by working backwards from current balance + starting_balance = current_balance + for txn in reversed(sorted_transactions): + starting_balance -= txn["transaction_value"] + + # Now calculate running balances going forward chronologically + running_balance = starting_balance + for txn in sorted_transactions: + running_balance += txn["transaction_value"] + running_balances[f"{txn['account_id']}-{txn['transaction_id']}"] = running_balance + + return running_balances \ No newline at end of file