This is an automated email from the ASF dual-hosted git repository.
taskain 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 388510476 [FINERACT-1958] Repayment schedule recalculation fix for
multidisburse down payment
388510476 is described below
commit 38851047670860020a0f215d0047c45f4695b8a5
Author: taskain7 <[email protected]>
AuthorDate: Wed Aug 23 09:20:03 2023 +0200
[FINERACT-1958] Repayment schedule recalculation fix for multidisburse down
payment
---
.../domain/AbstractLoanScheduleGenerator.java | 9 ++
.../service/LoanReadPlatformServiceImpl.java | 18 ++-
.../LoanRepaymentScheduleWithDownPaymentTest.java | 122 ++++++++++++++++++---
3 files changed, 132 insertions(+), 17 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index e152c78da..a5edd7abc 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -284,6 +284,15 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
// backup for pre-close transaction
updateCompoundingDetails(scheduleParams,
periodStartDateApplicableForInterest);
+ if (loanApplicationTerms.isMultiDisburseLoan() &&
loanApplicationTerms.isDownPaymentEnabled()) {
+ long numberOfDownPaymentPeriods = periods.stream() //
+
.filter(LoanScheduleModelPeriod::isDownPaymentPeriod).count();
+ Long
notApplicableForInstallmentRecalculationDownPaymentPeriods =
numberOfDownPaymentPeriods - 1;
+ // Only the first down payment installment counts for the
number of repayments
+ scheduleParams.setPeriodNumber(
+ scheduleParams.getPeriodNumber() -
notApplicableForInstallmentRecalculationDownPaymentPeriods.intValue());
+ }
+
// 5 determine principal,interest of repayment period
PrincipalInterest principalInterestForThisPeriod =
calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator,
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 831631693..1ce71ca2b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1221,7 +1221,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
periods.add(periodData);
this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
disbursementPeriodIds.add(data.getId());
- } else if (data.isDueForDisbursement(fromDate,
dueDate)) {
+ } else if (data.isDueForDisbursement(fromDate,
dueDate) && !disbursementPeriodIds.contains(data.getId())) {
if (!excludePastUndisbursed || data.isDisbursed()
||
!data.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
principal = principal.add(data.getPrincipal());
@@ -1235,7 +1235,23 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
}
periods.add(periodData);
this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
+ disbursementPeriodIds.add(data.getId());
}
+ } else if (fromDate.equals(dueDate) &&
data.disbursementDate().equals(fromDate)
+ &&
!disbursementPeriodIds.contains(data.getId())) {
+ principal = principal.add(data.getPrincipal());
+ LoanSchedulePeriodData periodData = null;
+ if (data.getChargeAmount() == null) {
+ periodData =
LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(),
data.getPrincipal(),
+ disbursementChargeAmount,
data.isDisbursed());
+ } else {
+ periodData =
LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(),
data.getPrincipal(),
+
disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount),
+ data.isDisbursed());
+ }
+ periods.add(periodData);
+ this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
+ disbursementPeriodIds.add(data.getId());
}
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
index 1adda179d..99d0d7194 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
@@ -338,6 +338,7 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
assertEquals(enableAutoRepaymentForDownPayment,
loanDetails.getEnableAutoRepaymentForDownPayment());
List<GetLoansLoanIdRepaymentPeriod> periods =
loanDetails.getRepaymentSchedule().getPeriods();
+ Double expectedOutstandingLoanBalanceOnDisbursement = 1000.00;
Double expectedDownPaymentAmount = 250.00;
LocalDate expectedDownPaymentDueDate = LocalDate.of(2022, 9, 3);
Double expectedRepaymentAmount = 250.00;
@@ -348,21 +349,110 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
LocalDate expectedThirdRepaymentDueDate = LocalDate.of(2022, 12, 3);
Double outstandingBalanceOnThirdRepayment = 0.00;
- assertTrue(periods.stream() //
- .anyMatch(period ->
expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
- &&
expectedDownPaymentDueDate.equals(period.getDueDate())));
- assertTrue(periods.stream()
- .anyMatch(period ->
expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
- &&
expectedFirstRepaymentDueDate.equals(period.getDueDate())
- &&
outstandingBalanceOnFirstRepayment.equals(period.getPrincipalLoanBalanceOutstanding())));
- assertTrue(periods.stream()
- .anyMatch(period ->
expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
- &&
expectedSecondRepaymentDueDate.equals(period.getDueDate())
- &&
outstandingBalanceOnSecondRepayment.equals(period.getPrincipalLoanBalanceOutstanding())));
- assertTrue(periods.stream()
- .anyMatch(period ->
expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
- &&
expectedThirdRepaymentDueDate.equals(period.getDueDate())
- &&
outstandingBalanceOnThirdRepayment.equals(period.getPrincipalLoanBalanceOutstanding())));
+ GetLoansLoanIdRepaymentPeriod firstDisbursementPeriod = periods.get(0);
+ assertEquals(expectedDownPaymentDueDate,
firstDisbursementPeriod.getDueDate());
+ assertEquals(expectedOutstandingLoanBalanceOnDisbursement,
firstDisbursementPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod firstDownPaymentPeriod = periods.get(1);
+ assertEquals(expectedDownPaymentAmount,
firstDownPaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedDownPaymentDueDate,
firstDownPaymentPeriod.getDueDate());
+
+ GetLoansLoanIdRepaymentPeriod firstRepaymentPeriod = periods.get(2);
+ assertEquals(expectedRepaymentAmount,
firstRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedFirstRepaymentDueDate,
firstRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnFirstRepayment,
firstRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod secondRepaymentPeriod = periods.get(3);
+ assertEquals(expectedRepaymentAmount,
secondRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedSecondRepaymentDueDate,
secondRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnSecondRepayment,
secondRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod thirdRepaymentPeriod = periods.get(4);
+ assertEquals(expectedRepaymentAmount,
thirdRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedThirdRepaymentDueDate,
thirdRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnThirdRepayment,
thirdRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
+ }
+
+ @Test
+ public void
loanRepaymentScheduleWithMultiDisbursementProductTwoDisbursementAndThreeRepaymentsAndDownPayment()
{
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ final Integer delinquencyBucketId =
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket =
DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ Boolean enableDownPayment = true;
+ BigDecimal disbursedAmountPercentageForDownPayment =
BigDecimal.valueOf(25);
+ Boolean enableAutoRepaymentForDownPayment = true;
+
+ final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+
+ Integer loanProductId =
createLoanProductWithDownPaymentConfiguration(loanTransactionHelper,
delinquencyBucketId, enableDownPayment,
+ "25", enableAutoRepaymentForDownPayment, true);
+
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(enableDownPayment,
getLoanProductsProductResponse.getEnableDownPayment());
+ assertEquals(0,
getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
+ .compareTo(disbursedAmountPercentageForDownPayment));
+ assertEquals(enableAutoRepaymentForDownPayment,
getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
+
+ final Integer loanId =
createApproveAndDisburseTwiceLoanAccount(clientId, loanProductId.longValue(),
loanExternalIdStr, "4");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ assertNotNull(loanDetails);
+ assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
+ assertEquals(0,
loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
+ assertEquals(enableAutoRepaymentForDownPayment,
loanDetails.getEnableAutoRepaymentForDownPayment());
+
+ loanTransactionHelper.printRepaymentSchedule(loanDetails);
+
+ List<GetLoansLoanIdRepaymentPeriod> periods =
loanDetails.getRepaymentSchedule().getPeriods();
+ Double expectedOutstandingLoanBalanceOnFirstDisbursement = 700.00;
+ Double expectedFirstDownPaymentAmount = 175.00;
+ LocalDate expectedFirstDownPaymentDueDate = LocalDate.of(2022, 9, 3);
+ Double expectedOutstandingLoanBalanceOnSecondDisbursement = 300.00;
+ Double expectedSecondDownPaymentAmount = 75.00;
+ LocalDate expectedSecondDownPaymentDueDate = LocalDate.of(2022, 9, 4);
+ Double expectedRepaymentAmount = 250.00;
+ LocalDate expectedFirstRepaymentDueDate = LocalDate.of(2022, 10, 3);
+ Double outstandingBalanceOnFirstRepayment = 500.00;
+ LocalDate expectedSecondRepaymentDueDate = LocalDate.of(2022, 11, 3);
+ Double outstandingBalanceOnSecondRepayment = 250.00;
+ LocalDate expectedThirdRepaymentDueDate = LocalDate.of(2022, 12, 3);
+ Double outstandingBalanceOnThirdRepayment = 0.00;
+
+ GetLoansLoanIdRepaymentPeriod firstDisbursementPeriod = periods.get(0);
+ assertEquals(expectedFirstDownPaymentDueDate,
firstDisbursementPeriod.getDueDate());
+ assertEquals(expectedOutstandingLoanBalanceOnFirstDisbursement,
firstDisbursementPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod firstDownPaymentPeriod = periods.get(1);
+ assertEquals(expectedFirstDownPaymentAmount,
firstDownPaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedFirstDownPaymentDueDate,
firstDownPaymentPeriod.getDueDate());
+
+ GetLoansLoanIdRepaymentPeriod secondDisbursementPeriod =
periods.get(2);
+ assertEquals(expectedSecondDownPaymentDueDate,
secondDisbursementPeriod.getDueDate());
+ assertEquals(expectedOutstandingLoanBalanceOnSecondDisbursement,
secondDisbursementPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod secondDownPaymentPeriod = periods.get(3);
+ assertEquals(expectedSecondDownPaymentAmount,
secondDownPaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedSecondDownPaymentDueDate,
secondDownPaymentPeriod.getDueDate());
+
+ GetLoansLoanIdRepaymentPeriod firstRepaymentPeriod = periods.get(4);
+ assertEquals(expectedRepaymentAmount,
firstRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedFirstRepaymentDueDate,
firstRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnFirstRepayment,
firstRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod secondRepaymentPeriod = periods.get(5);
+ assertEquals(expectedRepaymentAmount,
secondRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedSecondRepaymentDueDate,
secondRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnSecondRepayment,
secondRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
+
+ GetLoansLoanIdRepaymentPeriod thirdRepaymentPeriod = periods.get(6);
+ assertEquals(expectedRepaymentAmount,
thirdRepaymentPeriod.getTotalDueForPeriod());
+ assertEquals(expectedThirdRepaymentDueDate,
thirdRepaymentPeriod.getDueDate());
+ assertEquals(outstandingBalanceOnThirdRepayment,
thirdRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
}
private Integer createLoanProductWithDownPaymentConfiguration(final
LoanTransactionHelper loanTransactionHelper,
@@ -405,7 +495,7 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
private Integer createApproveAndDisburseTwiceLoanAccount(final Integer
clientID, final Long loanProductID, final String externalId,
final String numberOfRepayments) {
- String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
+ String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency(numberOfRepayments)
.withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments).withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()