This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 284022abb FINERACT-2081: fix NPE Loan product creation with null
values in Interest rate or Number of repayments
284022abb is described below
commit 284022abbaf05f075f341839e11fdf494a80e9dc
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Tue Aug 27 07:59:43 2024 -0500
FINERACT-2081: fix NPE Loan product creation with null values in Interest
rate or Number of repayments
---
.../serialization/LoanProductDataValidator.java | 60 ++++++++++++----------
...hAdvancedPaymentAllocationIntegrationTests.java | 44 ++++++++++++++++
2 files changed, 77 insertions(+), 27 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index b55f40c68..2b9a876ee 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -43,6 +43,7 @@ import
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRu
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
@@ -569,7 +570,7 @@ public final class LoanProductDataValidator {
Locale.getDefault());
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4);
- isInterestBearing =
interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
+ isInterestBearing =
MathUtil.isGreaterThanZero(interestRatePerPeriod);
}
// Fixed Length validation
@@ -1296,11 +1297,14 @@ public final class LoanProductDataValidator {
.integerGreaterThanZero();
}
- Integer numberOfRepayments = loanProduct.getNumberOfRepayments();
+ Integer numberOfRepayments = null;
if (this.fromApiJsonHelper.parameterExists(NUMBER_OF_REPAYMENTS,
element)) {
numberOfRepayments =
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(NUMBER_OF_REPAYMENTS,
element);
baseDataValidator.reset().parameter(NUMBER_OF_REPAYMENTS).value(numberOfRepayments).notNull().integerGreaterThanZero();
}
+ if (numberOfRepayments == null) {
+ numberOfRepayments = loanProduct.getNumberOfRepayments();
+ }
Integer repaymentEvery =
loanProduct.getLoanProductRelatedDetail().getRepayEvery();
if (this.fromApiJsonHelper.parameterExists(REPAYMENT_EVERY, element)) {
@@ -1608,7 +1612,7 @@ public final class LoanProductDataValidator {
}
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4);
- isInterestBearing =
interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
+ isInterestBearing =
MathUtil.isGreaterThanZero(interestRatePerPeriod);
}
// Fixed Length validation
@@ -2541,34 +2545,36 @@ public final class LoanProductDataValidator {
public void validateRepaymentPeriodWithGraceSettings(final Integer
numberOfRepayments, final Integer graceOnPrincipalPayment,
final Integer graceOnInterestPayment, final Integer
graceOnInterestCharged, final Integer recurringMoratoriumOnPrincipalPeriods,
DataValidatorBuilder baseDataValidator) {
- if (numberOfRepayments <=
defaultToZeroIfNull(graceOnPrincipalPayment)) {
-
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment)
- .failWithCode(".mustBeLessThan.numberOfRepayments");
- }
+ if (numberOfRepayments != null) {
+ if (numberOfRepayments <=
defaultToZeroIfNull(graceOnPrincipalPayment)) {
+
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment)
+ .failWithCode(".mustBeLessThan.numberOfRepayments");
+ }
- if (numberOfRepayments <= defaultToZeroIfNull(graceOnInterestPayment))
{
-
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment)
- .failWithCode(".mustBeLessThan.numberOfRepayments");
- }
+ if (numberOfRepayments <=
defaultToZeroIfNull(graceOnInterestPayment)) {
+
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment)
+ .failWithCode(".mustBeLessThan.numberOfRepayments");
+ }
- if (numberOfRepayments < defaultToZeroIfNull(graceOnInterestCharged)) {
-
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged)
- .failWithCode(".mustBeLessThan.numberOfRepayments");
- }
+ if (numberOfRepayments <
defaultToZeroIfNull(graceOnInterestCharged)) {
+
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged)
+ .failWithCode(".mustBeLessThan.numberOfRepayments");
+ }
- int graceOnPrincipal = 0;
- if (graceOnPrincipalPayment != null) {
- graceOnPrincipal = graceOnPrincipalPayment;
- }
- int recurMoratoriumOnPrincipal = 0;
- if (recurringMoratoriumOnPrincipalPeriods != null) {
- recurMoratoriumOnPrincipal = recurringMoratoriumOnPrincipalPeriods;
- }
+ int graceOnPrincipal = 0;
+ if (graceOnPrincipalPayment != null) {
+ graceOnPrincipal = graceOnPrincipalPayment;
+ }
+ int recurMoratoriumOnPrincipal = 0;
+ if (recurringMoratoriumOnPrincipalPeriods != null) {
+ recurMoratoriumOnPrincipal =
recurringMoratoriumOnPrincipalPeriods;
+ }
- if ((recurMoratoriumOnPrincipal > 0) && ((numberOfRepayments -
graceOnPrincipal) % (recurMoratoriumOnPrincipal + 1) != 1)) {
-
baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods")
- .value(graceOnPrincipal).value(recurMoratoriumOnPrincipal)
-
.failWithCode("causes.principal.moratorium.for.last.installment");
+ if ((recurMoratoriumOnPrincipal > 0) && ((numberOfRepayments -
graceOnPrincipal) % (recurMoratoriumOnPrincipal + 1) != 1)) {
+
baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods")
+
.value(graceOnPrincipal).value(recurMoratoriumOnPrincipal)
+
.failWithCode("causes.principal.moratorium.for.last.installment");
+ }
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
index a3303169a..40edf8ac3 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
@@ -364,6 +364,50 @@ public class
LoanProductWithAdvancedPaymentAllocationIntegrationTests {
loanProductError.get(0).get("userMessageGlobalisationCode"));
}
+ @Test
+ public void testCreateShouldFailWhenNoNumberOfRepaymentsIsProvided() {
+ // given
+ ResponseSpecification errorResponse = new
ResponseSpecBuilder().expectStatusCode(400).build();
+ LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(REQUEST_SPEC, errorResponse);
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation();
+ AdvancedPaymentData repaymentPaymentAllocation =
createRepaymentPaymentAllocation();
+
+ // when
+ String loanProduct = loanProductTestBuilder(customization ->
customization
+ .addAdvancedPaymentAllocation(defaultAllocation,
repaymentPaymentAllocation).withPrincipal("15,000.00")
+
.withNumberOfRepayments(null).withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
+ .withAccountingRulePeriodicAccrual(new Account[] {
ASSET_ACCOUNT, EXPENSE_ACCOUNT, INCOME_ACCOUNT, OVERPAYMENT_ACCOUNT })
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+ .withFeeAndPenaltyAssetAccount(FEE_PENALTY_ACCOUNT).build());
+
+ // when
+ List<Map<String, String>> loanProductError =
validationErrorHelper.getLoanProductError(loanProduct, "errors");
+ Assertions.assertEquals("The parameter numberOfRepayments is
mandatory.",
+ loanProductError.get(0).get("defaultUserMessage").replace('`',
' '));
+ }
+
+ @Test
+ public void testCreateShouldFailWhenNoInterestRateIsProvided() {
+ // given
+ ResponseSpecification errorResponse = new
ResponseSpecBuilder().expectStatusCode(400).build();
+ LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(REQUEST_SPEC, errorResponse);
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation();
+ AdvancedPaymentData repaymentPaymentAllocation =
createRepaymentPaymentAllocation();
+
+ // when
+ String loanProduct = loanProductTestBuilder(customization ->
customization
+ .addAdvancedPaymentAllocation(defaultAllocation,
repaymentPaymentAllocation).withPrincipal("15,000.00")
+
.withNumberOfRepayments("4").withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod(null)
+ .withAccountingRulePeriodicAccrual(new Account[] {
ASSET_ACCOUNT, EXPENSE_ACCOUNT, INCOME_ACCOUNT, OVERPAYMENT_ACCOUNT })
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+ .withFeeAndPenaltyAssetAccount(FEE_PENALTY_ACCOUNT).build());
+
+ // when
+ List<Map<String, String>> loanProductError =
validationErrorHelper.getLoanProductError(loanProduct, "errors");
+ Assertions.assertEquals("The parameter interestRatePerPeriod is
mandatory.",
+ loanProductError.get(0).get("defaultUserMessage").replace('`',
' '));
+ }
+
private String loanProductTestBuilder(Consumer<LoanProductTestBuilder>
customization) {
LoanProductTestBuilder builder = new
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")