Farooq Ayoade created FINERACT-2665:
---------------------------------------
Summary: PUT /loans/{loanId} (modify loan application) fails with
a NullPointerException (HTTP 500) when a loan product allows attribute
overrides but the request omits the attribute
Key: FINERACT-2665
URL: https://issues.apache.org/jira/browse/FINERACT-2665
Project: Apache Fineract
Issue Type: Bug
Components: Loan
Reporter: Farooq Ayoade
h3. Observed behavior
Modifying a submitted-and-pending loan application via {{PUT
/fineract-provider/api/v1/loans/\{loanId}}} — when the change triggers a
schedule recalculation (e.g. changing {{{}productId{}}}) and the request body
does not resend {{interestType}} — returns HTTP 500 with a raw NPE:
!http://localhost:63343/markdownPreview/1998094097/custom-guide/core-tickets!
{{java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()"
because "selectedMethod" is null
at
org.apache.fineract.portfolio.loanproduct.domain.InterestMethod.fromInt(InterestMethod.java:45)
at
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanApplicationTermsFrom(LoanScheduleAssembler.java:238)
at
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanTerms(LoanScheduleAssembler.java:178)
at
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanScheduleFrom(LoanScheduleAssembler.java:711)
at
org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformServiceImpl.calculateLoanSchedule(LoanScheduleCalculationPlatformServiceImpl.java:79)
at
org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl.updateFrom(LoanAssemblerImpl.java:866)
at
org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformServiceJpaRepositoryImpl.modifyApplication(LoanApplicationWritePlatformServiceJpaRepositoryImpl.java:277)}}
The same class of NPE occurs for {{amortizationType}} and
{{{}interestCalculationPeriodType{}}}, and a null {{repaymentEvery}} is
produced, when those overrides are enabled on the product and the field is
omitted.
h3. Expected behavior
"Allow overriding X" on a loan product means the override is {*}optional{*}.
When the modify request omits an override-enabled attribute, the schedule
should be (re)built using the *product's configured value* for that attribute —
exactly as it already does when the override is _not_ enabled — instead of
dereferencing a null and throwing.
h3. Steps to reproduce
# Have a loan product with one or more attribute overrides enabled — i.e. a
row in {{m_product_loan_configurable_attributes}} with {{interest_method_enum =
1}} (and/or {{{}amortization_method_enum{}}},
{{{}interest_calculated_in_period_enum{}}}, {{{}repay_every{}}}). A product
with *no* row also reproduces, because
{{LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes()}}
defaults every flag to {{{}true{}}}.
# Create a loan application (Submitted and pending approval) on some product.
# {{PUT /fineract-provider/api/v1/loans/\{loanId}}} changing {{productId}} to
an override-enabled product, with a minimal body that omits
{{{}interestType{}}}:
{{{ "locale": "en", "dateFormat": "dd MMMM yyyy", "loanType": "individual",
"productId": <overrideEnabledId> }}}
# Observe HTTP 500 and the NPE above. (Re-sending the loan's _current_ product
is a no-op — {{{}changes:{}{}}}, no recalculation — so it appears to "work";
the failure only manifests once a real change forces the schedule rebuild.)
h3. Root cause
{{LoanScheduleAssembler.assembleLoanApplicationTermsFrom(...)}} reads each
override-gated attribute from the request JSON when the product allows
overriding it, but passes the (possibly {{{}null{}}}) value straight into
{{{}XxxMethod.fromInt(...){}}}:
final Boolean allowOverridingInterestMethod =
loanProduct.getLoanConfigurableAttributes().getInterestMethodBoolean();
final Integer interestType =
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
// null on modify when omitted
final InterestMethod interestMethod = allowOverridingInterestMethod
? InterestMethod.fromInt(interestType) // line 238 — fromInt(null) →
NPE
: loanProduct.getLoanProductRelatedDetail().getInterestMethod();
{{}}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)