Farooq Ayoade created FINERACT-2689:
---------------------------------------
Summary: Closing (or posting interest on) a withholding-tax
deposit account fails with a 500 NullPointerException whenever the total
interest posted on the account is zero. The withholding-tax step dereferences a
null amount:
Key: FINERACT-2689
URL: https://issues.apache.org/jira/browse/FINERACT-2689
Project: Apache Fineract
Issue Type: Bug
Reporter: Farooq Ayoade
java.lang.NullPointerException: Cannot invoke
"java.math.BigDecimal.compareTo(java.math.BigDecimal)"
because "amount" is null
at ...SavingsAccount.createWithHoldTransaction(SavingsAccount.java:719)
at ...SavingsAccount.applyWithholdTaxForDepositAccounts(...)
at ...FixedDepositAccount.postPreMaturityInterest(...)
at ...DepositAccountDomainServiceJpa.handleFDAccountPreMatureClosure(...)
The most common trigger is premature-closing a short-term fixed deposit whose
{*}pre-closure penalty rate exceeds the chart rate{*}: the effective interest
is clamped to zero, so no interest-posting transaction is created, and the
withholding-tax pass then runs with a null interest total.
h3. Steps to reproduce
# Create a Fixed Deposit product with *withholding tax enabled* (assign a tax
group) and a pre-closure penalty configured so the penalty rate is *greater
than or equal to* the applicable chart rate (e.g. chart 5%, pre-closure penalty
20%, "Till Premature Withdrawal").
# Open and activate an FD account ({{{}withHoldTax = true{}}}, tax group
inherited).
# Premature-close it a few days into the term: {{POST
/fixeddepositaccounts/\{id}?command=prematureClose}} with
{{onAccountClosureId}} = withdraw (100) or transfer-to-savings (200).
# *500* {{{}NullPointerException … because "amount" is null{}}}. The command
rolls back; the account cannot be closed.
Because the penalty (20%) exceeds the chart rate (5%), the effective premature
rate clamps to 0
({{{}FixedDepositAccount.getEffectiveInterestRateAsFraction{}}} → {{{}max(rate
− penalty, 0){}}}), so {{postPreMaturityInterest}} posts *no* interest
transaction and the interest total is null at the withholding step.
h3. Root cause
{{FixedDepositAccount.postPreMaturityInterest(...)}} (and the normal
{{postInterest}} path) calls
{{{}SavingsAccount.applyWithholdTaxForDepositAccounts(...){}}}, which computes:
!http://localhost:63342/markdownPreview/1258363713/custom-guide/core-tickets!
{{final BigDecimal totalInterestPosted =
this.savingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(this.currency,
this.transactions);}}
{{SavingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(...)}}
returns {{total.getAmountDefaultedToNullIfZero()}} — i.e. *{{null}}* when there
are no (net) interest-posting transactions. That null is passed straight into
the withholding helpers, both of which guard only on the tax group and then
dereference {{{}amount{}}}:
!http://localhost:63342/markdownPreview/1258363713/custom-guide/core-tickets!
{{// SavingsAccount.createWithHoldTransaction(amount, date,
backdatedTxnsAllowedTill)
if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) { // NPE:
amount == null
// SavingsAccount.updateWithHoldTransaction(amount, withholdTransaction)
if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) { // same
flaw}}
For a withholding-tax account the tax group is non-null, so the first clause
passes and {{amount.compareTo}} throws. The bug surfaces on any withholding-tax
deposit account whose net interest posted is zero — premature close where the
penalty wipes out interest is the easy real-world path, but it also applies to
a zero-interest posting cycle.
h3. Fix
Null-guard {{amount}} in both {{SavingsAccount.createWithHoldTransaction(...)}}
and {{SavingsAccount.updateWithHoldTransaction(...)}} — a null (or
non-positive) interest total means there is nothing to withhold tax on, so both
should no-op and return {{{}false{}}}. No behavior change when interest is
actually posted: a non-null positive amount takes the exact same path as before.
h3. Impact
Withholding-tax fixed/recurring deposit accounts can be premature-closed (and
post interest) when net interest is zero, instead of 500-ing and rolling back.
No change for accounts with a positive interest total or with no tax group.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)