[
https://issues.apache.org/jira/browse/FINERACT-2797?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Josue Araujo updated FINERACT-2797:
-----------------------------------
Attachment: Screenshot 2026-08-31 at 7.36.40 p.m..png
Screenshot 2026-08-31 at 7.37.15 p.m..png
Screenshot 2026-08-31 at 7.40.09 p.m..png
> Force withdrawal balance validation fails on subsequent regular transactions
> ----------------------------------------------------------------------------
>
> Key: FINERACT-2797
> URL: https://issues.apache.org/jira/browse/FINERACT-2797
> Project: Apache Fineract
> Issue Type: Bug
> Reporter: Josue Araujo
> Priority: Major
> Attachments: Screenshot 2026-08-31 at 7.36.40 p.m..png, Screenshot
> 2026-08-31 at 7.37.15 p.m..png, Screenshot 2026-08-31 at 7.40.09 p.m..png
>
>
> _*Summary*_
> When "Force Withdrawal" (allow-force-withdrawal-on-savings-account global
> config, FINERACT-2471) is used to push a savings account balance below its
> minimum/zero threshold, any *subsequent* regular (non-forced) withdrawal on
> that same account throws InsufficientAccountBalanceException — even when the
> account's current balance is fully sufficient to cover the new withdrawal.
>
> _*Root cause*_
> validateAccountBalanceConstraints() replays the ENTIRE transaction history on
> every balance-affecting operation to recompute the running balance at each
> historical point (needed to correctly validate backdated transactions).
> isForceWithdrawalAllowed(isForceWithdrawal, runningBalance) is passed a
> single boolean scoped to the CURRENT call, not to the specific historical
> transaction being evaluated in the loop:
> private boolean isForceWithdrawalAllowed(final boolean isForceWithdrawal,
> final Money runningBalance) {
> if (!isForceWithdrawal || this.configurationDomainService == null)
> { return false; }
> ...
> }
> So when a later *regular* withdrawal is validated (isForceWithdrawal=false
> for that call), the loop re-encounters the earlier forced-withdrawal dip in
> the account's history, and — since the flag for the CURRENT call is false —
> throws, even though that historical dip was legitimately authorized at the
> time it happened. The "force" authorization is never persisted on the
> transaction itself, only used transiently at write time.
> * Steps to reproduce
> 1. Create a savings account/product: no interest, overdraft NOT allowed.
> 2. Global config: allow-force-withdrawal-on-savings-account=true,
> force-withdrawal-on-savings-account-limit=1000.
> 3. Deposit 10.00 (balance: 10.00)
> 4. Force withdrawal 15.00 (balance: -5.00) — succeeds as expected.
> 5. Deposit 20.00 (balance: 15.00)
> 6. Deposit 5.00 (balance: 20.00)
> 7. Regular withdrawal 3.00 (expected balance: 17.00)
> -> ACTUAL: throws InsufficientAccountBalanceException.
> _*Expected behavior*_
> Step 7 should succeed: the account's current balance (20.00) is more than
> sufficient for a 3.00 withdrawal. A previously authorized forced-withdrawal
> dip should not retroactively block unrelated future regular transactions.
>
> _*Environment*_
> - Branch: develop
> - Commit introducing the feature: 8c187f9d17fb839f26fc888f34a64f7c1f57a802
> (FINERACT-2471)
> - Verified still present as of commit
> c6328ab3062d8e1cf8395c00c40918a1741134ad
> (2026-07-27)
> _*Suggested fix*_
> Persist an is_force_transaction flag on m_savings_account_transaction at
> creation time, and check that per-transaction flag (or the current
> transaction's flag) inside the validateAccountBalanceConstraints loop
> instead of relying solely on the call-scoped isForceWithdrawal boolean.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)