[FINERACT-1958] Charge cannot be added to down payment installment
This commit is contained in:
+4
@@ -932,4 +932,8 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
return this.loanTransactionToRepaymentScheduleMappings;
|
||||
}
|
||||
|
||||
public boolean isDownPayment() {
|
||||
return isDownPayment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-22
@@ -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());
|
||||
if (!period.isDownPayment()) {
|
||||
|
||||
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());
|
||||
boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
|
||||
.filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
|
||||
.allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
|
||||
|
||||
period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod,
|
||||
feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
|
||||
penaltyChargesWrittenOffForRepaymentPeriod);
|
||||
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);
|
||||
|
||||
startDate = period.getDueDate();
|
||||
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()) {
|
||||
|
||||
+165
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user