[
https://issues.apache.org/jira/browse/FINERACT-2852?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Adam Saghy updated FINERACT-2852:
---------------------------------
Fix Version/s: 1.17.0
(was: 1.16.0)
> Savings amount holds can cause incorrect scheduled interest calculation
> -----------------------------------------------------------------------
>
> Key: FINERACT-2852
> URL: https://issues.apache.org/jira/browse/FINERACT-2852
> Project: Apache Fineract
> Issue Type: Bug
> Components: Savings
> Affects Versions: 1.14.0, 1.15.0
> Reporter: Ralph Hopman
> Assignee: Ralph Hopman
> Priority: Major
> Fix For: 1.17.0
>
>
> h2. Description
> Savings amount holds can incorrectly affect the balance used by the
> {*}scheduled savings interest calculation{*}.
> An amount hold should reduce the available balance but should not change the
> account's ledger/book balance. However, the savings transaction
> {{runningBalance}} currently includes holds and releases.
> The scheduled interest calculation operates on {{SavingsAccountData}} /
> {{SavingsAccountTransactionData}} DTOs and recalculates these running
> balances. In certain scenarios it then uses the resulting {{runningBalance}}
> directly as the end-of-day balance for interest calculation.
> As a result, when a normal deposit or withdrawal occurs while an amount is on
> hold, the held amount can incorrectly affect the principal on which interest
> is calculated.
> h2. Example
> Consider the following transaction history:
> {code:java}
> 01 Jan: Deposit 1,000
> 10 Jan: Hold 200
> 15 Jan: Withdrawal 100
> 20 Jan: Release 200
> {code}
> The correct ledger/book balances are:
> {code:java}
> 01 Jan: 1000
> 10 Jan: 1000 (available balance = 800)
> 15 Jan: 900 (available balance = 700)
> 20 Jan: 900
> {code}
> However, the transaction running balances are recalculated approximately as:
> {code:java}
> Deposit +1000 runningBalance = 1000
> Amount Hold -200 runningBalance = 800
> Withdrawal -100 runningBalance = 700
> Amount Release +200 runningBalance = 900
> {code}
> The {{SavingsAccountTransactionData.toEndOfDayBalance()}} implementation uses
> {{runningBalance}} directly for ordinary withdrawals:
> {code:java}
> if (isWithdrawal()) {
> endOfDayBalance = Money.of(currency, this.runningBalance);
> }
> {code}
> Therefore, after the withdrawal on 15 Jan the scheduled interest calculation
> can use:
> {code:java}
> endOfDayBalance = 700
> {code}
> instead of the correct ledger balance:
> {code:java}
> endOfDayBalance = 900
> {code}
> The held 200 is therefore incorrectly excluded from the balance used for
> interest calculation.
> h2. Why the Hold Transaction Alone Does Not Expose the Problem
> The hold transaction itself is not treated as a deposit or withdrawal when
> converting transactions to end-of-day balances. Therefore, a hold without any
> subsequent ledger-changing transaction may leave the interest-bearing balance
> unchanged.
> The incorrect balance becomes observable when a later transaction, such as a
> withdrawal or deposit, uses the hold-adjusted {{runningBalance}} as the new
> end-of-day balance.
> This also means that releasing the hold does not necessarily repair the
> interest balance, because {{AMOUNT_RELEASE}} itself is correctly not
> considered a ledger credit.
> h2. Scheduled Interest Path
> The scheduled interest job uses the DTO-based calculation path:
> {code:java}
> SavingsSchedularInterestPoster
> -> SavingsAccountWritePlatformService.postInterest(SavingsAccountData, ...)
> -> SavingsAccountInterestPostingServiceImpl
> -> recalculateDailyBalances(...)
> -> PostingPeriod.createFromDTO(...)
> -> SavingsAccountTransactionData.toEndOfDayBalance(...)
> -> EndOfDayBalance
> -> interest calculation
> {code}
> During {{{}recalculateDailyBalances(){}}}, holds and releases explicitly
> change the DTO running balance:
> {code:java}
> if (transaction.isCredit() || transaction.isAmountRelease()) {
> transactionAmount = transactionAmount.plus(transaction.getAmount());
> } else if (transaction.isDebit() || transaction.isAmountOnHold()) {
> transactionAmount = transactionAmount.minus(transaction.getAmount());
> }
> runningBalance = runningBalance.plus(transactionAmount);
> transaction.updateRunningBalance(runningBalance);
> {code}
> The resulting value can subsequently be used as the interest-bearing
> end-of-day balance.
> h2. Inconsistent Behavior Between Interest Calculation Paths
> The entity-based interest calculation reconstructs balances from the previous
> ledger balance and transaction amount.
> Conceptually:
> {code:java}
> Deposit -> openingBalance + amount
> Hold -> no balance change
> Withdrawal -> openingBalance - amount
> Release -> no balance change
> {code}
> For the example above, this produces the correct ledger balance of 900 after
> the withdrawal.
> The DTO-based scheduled interest calculation can instead use the
> hold-adjusted {{runningBalance}} and produce 700.
> This creates a risk that manual/entity-based interest posting and
> scheduled/DTO-based interest posting produce different results for the same
> account history.
> h2. Overdraft Accounts
> The DTO-based interest service also classifies transactions as
> positive-balance or overdraft transactions based directly on
> {{{}transaction.getRunningBalance(){}}}.
> A hold can therefore make the derived running balance negative even while the
> ledger balance remains positive.
> Example:
> {code:java}
> Ledger balance before hold: 100
> Hold: 80
> Available balance: 20
> Withdrawal: 30
> Ledger balance after withdrawal: 70
> Available balance: -10
> Derived runningBalance: -10
> {code}
> This may incorrectly classify a positive ledger balance as an overdraft
> balance for interest calculation.
> h2. Expected Behavior
> Savings interest must be calculated using the ledger/book balance. Amount
> holds and releases affect funds availability but must not change the balance
> used for interest calculation.
> For the example above, interest after 15 Jan should be calculated on 900, not
> 700.
> h2. Acceptance Criteria
> * {{AMOUNT_HOLD}} and {{AMOUNT_RELEASE}} do not affect the ledger balance
> used for interest calculation.
> * A withdrawal made while a hold is active is applied to the ledger balance,
> not to the available balance.
> * A deposit made while a hold is active is applied to the ledger balance,
> not to the available balance.
> * Releasing a hold does not change the interest-bearing balance.
> * Scheduled and manual interest posting produce identical results for the
> same transaction history.
> * Positive ledger balances are not classified as overdraft balances solely
> because an active hold makes the available balance negative.
> * Regression tests cover at least:
> ** deposit -> hold -> interest posting
> ** deposit -> hold -> withdrawal -> interest posting
> ** deposit -> hold -> deposit -> interest posting
> ** deposit -> hold -> withdrawal -> release -> interest posting
> ** a hold which causes available balance, but not ledger balance, to become
> negative
> * Tests verify both end-of-day balances and the final posted interest amount.
> h2. Suggested Regression Scenario
> A deterministic regression test could use:
> {code:java}
> Nominal annual interest rate: 36.5%
> Interest calculation: daily balance
> Days in year: 365
> Interest posting: monthly
> 01 Jan: Deposit 1000
> 10 Jan: Hold 200
> 15 Jan: Withdrawal 100
> 20 Jan: Release 200
> {code}
> With a 36.5% annual rate, the daily rate is 0.1%, which makes differences in
> the expected interest easy to verify.
> The ledger balance used for interest should be:
> {code:java}
> 01-14 Jan: 1000, except for the exact account-activation/date semantics of
> the test
> 15 Jan onward: 900
> {code}
> The hold and release should not introduce an 800 or 700 ledger balance into
> the interest calculation.
> h2. Related Issue
> Related to FINERACT-2851, describing that savings {{AMOUNT_HOLD}} and
> {{AMOUNT_RELEASE}} transactions incorrectly affect transaction
> {{{}runningBalance{}}}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)