Ralph Hopman created FINERACT-2852:
--------------------------------------

             Summary: 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.15.0, 1.14.0
            Reporter: Ralph Hopman
            Assignee: Ralph Hopman
             Fix For: 1.16.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)

Reply via email to