[ 
https://issues.apache.org/jira/browse/FINERACT-2689?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Farooq Ayoade updated FINERACT-2689:
------------------------------------
    Description: 
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:
 
 
{{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{}}}:
 

 
{{// 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.

  was:
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.


> 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
>            Priority: Major
>
> 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:
>  
>  
> {{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{}}}:
>  
>  
> {{// 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)

Reply via email to