This is an automated email from the ASF dual-hosted git repository.
manojvm pushed a commit to branch 1.7.1
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/1.7.1 by this push:
new 67bb10cb5 FINERACT-1746: Random Installments Generation Issue (#2629)
67bb10cb5 is described below
commit 67bb10cb56c1b8cc951f17ba0683037492785664
Author: logoutdhaval <[email protected]>
AuthorDate: Sat Oct 15 13:18:12 2022 +0530
FINERACT-1746: Random Installments Generation Issue (#2629)
Co-authored-by: Dhaval Maniyar <[email protected]>
---
.../loanschedule/data/LoanScheduleParams.java | 73 +++---
.../domain/AbstractLoanScheduleGenerator.java | 15 +-
.../ClientLoanIntegrationTest.java | 283 +++++++++++++++++++++
.../common/loans/LoanProductTestBuilder.java | 9 +
.../common/loans/LoanTransactionHelper.java | 5 +
5 files changed, 350 insertions(+), 35 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
index c5c0bec83..1e18a9608 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
@@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import lombok.Getter;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -40,6 +41,8 @@ public final class LoanScheduleParams {
private LocalDate periodStartDate;
private LocalDate actualRepaymentDate;
+ @Getter
+ private LocalDate originalRepaymentDate;
// variables for cumulative totals
private Money totalCumulativePrincipal;
@@ -100,13 +103,13 @@ public final class LoanScheduleParams {
private final boolean applyInterestRecalculation;
private LoanScheduleParams(final int periodNumber, final int
instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
- final LocalDate actualRepaymentDate, final Money
totalCumulativePrincipal, final Money totalCumulativeInterest,
- final Money totalFeeChargesCharged, final Money
totalPenaltyChargesCharged, final Money totalRepaymentExpected,
- Money totalOutstandingInterestPaymentDueToGrace, final Money
reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
- final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate,
Money> compoundingMap, final Money unCompoundedAmount,
- final Map<LocalDate, Money> disburseDetailMap, Money
principalToBeScheduled, final Money outstandingBalance,
- final Money outstandingBalanceAsPerRest, final
List<LoanRepaymentScheduleInstallment> installments,
- final Collection<RecalculationDetail> recalculationDetails,
+ final LocalDate actualRepaymentDate, final LocalDate
originalRepaymentDate, final Money totalCumulativePrincipal,
+ final Money totalCumulativeInterest, final Money
totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
+ final Money totalRepaymentExpected, Money
totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
+ final Map<LocalDate, Money> principalPortionMap, final
Map<LocalDate, Money> latePaymentMap,
+ final Map<LocalDate, Money> compoundingMap, final Money
unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
+ Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest,
+ final List<LoanRepaymentScheduleInstallment> installments, final
Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor
loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate, final MonetaryCurrency currency,
final boolean applyInterestRecalculation) {
this.periodNumber = periodNumber;
@@ -114,6 +117,7 @@ public final class LoanScheduleParams {
this.loanTermInDays = loanTermInDays;
this.periodStartDate = periodStartDate;
this.actualRepaymentDate = actualRepaymentDate;
+ this.originalRepaymentDate = originalRepaymentDate;
this.totalCumulativePrincipal = totalCumulativePrincipal;
this.totalCumulativeInterest = totalCumulativeInterest;
this.totalFeeChargesCharged = totalFeeChargesCharged;
@@ -142,10 +146,10 @@ public final class LoanScheduleParams {
}
public static LoanScheduleParams
createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int
instalmentNumber,
- int loanTermInDays, LocalDate periodStartDate, final LocalDate
actualRepaymentDate, final Money totalCumulativePrincipal,
- final Money totalCumulativeInterest, final Money
totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
- final Money totalRepaymentExpected, Money
totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
- final Map<LocalDate, Money> principalPortionMap, final
Map<LocalDate, Money> latePaymentMap,
+ int loanTermInDays, LocalDate periodStartDate, final LocalDate
actualRepaymentDate, final LocalDate originalRepaymentDate,
+ final Money totalCumulativePrincipal, final Money
totalCumulativeInterest, final Money totalFeeChargesCharged,
+ final Money totalPenaltyChargesCharged, final Money
totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace,
+ final Money reducePrincipal, final Map<LocalDate, Money>
principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final Map<LocalDate, Money> compoundingMap, Money
unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
final Money principalToBeScheduled, final Money
outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final
Collection<RecalculationDetail> recalculationDetails,
@@ -153,11 +157,11 @@ public final class LoanScheduleParams {
final MonetaryCurrency currency, final boolean
applyInterestRecalculation) {
final boolean partialUpdate = true;
return new LoanScheduleParams(periodNumber, instalmentNumber,
loanTermInDays, periodStartDate, actualRepaymentDate,
- totalCumulativePrincipal, totalCumulativeInterest,
totalFeeChargesCharged, totalPenaltyChargesCharged,
- totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
principalPortionMap, latePaymentMap,
- compoundingMap, unCompoundedAmount, disburseDetailMap,
principalToBeScheduled, outstandingBalance,
- outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor,
- scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
+ originalRepaymentDate, totalCumulativePrincipal,
totalCumulativeInterest, totalFeeChargesCharged,
+ totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+ principalPortionMap, latePaymentMap, compoundingMap,
unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+ outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails,
+ loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams
createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail>
recalculationDetails,
@@ -167,6 +171,7 @@ public final class LoanScheduleParams {
final int instalmentNumber = 1;
final LocalDate periodStartDate = null;
final LocalDate actualRepaymentDate = null;
+ final LocalDate originalRepaymentDate = null;
final Money totalCumulativePrincipal = null;
final Money totalCumulativeInterest = null;
final Money totalFeeChargesCharged = null;
@@ -187,11 +192,11 @@ public final class LoanScheduleParams {
final MonetaryCurrency currency = null;
final Money unCompoundedAmount = null;
return new LoanScheduleParams(periodNumber, instalmentNumber,
loanTermInDays, periodStartDate, actualRepaymentDate,
- totalCumulativePrincipal, totalCumulativeInterest,
totalFeeChargesCharged, totalPenaltyChargesCharged,
- totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
principalPortionMap, latePaymentMap,
- compoundingMap, unCompoundedAmount, disburseDetailMap,
principalToBeScheduled, outstandingBalance,
- outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor,
- scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
+ originalRepaymentDate, totalCumulativePrincipal,
totalCumulativeInterest, totalFeeChargesCharged,
+ totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+ principalPortionMap, latePaymentMap, compoundingMap,
unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+ outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails,
+ loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final
MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
@@ -202,6 +207,7 @@ public final class LoanScheduleParams {
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace =
Money.zero(currency);
+ final LocalDate originalRepaymentDate = periodStartDate;
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
@@ -221,11 +227,11 @@ public final class LoanScheduleParams {
final boolean applyInterestRecalculation = false;
final Money unCompoundedAmount = Money.zero(currency);
return new LoanScheduleParams(periodNumber, instalmentNumber,
loanTermInDays, periodStartDate, actualRepaymentDate,
- totalCumulativePrincipal, totalCumulativeInterest,
totalFeeChargesCharged, totalPenaltyChargesCharged,
- totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
principalPortionMap, latePaymentMap,
- compoundingMap, unCompoundedAmount, disburseDetailMap,
principalToBeScheduled, outstandingBalance,
- outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor,
- scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
+ originalRepaymentDate, totalCumulativePrincipal,
totalCumulativeInterest, totalFeeChargesCharged,
+ totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+ principalPortionMap, latePaymentMap, compoundingMap,
unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+ outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails,
+ loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final
MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
@@ -236,6 +242,7 @@ public final class LoanScheduleParams {
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace =
Money.zero(currency);
+ final LocalDate originalRepaymentDate = periodStartDate;
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
@@ -255,11 +262,11 @@ public final class LoanScheduleParams {
final boolean applyInterestRecalculation =
loanScheduleParams.applyInterestRecalculation;
final Money unCompoundedAmount = Money.zero(currency);
return new LoanScheduleParams(periodNumber, instalmentNumber,
loanTermInDays, periodStartDate, actualRepaymentDate,
- totalCumulativePrincipal, totalCumulativeInterest,
totalFeeChargesCharged, totalPenaltyChargesCharged,
- totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
principalPortionMap, latePaymentMap,
- compoundingMap, unCompoundedAmount, disburseDetailMap,
principalToBeScheduled, outstandingBalance,
- outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor,
- scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
+ originalRepaymentDate, totalCumulativePrincipal,
totalCumulativeInterest, totalFeeChargesCharged,
+ totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+ principalPortionMap, latePaymentMap, compoundingMap,
unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+ outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails,
+ loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
partialUpdate, currency, applyInterestRecalculation);
}
public int getPeriodNumber() {
@@ -390,6 +397,10 @@ public final class LoanScheduleParams {
this.actualRepaymentDate = actualRepaymentDate;
}
+ public void setOriginalRepaymentDate(LocalDate originalRepaymentDate) {
+ this.originalRepaymentDate = originalRepaymentDate;
+ }
+
public void setReducePrincipal(Money reducePrincipal) {
this.reducePrincipal = reducePrincipal;
}
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 4f906e76e..4894cc06f 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
@@ -2129,6 +2129,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
lastRestDate =
getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms,
holidayDetailDTO);
}
+
LocalDate actualRepaymentDate =
loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
@@ -2144,6 +2145,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
// Actual period Number plus interest only repayments
int instalmentNumber = 1;
LocalDate lastInstallmentDate = actualRepaymentDate;
+ LocalDate originalRepaymentDate = lastInstallmentDate;
LocalDate periodStartDate =
loanApplicationTerms.getExpectedDisbursementDate();
// Set fixed Amortization Amounts(either EMI or Principal )
updateAmortization(mc, loanApplicationTerms, periodNumber,
outstandingBalance);
@@ -2193,15 +2195,19 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
actualRepaymentDate =
this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
loanApplicationTerms, isFirstRepayment);
if (actualRepaymentDate.isAfter(rescheduleFrom) ||
actualRepaymentDate.isEqual(rescheduleFrom)) {
- actualRepaymentDate = lastInstallmentDate;
+ actualRepaymentDate = originalRepaymentDate;
}
isFirstRepayment = false;
LocalDate prevLastInstDate = lastInstallmentDate;
+ originalRepaymentDate =
actualRepaymentDate.plusMonths(1);
lastInstallmentDate = this.scheduledDateGenerator
.adjustRepaymentDate(actualRepaymentDate,
loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
LocalDate modifiedLastInstDate = null;
LoanTermVariationsData variation1 = null;
+
+ boolean hasDueDateVariation = false;
while
(loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate))
{
+ hasDueDateVariation = true;
LoanTermVariationsData variation =
loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
modifiedLastInstDate =
variation.getDateValue();
@@ -2209,7 +2215,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
}
}
- if
(!lastInstallmentDate.isEqual(installment.getDueDate())
+ if (hasDueDateVariation &&
!lastInstallmentDate.isEqual(installment.getDueDate())
&&
!installment.getDueDate().equals(modifiedLastInstDate)) {
lastInstallmentDate = prevLastInstDate;
actualRepaymentDate = lastInstallmentDate;
@@ -2347,8 +2353,8 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
if (!newRepaymentScheduleInstallments.isEmpty() &&
totalCumulativeInterest.isGreaterThanZero()) {
Money totalOutstandingInterestPaymentDueToGrace =
Money.zero(currency);
loanScheduleParams =
LoanScheduleParams.createLoanScheduleParamsForPartialUpdate(periodNumber,
instalmentNumber,
- loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest,
- totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected,
+ loanTermInDays, periodStartDate, actualRepaymentDate,
originalRepaymentDate, totalCumulativePrincipal,
+ totalCumulativeInterest, totalFeeChargesCharged,
totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstandingInterestPaymentDueToGrace,
reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
uncompoundedAmount, disburseDetailMap,
principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
newRepaymentScheduleInstallments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate,
@@ -2356,6 +2362,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
retainedInstallments.addAll(newRepaymentScheduleInstallments);
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
loanApplicationTerms.updateTotalInterestDue(Money.of(currency,
loan.getLoanSummary().getTotalInterestCharged()));
+
loanScheduleParams.setOriginalRepaymentDate(originalRepaymentDate);
} else {
loanApplicationTerms.getLoanTermVariations().resetVariations();
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index b5324799b..22bae64a3 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -32,6 +32,7 @@ import io.restassured.specification.ResponseSpecification;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DecimalFormat;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -45,8 +46,10 @@ import java.util.Map;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.WorkingDaysHelper;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
@@ -4602,6 +4605,20 @@ public class ClientLoanIntegrationTest {
recalculationRestFrequencyOnDayType,
recalculationRestFrequencyDayOfWeekType);
}
+ private Integer
createLoanProductWithInterestRecalculationAndCompoundingDetails(final String
repaymentStrategy,
+ final String interestRecalculationCompoundingMethod, final String
rescheduleStrategyMethod,
+ final String recalculationRestFrequencyType, final String
preCloseInterestCalculationStrategy, final Account[] accounts,
+ final String installmentMultipleOf) {
+ final String recalculationCompoundingFrequencyType = null;
+ final String recalculationCompoundingFrequencyInterval = null;
+ final Integer recalculationCompoundingFrequencyOnDayType = null;
+ final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
+ return createLoanProductWithInterestRecalculation(repaymentStrategy,
interestRecalculationCompoundingMethod,
+ rescheduleStrategyMethod,
recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval,
+ preCloseInterestCalculationStrategy, accounts, null, false,
recalculationCompoundingFrequencyOnDayType,
+ recalculationCompoundingFrequencyDayOfWeekType,
installmentMultipleOf);
+ }
+
private Integer createLoanProductWithInterestRecalculation(final String
repaymentStrategy,
final String interestRecalculationCompoundingMethod, final String
rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String
recalculationRestFrequencyInterval,
@@ -4636,6 +4653,37 @@ public class ClientLoanIntegrationTest {
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
+ private Integer createLoanProductWithInterestRecalculation(final String
repaymentStrategy,
+ final String interestRecalculationCompoundingMethod, final String
rescheduleStrategyMethod,
+ final String recalculationCompoundingFrequencyType, final String
recalculationCompoundingFrequencyInterval,
+ final String preCloseInterestCalculationStrategy, final Account[]
accounts, final String chargeId,
+ boolean isArrearsBasedOnOriginalSchedule, final Integer
recalculationCompoundingFrequencyOnDayType,
+ final Integer recalculationCompoundingFrequencyDayOfWeekType,
final String installmentsMultiplesOf) {
+ LOG.info("------------------------------CREATING NEW LOAN PRODUCT
---------------------------------------");
+ LoanProductTestBuilder builder = new
LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12")
+
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9")
+
.withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments()
+
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod,
rescheduleStrategyMethod,
+ preCloseInterestCalculationStrategy)
+
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod,
rescheduleStrategyMethod,
+ preCloseInterestCalculationStrategy)
+
.withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+ recalculationCompoundingFrequencyInterval,
recalculationCompoundingFrequencyOnDayType,
+ recalculationCompoundingFrequencyDayOfWeekType)
+
.withDefineInstallmentAmount(true).withInstallmentAmountInMultiplesOf(installmentsMultiplesOf);
+ if (accounts != null) {
+ builder = builder.withAccountingRulePeriodicAccrual(accounts);
+ }
+
+ if (isArrearsBasedOnOriginalSchedule) {
+ builder = builder.withArrearsConfiguration();
+ }
+
+ final String loanProductJSON = builder.build(chargeId);
+ return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
private Integer applyForLoanApplicationForInterestRecalculation(final
Integer clientID, final Integer loanProductID,
final String disbursementDate, final String repaymentStrategy,
final List<HashMap> charges) {
return applyForLoanApplicationForInterestRecalculation(clientID,
loanProductID, disbursementDate, repaymentStrategy, charges, null,
@@ -4689,6 +4737,37 @@ public class ClientLoanIntegrationTest {
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
+ private Integer applyForLoanApplicationForInterestRecalculation(final
Integer clientID, final Integer loanProductID,
+ final String disbursementDate, final String repaymentStrategy,
final String firstRepaymentDate) {
+ LOG.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ final Integer collateralId =
CollateralManagementHelper.createCollateralProduct(this.requestSpec,
this.responseSpec);
+ Assertions.assertNotNull(collateralId);
+ List<HashMap> collaterals = new ArrayList<>();
+
+ final Integer clientCollateralId =
CollateralManagementHelper.createClientCollateral(this.requestSpec,
this.responseSpec,
+ String.valueOf(clientID), collateralId);
+ Assertions.assertNotNull(clientCollateralId);
+ addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+ final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+ .withPrincipal("10000.00") //
+ .withLoanTermFrequency("12") //
+ .withLoanTermFrequencyAsMonths() //
+ .withNumberOfRepayments("12") //
+ .withRepaymentEveryAfter("1") //
+ .withLoanTermFrequencyAsMonths() //
+ .withInterestRatePerPeriod("19.9") //
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsDecliningBalance() //
+ .withInterestCalculationPeriodTypeAsDays() //
+ .withExpectedDisbursementDate(disbursementDate) //
+ .withSubmittedOnDate(disbursementDate) //
+
.withwithRepaymentStrategy(repaymentStrategy).withRepaymentFrequencyTypeAsMonths()//
+
.withFirstRepaymentDate(firstRepaymentDate).withCollaterals(collaterals)
+ .build(clientID.toString(), loanProductID.toString(), null);
+ return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ }
+
private void verifyLoanRepaymentSchedule(final ArrayList<HashMap>
loanSchedule, List<Map<String, Object>> expectedvalues) {
int index = 1;
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, index);
@@ -5673,6 +5752,210 @@ public class ClientLoanIntegrationTest {
assertEquals(clientCollateralId, clientCollateralIdResult);
}
+ @Test
+ public void
testLoanScheduleWithInterestRecalculationMakePrepaymentAfterRepayment() {
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
+ Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ Calendar currentDate =
Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ startDate.add(Calendar.MONTH, -8);
+
+ Calendar firstRepaymentDate = (Calendar) startDate.clone();
+ firstRepaymentDate.add(Calendar.MONTH, 1);
+ firstRepaymentDate.add(Calendar.DAY_OF_MONTH,
firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) -
Calendar.DAY_OF_MONTH);
+ String firstRepayment =
dateFormat.format(firstRepaymentDate.getTime());
+
+ final String loanDisbursementDate =
dateFormat.format(startDate.getTime());
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec,
this.responseSpec, clientID);
+ final Integer loanProductID =
createLoanProductWithInterestRecalculationAndCompoundingDetails(
+
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+ LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
"12");
+
+ final Integer loanID =
applyForLoanApplicationForInterestRecalculation(clientID, loanProductID,
loanDisbursementDate,
+
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
firstRepayment);
+
+ Assertions.assertNotNull(loanID);
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ LOG.info("-----------------------------------APPROVE
LOAN-----------------------------------------");
+ loanStatusHashMap =
this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ LOG.info("-------------------------------DISBURSE
LOAN-------------------------------------------");
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate,
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ ArrayList<HashMap> loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+ startDate.add(Calendar.DAY_OF_MONTH, 2);
+ String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+ //
+ Float earlyPayment = Float.parseFloat("3000");
+ this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate,
earlyPayment, loanID);
+
+ HashMap prepayDetail =
this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec,
loanID);
+ String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+ String loanPrepaymentDate = dateFormat.format(currentDate.getTime());
+ this.loanTransactionHelper.makeRepayment(loanPrepaymentDate,
Float.parseFloat(prepayAmount), loanID);
+ loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+ }
+
+ @Test
+ public void
testLoanScheduleWithInterestRecalculationMakeAdvancePaymentTillSettlement() {
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ final ResponseSpecification errorResponse = new
ResponseSpecBuilder().expectStatusCode(403).build();
+ final LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(this.requestSpec, errorResponse);
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
+ Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ Calendar currentDate =
Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ startDate.add(Calendar.MONTH, -8);
+
+ Calendar firstRepaymentDate = (Calendar) startDate.clone();
+ firstRepaymentDate.add(Calendar.MONTH, 1);
+ firstRepaymentDate.add(Calendar.DAY_OF_MONTH,
firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) -
Calendar.DAY_OF_MONTH);
+ String firstRepayment =
dateFormat.format(firstRepaymentDate.getTime());
+
+ final String loanDisbursementDate =
dateFormat.format(startDate.getTime());
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec,
this.responseSpec, clientID);
+ final Integer loanProductID =
createLoanProductWithInterestRecalculationAndCompoundingDetails(
+
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+ LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
"12");
+
+ final Integer loanID =
applyForLoanApplicationForInterestRecalculation(clientID, loanProductID,
loanDisbursementDate,
+
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
firstRepayment);
+
+ Assertions.assertNotNull(loanID);
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ LOG.info("-----------------------------------APPROVE
LOAN-----------------------------------------");
+ loanStatusHashMap =
this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ LOG.info("-------------------------------DISBURSE
LOAN-------------------------------------------");
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate,
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ ArrayList<HashMap> loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+ Calendar repaymentDate = (Calendar) firstRepaymentDate.clone();
+ startDate.add(Calendar.DAY_OF_MONTH, 2);
+ String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+ //
+ Float earlyPayment = Float.parseFloat("3000");
+ String retrieveDueDate = null;
+ Float amount = null;
+ this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate,
earlyPayment, loanID);
+ for (int i = 1; i < loanSchedule.size(); i++) {
+
+ retrieveDueDate = dateFormat.format(repaymentDate.getTime());
+ amount = (Float) loanSchedule.get(i).get("principalOriginalDue") +
(Float) loanSchedule.get(i).get("interestOriginalDue");
+ if (currentDate.after(repaymentDate)) {
+ this.loanTransactionHelper.makeRepayment(retrieveDueDate,
amount, loanID);
+ } else {
+ break;
+ }
+ repaymentDate.add(Calendar.MONTH, 1);
+ }
+ HashMap savingsAccountErrorData =
validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID);
+ ArrayList<HashMap> error = (ArrayList<HashMap>)
savingsAccountErrorData.get("errors");
+ assertEquals("error.msg.loan.transaction.cannot.be.a.future.date",
error.get(0).get("userMessageGlobalisationCode"));
+ }
+
+ @Test
+ public void testLoanScheduleWithInterestRecalculationAfterLatePayment() {
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ WorkingDaysHelper.updateWorkingDaysWeekDays(this.requestSpec,
this.responseSpec);
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
+
+ final String loanDisbursementDate = "28 January 2021";
+ String firstRepayment = "01 March 2021";
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec,
this.responseSpec, clientID);
+ final Integer loanProductID =
createLoanProductWithInterestRecalculationAndCompoundingDetails(
+
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+ LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
"12");
+
+ final Integer loanID =
applyForLoanApplicationForInterestRecalculation(clientID, loanProductID,
loanDisbursementDate,
+
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
firstRepayment);
+
+ Assertions.assertNotNull(loanID);
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ LOG.info("-----------------------------------APPROVE
LOAN-----------------------------------------");
+ loanStatusHashMap =
this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ LOG.info("-------------------------------DISBURSE
LOAN-------------------------------------------");
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate,
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ ArrayList<HashMap> loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+
+ List<Map<String, Object>> expectedvalues = new ArrayList<>();
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("01
March 2021"), 0, false, "388.11", "1831.89", "0.0", "0.0");
+ this.loanTransactionHelper.makeRepayment("01 March 2021", 2220.0F,
loanID);
+
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("01
April 2021"), 0, false, "270.55", "1949.45", "0.0", "0.0");
+ this.loanTransactionHelper.makeRepayment("01 April 2021", 2220.0F,
loanID);
+
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("03 May
2021"), 0, false, "264.31", "1955.69", "0.0", "0.0");
+ this.loanTransactionHelper.makeRepayment("04 May 2021", 2220.0F,
loanID);
+
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May
2021"), 0, false, "0.0", "61.12", "0.0", "0.0");
+ this.loanTransactionHelper.makeRepayment("01 June 2021", 2220.0F,
loanID);
+
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("01
June 2021"), 0, false, "496.07", "1662.81", "0.0", "0.0");
+ addRepaymentValues(expectedvalues, convertStringDateToCalender("01
July 2021"), 0, false, "535.78", "1684.22", "0.0", "0.0");
+ loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+ verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+ WorkingDaysHelper.updateWorkingDays(this.requestSpec,
this.responseSpec);
+ }
+
+ private Calendar convertStringDateToCalender(final String stringDate) {
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ Calendar date = Calendar.getInstance();
+ try {
+ Date date1 = dateFormat.parse(stringDate);
+ date.setTime(date1);
+
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ return date;
+ }
+
private void validateIfValuesAreNotOverridden(Integer loanID, Integer
loanProductID) {
String loanProductDetails =
this.loanTransactionHelper.getLoanProductDetails(this.requestSpec,
this.responseSpec, loanProductID);
String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 7f0e0f8b8..ecd5aa2e7 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -126,6 +126,7 @@ public class LoanProductTestBuilder {
private boolean syncExpectedWithDisbursementDate = false;
private String fixedPrincipalPercentagePerInstallment;
private String installmentAmountInMultiplesOf;
+ private boolean canDefineInstallmentAmount;
public String build(final String chargeId) {
final HashMap<String, Object> map = new HashMap<>();
@@ -163,6 +164,9 @@ public class LoanProductTestBuilder {
if (this.minimumDaysBetweenDisbursalAndFirstRepayment != null) {
map.put("minimumDaysBetweenDisbursalAndFirstRepayment",
this.minimumDaysBetweenDisbursalAndFirstRepayment);
}
+ if (this.canDefineInstallmentAmount) {
+ map.put("canDefineInstallmentAmount",
this.canDefineInstallmentAmount);
+ }
if (multiDisburseLoan) {
map.put("multiDisburseLoan", this.multiDisburseLoan);
map.put("maxTrancheCount", this.maxTrancheCount);
@@ -443,6 +447,11 @@ public class LoanProductTestBuilder {
return this;
}
+ public LoanProductTestBuilder withDefineInstallmentAmount(final boolean
canDefineInstallmentAmount) {
+ this.canDefineInstallmentAmount = canDefineInstallmentAmount;
+ return this;
+ }
+
public LoanProductTestBuilder withInterestRecalculationDetails(final
String interestRecalculationCompoundingMethod,
final String rescheduleStrategyMethod, String
preCloseInterestCalculationStrategy) {
this.isInterestRecalculationEnabled = true;
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 85527944d..36fbfdca2 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -298,6 +298,11 @@ public class LoanTransactionHelper {
return
performLoanTransaction(createGlimAccountURL(DISBURSE_LOAN_COMMAND, glimID),
getDisbursementAsJSON(date));
}
+ public HashMap disburseLoanWithNetDisbursalAmount(final String date, final
Integer loanID, final String netDisbursalAmount) {
+ return
performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
+ getDisburseLoanAsJSON(date, null, netDisbursalAmount));
+ }
+
public HashMap undoDisburseGlimAccount(final Integer glimID) {
LOG.info("--------------------------------- UNDO DISBURSAL GLIM
APPLICATION -------------------------------");
final String undoBodyJson = "{'note':'UNDO DISBURSAL'}";