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 498e81a73 [FINERACT-1958] Charge cannot be added to down payment
installment
498e81a73 is described below
commit 498e81a734acdb1b2b511608f8fbacd03009e8fa
Author: taskain7 <[email protected]>
AuthorDate: Mon Aug 28 11:17:08 2023 +0200
[FINERACT-1958] Charge cannot be added to down payment installment
---
.../domain/LoanRepaymentScheduleInstallment.java | 4 +
.../LoanRepaymentScheduleProcessingWrapper.java | 57 +++----
.../LoanRepaymentScheduleWithDownPaymentTest.java | 166 ++++++++++++++++++++-
3 files changed, 201 insertions(+), 26 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 44bf43e47..69852054f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -932,4 +932,8 @@ public class LoanRepaymentScheduleInstallment extends
AbstractAuditableWithUTCDa
return this.loanTransactionToRepaymentScheduleMappings;
}
+ public boolean isDownPayment() {
+ return isDownPayment;
+ }
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
index 04ded2c13..2d73f5295 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
@@ -42,37 +42,45 @@ public class LoanRepaymentScheduleProcessingWrapper {
LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods)
{
- final Money feeChargesDueForRepaymentPeriod =
cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
- currency, period, totalPrincipal, totalInterest,
!period.isRecalculatedInterestComponent());
- final Money feeChargesWaivedForRepaymentPeriod =
cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
- currency, !period.isRecalculatedInterestComponent(),
period.isFirstPeriod());
- final Money feeChargesWrittenOffForRepaymentPeriod =
cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
- loanCharges, currency,
!period.isRecalculatedInterestComponent(), period.isFirstPeriod());
-
- final Money penaltyChargesDueForRepaymentPeriod =
cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges,
- currency, period, totalPrincipal, totalInterest,
!period.isRecalculatedInterestComponent());
- final Money penaltyChargesWaivedForRepaymentPeriod =
cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
- loanCharges, currency,
!period.isRecalculatedInterestComponent(), period.isFirstPeriod());
- final Money penaltyChargesWrittenOffForRepaymentPeriod =
cumulativePenaltyChargesWrittenOffWithin(startDate,
- period.getDueDate(), loanCharges, currency,
!period.isRecalculatedInterestComponent(), period.isFirstPeriod());
-
- period.updateChargePortion(feeChargesDueForRepaymentPeriod,
feeChargesWaivedForRepaymentPeriod,
- feeChargesWrittenOffForRepaymentPeriod,
penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
- penaltyChargesWrittenOffForRepaymentPeriod);
-
- startDate = period.getDueDate();
+ if (!period.isDownPayment()) {
+
+ boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
+ .filter(repaymentPeriod ->
repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
+
.allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
+
+ final Money feeChargesDueForRepaymentPeriod =
cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
+ currency, period, totalPrincipal, totalInterest,
!period.isRecalculatedInterestComponent(),
+ isFirstNonDownPaymentPeriod);
+ final Money feeChargesWaivedForRepaymentPeriod =
cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(),
+ loanCharges, currency,
!period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
+ final Money feeChargesWrittenOffForRepaymentPeriod =
cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
+ loanCharges, currency,
!period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
+
+ final Money penaltyChargesDueForRepaymentPeriod =
cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(),
+ loanCharges, currency, period, totalPrincipal,
totalInterest, !period.isRecalculatedInterestComponent(),
+ isFirstNonDownPaymentPeriod);
+ final Money penaltyChargesWaivedForRepaymentPeriod =
cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
+ loanCharges, currency,
!period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
+ final Money penaltyChargesWrittenOffForRepaymentPeriod =
cumulativePenaltyChargesWrittenOffWithin(startDate,
+ period.getDueDate(), loanCharges, currency,
!period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
+
+ period.updateChargePortion(feeChargesDueForRepaymentPeriod,
feeChargesWaivedForRepaymentPeriod,
+ feeChargesWrittenOffForRepaymentPeriod,
penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
+ penaltyChargesWrittenOffForRepaymentPeriod);
+
+ startDate = period.getDueDate();
+ }
}
}
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart,
final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency,
LoanRepaymentScheduleInstallment period, final Money totalPrincipal,
- final Money totalInterest, boolean isInstallmentChargeApplicable) {
+ final Money totalInterest, boolean isInstallmentChargeApplicable,
boolean isFirstPeriod) {
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement())
{
- boolean isDue = period.isFirstPeriod()
- ?
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart,
periodEnd)
+ boolean isDue = isFirstPeriod ?
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart,
periodEnd)
:
loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() &&
isInstallmentChargeApplicable) {
if (loanCharge.getChargeCalculation().isPercentageBased())
{
@@ -175,14 +183,13 @@ public class LoanRepaymentScheduleProcessingWrapper {
private Money cumulativePenaltyChargesDueWithin(final LocalDate
periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency
currency, LoanRepaymentScheduleInstallment period,
- final Money totalPrincipal, final Money totalInterest, boolean
isInstallmentChargeApplicable) {
+ final Money totalPrincipal, final Money totalInterest, boolean
isInstallmentChargeApplicable, boolean isFirstPeriod) {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
- boolean isDue = period.isFirstPeriod()
- ?
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart,
periodEnd)
+ boolean isDue = isFirstPeriod ?
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart,
periodEnd)
:
loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
if (loanCharge.isInstalmentFee() &&
isInstallmentChargeApplicable) {
if (loanCharge.getChargeCalculation().isPercentageBased())
{
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 99d0d7194..0c7c2a3bb 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
@@ -36,8 +36,11 @@ import
org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
@@ -455,6 +458,167 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
assertEquals(outstandingBalanceOnThirdRepayment,
thirdRepaymentPeriod.getPrincipalLoanBalanceOutstanding());
}
+ @Test
+ public void loanRepaymentScheduleWithChargeAndDownPayment() {
+ 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 = false;
+
+ 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 = createApproveAndDisburseLoanAccount(clientId,
loanProductId.longValue(), loanExternalIdStr, "1");
+
+ final Double feeAmount = 10.00;
+ String payloadJSON =
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
feeAmount.toString(),
+ false);
+ final PostChargesResponse postChargesResponse =
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+ assertNotNull(postChargesResponse);
+ final Long loanChargeId = postChargesResponse.getResourceId();
+
+ payloadJSON =
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(),
"03 September 2022",
+ feeAmount.toString());
+ PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse =
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+ responseSpec);
+ assertNotNull(postLoansLoanIdChargesResponse);
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ assertNotNull(loanDetails);
+ assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
+ assertEquals(0,
loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
+ assertEquals(enableAutoRepaymentForDownPayment,
loanDetails.getEnableAutoRepaymentForDownPayment());
+
+ List<GetLoansLoanIdRepaymentPeriod> periods =
loanDetails.getRepaymentSchedule().getPeriods();
+ Double expectedDownPaymentAmount = 250.00;
+ LocalDate expectedDownPaymentDueDate = LocalDate.of(2022, 9, 3);
+ Double expectedRepaymentAmount = 750.00;
+ Double expectedTotalDueForRepaymentInstallment = 760.0;
+ LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
+
+ assertTrue(periods.stream() //
+ .anyMatch(period ->
expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
+ &&
expectedDownPaymentDueDate.equals(period.getDueDate()) //
+ &&
Double.valueOf(0.00).equals(period.getFeeChargesDue())));
+ assertTrue(periods.stream() //
+ .anyMatch(period ->
expectedTotalDueForRepaymentInstallment.equals(period.getTotalDueForPeriod()) //
+ &&
expectedRepaymentAmount.equals(period.getPrincipalDue()) //
+ &&
expectedRepaymentDueDate.equals(period.getDueDate()) //
+ && feeAmount.equals(period.getFeeChargesDue())));
+ }
+
+ @Test
+ public void
loanRepaymentScheduleWithMultiDisbursementProductTwoDisbursementAndThreeRepaymentsAndDownPaymentAndCharge()
{
+ 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");
+
+ final Double feeAmount = 10.00;
+ String payloadJSON =
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
feeAmount.toString(),
+ false);
+ final PostChargesResponse postChargesResponse =
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+ assertNotNull(postChargesResponse);
+ final Long loanChargeId = postChargesResponse.getResourceId();
+
+ payloadJSON =
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(),
"04 September 2022",
+ feeAmount.toString());
+ PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse =
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+ responseSpec);
+ assertNotNull(postLoansLoanIdChargesResponse);
+
+ 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;
+ Double expectedRepaymentTotalDueWithCharge = 260.0;
+ 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.getPrincipalDue());
+ assertEquals(expectedRepaymentTotalDueWithCharge,
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,
final Integer delinquencyBucketId, Boolean enableDownPayment,
String disbursedAmountPercentageForDownPayment,
Boolean enableAutoRepaymentForDownPayment, boolean
multiDisbursement) {
@@ -488,7 +652,7 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
final Integer loanId =
loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId,
null);
- HashMap hashMap =
loanTransactionHelper.disburseLoanWithTransactionAmount("03 September 2022",
loanId, "1000");
+ loanTransactionHelper.disburseLoanWithTransactionAmount("03 September
2022", loanId, "1000");
return loanId;
}