This is an automated email from the ASF dual-hosted git repository.
avikg 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 7c235c5
FINERACT-1443-loan-interest-recover-policy-fix-when-interest-more-than-EMI
(#2033)
7c235c5 is described below
commit 7c235c583b5fe2b85f2b4cbd8a53f59c05d0fc46
Author: Manoj <[email protected]>
AuthorDate: Fri Feb 18 23:52:51 2022 +0530
FINERACT-1443-loan-interest-recover-policy-fix-when-interest-more-than-EMI
(#2033)
---
.../domain/ConfigurationDomainService.java | 6 +-
.../domain/ConfigurationDomainServiceJpa.java | 9 +-
.../loanaccount/data/ScheduleGeneratorDTO.java | 17 ++-
.../portfolio/loanaccount/domain/Loan.java | 5 +-
.../domain/AbstractLoanScheduleGenerator.java | 70 ++---------
...liningBalanceInterestLoanScheduleGenerator.java | 48 ++++----
.../domain/DefaultScheduledDateGenerator.java | 18 +++
.../loanschedule/domain/LoanApplicationTerms.java | 63 +++++++---
.../loanschedule/domain/PrincipalInterest.java | 9 ++
.../domain/ScheduledDateGenerator.java | 3 +
.../service/LoanScheduleAssembler.java | 8 +-
.../loanaccount/service/LoanUtilService.java | 8 +-
.../V392__interest_recovery_conf_for_rescedule.sql | 29 +++++
.../LoanRescheduleOnDecliningBalanceLoanTest.java | 130 ++++++++++++++++++---
.../common/GlobalConfigurationHelper.java | 28 +++--
.../common/loans/LoanApplicationTestBuilder.java | 7 ++
16 files changed, 310 insertions(+), 148 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 8a2a726..037f4d6 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -75,6 +75,10 @@ public interface ConfigurationDomainService {
boolean isSkippingMeetingOnFirstDayOfMonthEnabled();
+ boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI();
+
+ boolean isPrincipalCompoundingDisabledForOverdueLoans();
+
Long retreivePeroidInNumberOfDaysForSkipMeetingDate();
boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled();
@@ -97,8 +101,6 @@ public interface ConfigurationDomainService {
boolean isFirstRepaymentDateAfterRescheduleAllowedOnHoliday();
- boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI();
-
String getAccountMappingForPaymentType();
String getAccountMappingForCharge();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 6b6c595..30426ef 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -259,8 +259,13 @@ public class ConfigurationDomainServiceJpa implements
ConfigurationDomainService
}
@Override
- public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI() {
- return
getGlobalConfigurationPropertyData("is-interest-to-be-appropriated-equally-when-greater-than-emi").isEnabled();
+ public boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI() {
+ return
getGlobalConfigurationPropertyData("is-interest-to-be-recovered-first-when-greater-than-emi").isEnabled();
+ }
+
+ @Override
+ public boolean isPrincipalCompoundingDisabledForOverdueLoans() {
+ return
getGlobalConfigurationPropertyData("is-principal-compounding-disabled-for-overdue-loans").isEnabled();
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
index 4d609bf..8fc3371 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
@@ -44,7 +44,8 @@ public class ScheduleGeneratorDTO {
final boolean isSkipRepaymentOnFirstDayofMonth;
final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
final boolean isFirstRepaymentDateAllowedOnHoliday;
- final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI;
+ final boolean isPrincipalCompoundingDisabledForOverdueLoans;
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory
loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final
HolidayDetailDTO holidayDetailDTO,
@@ -53,7 +54,8 @@ public class ScheduleGeneratorDTO {
final Calendar calendar, final CalendarHistoryDataWrapper
calendarHistoryDataWrapper,
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled,
final Integer numberOfdays,
final boolean isSkipRepaymentOnFirstDayofMonth, final Boolean
isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled,
- final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean
isInterestToBeAppropriatedEquallyWhenGreaterThanEMI) {
+ final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean
isInterestToBeRecoveredFirstWhenGreaterThanEMI,
+ final boolean isPrincipalCompoundingDisabledForOverdueLoans) {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency;
@@ -71,7 +73,8 @@ public class ScheduleGeneratorDTO {
this.isSkipRepaymentOnFirstDayofMonth =
isSkipRepaymentOnFirstDayofMonth;
this.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled =
isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
this.isFirstRepaymentDateAllowedOnHoliday =
isFirstRepaymentDateAllowedOnHoliday;
- this.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI =
isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ this.isInterestToBeRecoveredFirstWhenGreaterThanEMI =
isInterestToBeRecoveredFirstWhenGreaterThanEMI;
+ this.isPrincipalCompoundingDisabledForOverdueLoans =
isPrincipalCompoundingDisabledForOverdueLoans;
}
public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
@@ -150,7 +153,11 @@ public class ScheduleGeneratorDTO {
return isFirstRepaymentDateAllowedOnHoliday;
}
- public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI() {
- return isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ public boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI() {
+ return isInterestToBeRecoveredFirstWhenGreaterThanEMI;
+ }
+
+ public boolean isPrincipalCompoundingDisabledForOverdueLoans() {
+ return isPrincipalCompoundingDisabledForOverdueLoans;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 104ddea..0cfe9b5 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -5623,7 +5623,8 @@ public class Loan extends AbstractPersistableCustom {
rescheduleStrategyMethod, calendar, getApprovedPrincipal(),
annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper,
scheduleGeneratorDTO.getNumberOfdays(),
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(),
holidayDetailDTO, allowCompoundingOnEod,
scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(),
-
scheduleGeneratorDTO.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI(),
this.fixedPrincipalPercentagePerInstallment);
+
scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(),
this.fixedPrincipalPercentagePerInstallment,
+
scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans());
return loanApplicationTerms;
}
@@ -5905,7 +5906,7 @@ public class Loan extends AbstractPersistableCustom {
compoundingCalendarInstance, compoundingFrequencyType,
this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, loanCalendar,
getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper, numberofdays,
isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod, false,
- false, this.fixedPrincipalPercentagePerInstallment);
+ false, this.fixedPrincipalPercentagePerInstallment, false);
}
/**
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 94a6f59..fd008cf 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
@@ -286,8 +286,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
// will check for EMI amount greater than interest calculated
if (loanApplicationTerms.getFixedEmiAmount() != null
- &&
loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount())
< 0
- &&
!loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled())
{
+ &&
loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount())
< 0) {
String errorMsg = "EMI amount must be greater than : " +
principalInterestForThisPeriod.interest().getAmount();
throw new MultiDisbursementEmiAmountException(errorMsg,
principalInterestForThisPeriod.interest().getAmount(),
loanApplicationTerms.getFixedEmiAmount());
@@ -333,7 +332,9 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
scheduleParams.getOutstandingBalance(),
currentPeriodParams.getInterestForThisPeriod(),
currentPeriodParams.getFeeChargesForInstallment(),
currentPeriodParams.getPenaltyChargesForInstallment(),
totalInstallmentDue, !isCompletePeriod);
-
+ if (principalInterestForThisPeriod.getRescheduleInterestPortion()
!= null) {
+
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
+ }
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(),
installment);
// apply loan transactions on installments to identify early/late
// payments for interest recalculation
@@ -379,44 +380,6 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(Money.zero(currency));
}
- if
(loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
- && loanApplicationTerms.getInterestTobeApproppriated() != null
- &&
loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero()) {
- int emisTobeChanged = 1;
- for (LoanScheduleModelPeriod installment :
(List<LoanScheduleModelPeriod>) periods) {
- if (!installment.isEMIFixedSpecificToInstallment()) {
- emisTobeChanged++;
- }
- }
- if (emisTobeChanged > 1) {
- emisTobeChanged--;
- }
- Money interestTobeApproppriated =
loanApplicationTerms.getInterestTobeApproppriated();
- Money interestFraction =
interestTobeApproppriated.dividedBy(emisTobeChanged, mc.getRoundingMode());
- BigDecimal roundFraction =
interestFraction.getAmount().remainder(BigDecimal.ONE);
- interestFraction = interestFraction.minus(roundFraction);
- for (LoanScheduleModelPeriod installment :
(List<LoanScheduleModelPeriod>) periods) {
- if (!installment.isEMIFixedSpecificToInstallment()) {
- installment.addInterestAmount(interestFraction);
-
installment.setRescheduleInterestPortion(interestFraction.getAmount());
- interestTobeApproppriated =
interestTobeApproppriated.minus(interestFraction);
- }
- }
- LoanScheduleModelPeriod lastInstallment =
((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
-
- if (interestTobeApproppriated.isGreaterThanZero()) {
- lastInstallment.addInterestAmount(interestTobeApproppriated);
- BigDecimal rescheduleInterestPortion = BigDecimal.ZERO;
- if (lastInstallment.rescheduleInterestPortion() != null) {
- rescheduleInterestPortion =
lastInstallment.rescheduleInterestPortion();
- }
-
lastInstallment.setRescheduleInterestPortion(rescheduleInterestPortion.add(interestTobeApproppriated.getAmount()));
- }
-
scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated());
-
scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated());
-
loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency));
- }
-
// determine fees and penalties for charges which depends on total
// loan interest
updatePeriodsWithCharges(currency, scheduleParams, periods,
nonCompoundingCharges);
@@ -1054,6 +1017,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
modifiedScheduledDueDate =
loanTermVariationsData.getDateValue();
if (!loanTermVariationsData.isSpecificToInstallment()) {
scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate);
+
loanApplicationTerms.setNewScheduledDueDateStart(modifiedScheduledDueDate);
}
loanTermVariationsData.setProcessed(true);
}
@@ -2397,23 +2361,11 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
periods.clear();
}
- BigDecimal rescheuleInterestPortionTobeRetained = BigDecimal.ZERO;
- for (LoanRepaymentScheduleInstallment retainedInstallment :
retainedInstallments) {
- if (retainedInstallment.getRescheduleInterestPortion() != null) {
- rescheuleInterestPortionTobeRetained =
rescheuleInterestPortionTobeRetained
-
.add(retainedInstallment.getRescheduleInterestPortion());
- }
- }
-
- BigDecimal rescheuleInterestPortionTotal = BigDecimal.ZERO;
- for (LoanRepaymentScheduleInstallment inst :
loan.getRepaymentScheduleInstallments()) {
- if (inst.getRescheduleInterestPortion() != null) {
- rescheuleInterestPortionTotal =
rescheuleInterestPortionTotal.add(inst.getRescheduleInterestPortion());
- }
+ if (retainedInstallments.size() > 0
+ && retainedInstallments.get(retainedInstallments.size() -
1).getRescheduleInterestPortion() != null) {
+ loanApplicationTerms.setInterestTobeApproppriated(
+ Money.of(loan.getCurrency(),
retainedInstallments.get(retainedInstallments.size() -
1).getRescheduleInterestPortion()));
}
-
- BigDecimal rescheuleInterestPortionTobeAppropriated =
rescheuleInterestPortionTotal.subtract(rescheuleInterestPortionTobeRetained);
-
loanApplicationTerms.setInterestTobeApproppriated(Money.of(loan.getCurrency(),
rescheuleInterestPortionTobeAppropriated));
LoanScheduleModel loanScheduleModel = generate(mc,
loanApplicationTerms, loan.charges(), holidayDetailDTO, loanScheduleParams);
for (LoanScheduleModelPeriod loanScheduleModelPeriod :
loanScheduleModel.getPeriods()) {
@@ -2640,8 +2592,8 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
final HolidayDetailDTO holidayDetailDTO) {
LocalDate nextScheduleDate = null;
if
(loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
- nextScheduleDate =
this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate,
loanApplicationTerms,
- holidayDetailDTO);
+ nextScheduleDate =
this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate,
+ loanApplicationTerms, holidayDetailDTO);
} else {
CalendarInstance calendarInstance =
loanApplicationTerms.getRestCalendarInstance();
nextScheduleDate =
CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
index a120fa8..c391125 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
@@ -113,7 +113,11 @@ public class DecliningBalanceInterestLoanScheduleGenerator
extends AbstractLoanS
compoundFee = compoundingMap.get(principal.getKey());
compoundingMap.put(principal.getKey(),
interestToBeCompounded.plus(compoundFee));
}
- balanceForInterestCalculation =
balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee);
+ if
(!loanApplicationTerms.isPrincipalCompoundingDisabledForOverdueLoans()) {
+ balanceForInterestCalculation =
balanceForInterestCalculation.plus(principal.getValue());
+ }
+ balanceForInterestCalculation =
balanceForInterestCalculation.plus(compoundFee);
+
if (interestRates.containsKey(principal.getKey())) {
loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey()));
}
@@ -128,42 +132,27 @@ public class
DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
interestForThisInstallment =
interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
- Money interestTobeApproppriated =
loanApplicationTerms.getInterestTobeApproppriated() == null
- ? Money.zero(interestForThisInstallment.getCurrency())
- : loanApplicationTerms.getInterestTobeApproppriated();
-
- if (loanApplicationTerms.getFixedEmiAmount() != null
- &&
loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
&& interestForThisInstallment
-
.isGreaterThan(Money.of(interestForThisInstallment.getCurrency(),
loanApplicationTerms.getFixedEmiAmount()))) {
- LocalDate actualPeriodEndDate =
this.scheduledDateGenerator.generateNextRepaymentDate(interestStartDate,
loanApplicationTerms,
- false);
- PrincipalInterest tempInterest =
loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
- interestCalculationGraceOnRepaymentPeriodFraction,
periodNumber, mc, cumulatingInterestDueToGrace,
- balanceForInterestCalculation, interestStartDate,
actualPeriodEndDate);
-
- Money fixedEmi =
Money.of(interestForThisInstallment.getCurrency(),
loanApplicationTerms.getFixedEmiAmount());
-
- if (tempInterest.interest().isGreaterThan(fixedEmi)) {
- loanApplicationTerms
-
.setInterestTobeApproppriated(interestTobeApproppriated.plus(interestForThisInstallment.minus(fixedEmi)));
- interestForThisInstallment = fixedEmi;
- } else {
- loanApplicationTerms.setInterestTobeApproppriated(
-
interestTobeApproppriated.plus(interestForThisInstallment.minus(tempInterest.interest())));
- interestForThisInstallment = tempInterest.interest();
- }
+ if
(loanApplicationTerms.isInterestToBeRecoveredFirstWhenGreaterThanEMIEnabled()
+ && loanApplicationTerms.isInterestTobeApproppriated()) {
+ interestForThisInstallment =
interestForThisInstallment.add(loanApplicationTerms.getInterestTobeApproppriated());
+
loanApplicationTerms.setInterestTobeApproppriated(interestForThisInstallment.zero());
}
- cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
-
Money interestForPeriod = interestForThisInstallment;
if (interestForPeriod.isGreaterThanZero()) {
interestForPeriod =
interestForPeriod.minus(cumulatingInterestPaymentDueToGrace);
} else {
interestForPeriod =
cumulatingInterestDueToGrace.minus(cumulatingInterestPaymentDueToGrace);
}
+
Money principalForThisInstallment =
loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
outstandingBalance,
periodNumber, mc, interestForPeriod);
+ if
(loanApplicationTerms.isInterestToBeRecoveredFirstWhenGreaterThanEMIEnabled()
&& principalForThisInstallment.isLessThanZero()
+ && !loanApplicationTerms.isLastRepaymentPeriod(periodNumber)) {
+
loanApplicationTerms.setInterestTobeApproppriated(principalForThisInstallment.abs());
+ interestForThisInstallment =
interestForThisInstallment.minus(loanApplicationTerms.getInterestTobeApproppriated());
+ principalForThisInstallment = principalForThisInstallment.zero();
+ }
// update cumulative fields for principal & interest
final Money interestBroughtFowardDueToGrace =
cumulatingInterestDueToGrace;
@@ -173,6 +162,9 @@ public class DecliningBalanceInterestLoanScheduleGenerator
extends AbstractLoanS
principalForThisInstallment =
loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
totalCumulativePrincipalToDate, periodNumber);
- return new PrincipalInterest(principalForThisInstallment,
interestForThisInstallment, interestBroughtFowardDueToGrace);
+ PrincipalInterest principalInterest = new
PrincipalInterest(principalForThisInstallment, interestForThisInstallment,
+ interestBroughtFowardDueToGrace);
+
principalInterest.setRescheduleInterestPortion(loanApplicationTerms.getInterestTobeApproppriated());
+ return principalInterest;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index d2853d4..584de8c 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -327,4 +327,22 @@ public class DefaultScheduledDateGenerator implements
ScheduledDateGenerator {
generatedDate = adjustRepaymentDate(generatedDate,
loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return generatedDate;
}
+
+ @Override
+ public LocalDate
generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate
lastRepaymentDate,
+ LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO
holidayDetailDTO) {
+
+ LocalDate generatedDate =
loanApplicationTerms.getExpectedDisbursementDate();
+ boolean isFirstRepayment = true;
+ if (loanApplicationTerms.getNewScheduledDueDateStart() != null) {
+ generatedDate = loanApplicationTerms.getNewScheduledDueDateStart();
+ isFirstRepayment = false;
+ }
+ while (!generatedDate.isAfter(lastRepaymentDate)) {
+ generatedDate = generateNextRepaymentDate(generatedDate,
loanApplicationTerms, isFirstRepayment);
+ isFirstRepayment = false;
+ }
+ generatedDate = adjustRepaymentDate(generatedDate,
loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
+ return generatedDate;
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 6386d37..11a344b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -188,7 +188,9 @@ public final class LoanApplicationTerms {
private final boolean isFirstRepaymentDateAllowedOnHoliday;
- private final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ private final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI;
+
+ private boolean isPrincipalCompoundingDisabledForOverdueLoans;
private final HolidayDetailDTO holidayDetailDTO;
@@ -209,6 +211,8 @@ public final class LoanApplicationTerms {
private Money interestTobeApproppriated;
private final BigDecimal fixedPrincipalPercentagePerInstallment;
+ private LocalDate newScheduledDueDateStart;
+
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency
currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final
Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer
nthDay, DayOfWeekType weekDayType,
@@ -230,7 +234,8 @@ public final class LoanApplicationTerms {
BigDecimal approvedAmount, List<LoanTermVariationsData>
loanTermVariations,
Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final
Integer numberOfDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO
holidayDetailDTO, final boolean allowCompoundingOnEod,
- final boolean isEqualAmortization, final BigDecimal
fixedPrincipalPercentagePerInstallment) {
+ final boolean isEqualAmortization, final boolean
isInterestToBeRecoveredFirstWhenGreaterThanEMI,
+ final BigDecimal fixedPrincipalPercentagePerInstallment, final
boolean isPrincipalCompoundingDisabledForOverdueLoans) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -245,8 +250,9 @@ public final class LoanApplicationTerms {
recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf,
preClosureInterestCalculationStrategy, loanCalendar, approvedAmount,
loanTermVariations,
calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays,
- isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO,
allowCompoundingOnEod, isEqualAmortization, false, false,
- fixedPrincipalPercentagePerInstallment);
+ isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO,
allowCompoundingOnEod, isEqualAmortization, false,
+ isInterestToBeRecoveredFirstWhenGreaterThanEMI,
fixedPrincipalPercentagePerInstallment,
+ isPrincipalCompoundingDisabledForOverdueLoans);
}
@@ -273,7 +279,7 @@ public final class LoanApplicationTerms {
compoundingMethod, compoundingCalendarInstance,
compoundingFrequencyType, loanPreClosureInterestCalculationStrategy,
rescheduleStrategyMethod, loanCalendar, approvedAmount,
annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper, numberOfDays,
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod,
false,
- false, null);
+ false, null, false);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency
applicationCurrency, final Integer loanTermFrequency,
@@ -291,8 +297,8 @@ public final class LoanApplicationTerms {
BigDecimal annualNominalInterestRate, final
List<LoanTermVariationsData> loanTermVariations,
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final
Integer numberOfDays,
final boolean isSkipRepaymentOnFirstDayOfMonth, final
HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod,
- final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean
isInterestToBeAppropriatedEquallyWhenGreaterThanEMI,
- final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean
isInterestToBeRecoveredFirstWhenGreaterThanEMI,
+ final BigDecimal fixedPrincipalPercentagePerInstallment, final
boolean isPrincipalCompoundingDisabledForOverdueLoans) {
final Integer numberOfRepayments =
loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery =
loanProductRelatedDetail.getRepayEvery();
@@ -331,7 +337,8 @@ public final class LoanApplicationTerms {
loanPreClosureInterestCalculationStrategy, loanCalendar,
approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled,
numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO,
allowCompoundingOnEod, isEqualAmortization,
isFirstRepaymentDateAllowedOnHoliday,
- isInterestToBeAppropriatedEquallyWhenGreaterThanEMI,
fixedPrincipalPercentagePerInstallment);
+ isInterestToBeRecoveredFirstWhenGreaterThanEMI,
fixedPrincipalPercentagePerInstallment,
+ isPrincipalCompoundingDisabledForOverdueLoans);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency
applicationCurrency, final Integer loanTermFrequency,
@@ -390,7 +397,7 @@ public final class LoanApplicationTerms {
recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount,
loanTermVariations,
calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays,
- isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO,
allowCompoundingOnEod, isEqualAmortization, false, false, null);
+ isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO,
allowCompoundingOnEod, isEqualAmortization, false, false, null, false);
}
@@ -417,8 +424,8 @@ public final class LoanApplicationTerms {
applicationTerms.calendarHistoryDataWrapper,
applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled,
applicationTerms.numberOfDays,
applicationTerms.isSkipRepaymentOnFirstDayOfMonth,
applicationTerms.holidayDetailDTO,
applicationTerms.allowCompoundingOnEod,
applicationTerms.isEqualAmortization,
- applicationTerms.isFirstRepaymentDateAllowedOnHoliday,
applicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI,
- applicationTerms.fixedPrincipalPercentagePerInstallment);
+ applicationTerms.isFirstRepaymentDateAllowedOnHoliday,
applicationTerms.isInterestToBeRecoveredFirstWhenGreaterThanEMI,
+ applicationTerms.fixedPrincipalPercentagePerInstallment,
applicationTerms.isPrincipalCompoundingDisabledForOverdueLoans);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final
Integer loanTermFrequency,
@@ -443,7 +450,8 @@ public final class LoanApplicationTerms {
final CalendarHistoryDataWrapper calendarHistoryDataWrapper,
Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled,
final Integer numberOfDays, final boolean
isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO,
final boolean allowCompoundingOnEod, final boolean
isEqualAmortization, final boolean isFirstRepaymentDateAllowedOnHoliday,
- final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI,
final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
final BigDecimal fixedPrincipalPercentagePerInstallment,
+ final boolean isPrincipalCompoundingDisabledForOverdueLoans) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -517,8 +525,9 @@ public final class LoanApplicationTerms {
this.totalPrincipalAccounted = principal.zero();
this.isEqualAmortization = isEqualAmortization;
this.isFirstRepaymentDateAllowedOnHoliday =
isFirstRepaymentDateAllowedOnHoliday;
- this.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI =
isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ this.isInterestToBeRecoveredFirstWhenGreaterThanEMI =
isInterestToBeRecoveredFirstWhenGreaterThanEMI;
this.fixedPrincipalPercentagePerInstallment =
fixedPrincipalPercentagePerInstallment;
+ this.isPrincipalCompoundingDisabledForOverdueLoans =
isPrincipalCompoundingDisabledForOverdueLoans;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money
principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -743,6 +752,10 @@ public final class LoanApplicationTerms {
return periodNumber == numberOfRepayments;
}
+ public boolean isLastRepaymentPeriod(final int periodNumber) {
+ return periodNumber == this.actualNumberOfRepayments;
+ }
+
/**
* general method to calculate totalInterestDue discounting any grace
settings
*/
@@ -1248,7 +1261,7 @@ public final class LoanApplicationTerms {
Money interestDue = Money.zero(outstandingBalance.getCurrency());
final BigDecimal periodicInterestRate =
periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType,
- periodStartDate, periodEndDate);
+ periodStartDate, periodEndDate);// 0.021232877 ob:14911.64
interestDue =
outstandingBalance.multiplyRetainScale(periodicInterestRate,
mc.getRoundingMode());
return interestDue;
@@ -1334,7 +1347,6 @@ public final class LoanApplicationTerms {
private Money calculatePrincipalDueForInstallment(final int periodNumber,
final Money totalDuePerInstallment,
final Money periodInterest) {
-
Money principal = totalDuePerInstallment.minus(periodInterest);
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principal = principal.zero();
@@ -1795,15 +1807,30 @@ public final class LoanApplicationTerms {
}
public Money getInterestTobeApproppriated() {
- return interestTobeApproppriated;
+ return interestTobeApproppriated == null ? this.principal.zero() :
interestTobeApproppriated;
}
public void setInterestTobeApproppriated(Money interestTobeApproppriated) {
this.interestTobeApproppriated = interestTobeApproppriated;
}
- public boolean
isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled() {
- return isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+ public Boolean isInterestTobeApproppriated() {
+ return interestTobeApproppriated != null &&
interestTobeApproppriated.isGreaterThanZero();
+ }
+
+ public boolean isInterestToBeRecoveredFirstWhenGreaterThanEMIEnabled() {
+ return isInterestToBeRecoveredFirstWhenGreaterThanEMI;
+ }
+
+ public boolean isPrincipalCompoundingDisabledForOverdueLoans() {
+ return isPrincipalCompoundingDisabledForOverdueLoans;
}
+ public LocalDate getNewScheduledDueDateStart() {
+ return newScheduledDueDateStart;
+ }
+
+ public void setNewScheduledDueDateStart(LocalDate
newScheduledDueDateStart) {
+ this.newScheduledDueDateStart = newScheduledDueDateStart;
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
index 63c77aa..3a0e724 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PrincipalInterest.java
@@ -25,6 +25,7 @@ public class PrincipalInterest {
private final Money principal;
private final Money interest;
private final Money interestPaymentDueToGrace;
+ private Money rescheduleInterestPortion;
public PrincipalInterest(final Money principal, final Money interest,
final Money interestPaymentDueToGrace) {
this.principal = principal;
@@ -43,4 +44,12 @@ public class PrincipalInterest {
public Money interestPaymentDueToGrace() {
return this.interestPaymentDueToGrace;
}
+
+ public Money getRescheduleInterestPortion() {
+ return rescheduleInterestPortion;
+ }
+
+ public void setRescheduleInterestPortion(Money rescheduleInterestPortion) {
+ this.rescheduleInterestPortion = rescheduleInterestPortion;
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
index 969db0e..f2e6de2 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -43,4 +43,7 @@ public interface ScheduledDateGenerator {
LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate
lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
HolidayDetailDTO holidayDetailDTO);
+
+ LocalDate
generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate
lastRepaymentDate,
+ LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO
holidayDetailDTO);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index d41e0e3..205fb84 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -448,7 +448,10 @@ public class LoanScheduleAssembler {
Date.from(expectedDisbursementDate.atStartOfDay(ZoneId.systemDefault()).toInstant()),
HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled,
holidays, workingDays);
-
+ final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI =
this.configurationDomainService
+ .isInterestToBeRecoveredFirstWhenGreaterThanEMI();
+ final boolean isPrincipalCompoundingDisabledForOverdueLoans =
this.configurationDomainService
+ .isPrincipalCompoundingDisabledForOverdueLoans();
return LoanApplicationTerms.assembleFrom(applicationCurrency,
loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, nthDay,
weekDayType, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType,
annualNominalInterestRate, interestCalculationPeriodMethod,
@@ -460,7 +463,8 @@ public class LoanScheduleAssembler {
compoundingMethod, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf,
loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO,
loanTermVariations,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays,
isSkipMeetingOnFirstDay, detailDTO,
- allowCompoundingOnEod, isEqualAmortization,
fixedPrincipalPercentagePerInstallment);
+ allowCompoundingOnEod, isEqualAmortization,
isInterestToBeRecoveredFirstWhenGreaterThanEMI,
+ fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer
repaymentEvery,
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
index ee201c8..0653a72 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
@@ -144,15 +144,17 @@ public class LoanUtilService {
boolean isFirstRepaymentDateAllowedOnHoliday =
this.configurationDomainService
.isFirstRepaymentDateAfterRescheduleAllowedOnHoliday();
- boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI =
this.configurationDomainService
- .isInterestToBeAppropriatedEquallyWhenGreaterThanEMI();
+ boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI =
this.configurationDomainService
+ .isInterestToBeRecoveredFirstWhenGreaterThanEMI();
+ boolean isPrincipalCompoundingDisabledForOverdueLoans =
this.configurationDomainService
+ .isPrincipalCompoundingDisabledForOverdueLoans();
ScheduleGeneratorDTO scheduleGeneratorDTO = new
ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetails,
restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod, floatingRateDTO, calendar,
calendarHistoryDataWrapper,
isInterestChargedFromDateAsDisbursementDateEnabled,
numberOfDays, isSkipRepaymentOnFirstMonth,
isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled,
isFirstRepaymentDateAllowedOnHoliday,
- isInterestToBeAppropriatedEquallyWhenGreaterThanEMI);
+ isInterestToBeRecoveredFirstWhenGreaterThanEMI,
isPrincipalCompoundingDisabledForOverdueLoans);
return scheduleGeneratorDTO;
}
diff --git
a/fineract-provider/src/main/resources/sql/migrations/core_db/V392__interest_recovery_conf_for_rescedule.sql
b/fineract-provider/src/main/resources/sql/migrations/core_db/V392__interest_recovery_conf_for_rescedule.sql
new file mode 100644
index 0000000..c866dd0
--- /dev/null
+++
b/fineract-provider/src/main/resources/sql/migrations/core_db/V392__interest_recovery_conf_for_rescedule.sql
@@ -0,0 +1,29 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+delete from c_configuration where name =
'is-interest-to-be-appropriated-equally-when-greater-than-emi';
+
+INSERT INTO `c_configuration` (`name`, `value`, `date_value`, `enabled`,
`is_trap_door`, `description`)
+VALUES
+ ('is-interest-to-be-recovered-first-when-greater-than-emi', 0, NULL, 0, 0,
'If enabled, when interest amount is greater than EMI, the additional interest
is recovered first before princupal');
+
+
+INSERT INTO `c_configuration` (`name`, `value`, `date_value`, `enabled`,
`is_trap_door`, `description`)
+VALUES
+ ('is-principal-compounding-disabled-for-overdue-loans', 0, NULL, 0, 0, 'If
enabled, it donot consider principal of an upaid installment for calculating
interest of next installment. this is for testing back-dated loan sshedule');
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
index 4734355..6fb1607 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
@@ -225,31 +225,31 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
}
/**
- * enables the configuration
`is-interest-to-be-appropriated-equally-when-greater-than-emi`
+ * enables the configuration
`is-interest-to-be-recovered-first-when-greater-than-emi`
**/
private void enableConfig() {
-
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "34", true);
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
}
/**
- * disables the configuration
`is-interest-to-be-appropriated-equally-when-greater-than-emi`
+ * disables the configuration
`is-interest-to-be-recovered-first-when-greater-than-emi`
**/
private void disableConfig() {
-
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "34", false);
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", false);
}
@Test
public void testCreateLoanRescheduleRequestWithInterestAppropriation() {
// create all required entities
this.createRequiredEntities();
- this.createAndApproveLoanRescheduleRequestForInterestAppropriation();
+
this.createAndApproveLoanRescheduleRequestForRecoverInterestInterestFirst();
}
/**
* create new loan reschedule request
**/
- private void
createAndApproveLoanRescheduleRequestForInterestAppropriation() {
+ private void
createAndApproveLoanRescheduleRequestForRecoverInterestInterestFirst() {
LOG.info(
"---------------------------------CREATING LOAN RESCHEDULE
REQUEST FOR INTEREST APPROPRIATTION-------------------------------------");
@@ -279,8 +279,8 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
final HashMap loanSummary =
this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec,
loanId);
final Float totalExpectedRepayment = (Float)
loanSummary.get("totalExpectedRepayment");
- assertEquals(12186, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
- assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED REPAYMENT is NOK");
+ assertEquals(10831, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
+ assertEquals(125184, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
@@ -327,7 +327,7 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
final HashMap loanSummary =
this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec,
loanId);
final Float totalExpectedRepayment = (Float)
loanSummary.get("totalExpectedRepayment");
- assertEquals(12326, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
+ assertEquals(10831, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
assertEquals(131512, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
@@ -338,13 +338,13 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
public void
testCreateLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
// create all required entities
this.createRequiredEntities();
-
this.createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI();
+
this.createAndApproveLoanRescheduleRequestForRecoverInterestFirstAndFixedEMI();
}
/**
- * create new loan reschedule request with combination of date change,
interest appropriation and fixed emi
+ * create new loan reschedule request with combination of date change,
recover interest first and fixed emi
**/
- private void
createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
+ private void
createAndApproveLoanRescheduleRequestForRecoverInterestFirstAndFixedEMI() {
LOG.info(
"---------------------------------CREATING LOAN RESCHEDULE
REQUEST FOR INTEREST APPROPRIATTION-------------------------------------");
@@ -379,8 +379,8 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
assertEquals(5000, totalFixedDueForPeriod.intValue(), "EXPECTED FIXED
REPAYMENT is NOK");
- assertEquals(15316, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
- assertEquals(120806, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED REPAYMENT is NOK");
+ assertEquals(15417, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
is NOK");
+ assertEquals(121412, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
@@ -390,7 +390,7 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
public void
testCreateLoanRescheduleRequestWithMultpleInterestAppropriation() {
// create all required entities
this.createRequiredEntities();
- this.createAndApproveLoanRescheduleRequestForInterestAppropriation();
+
this.createAndApproveLoanRescheduleRequestForRecoverInterestInterestFirst();
this.createAndApproveLoanRescheduleRequestForSecondInterestAppropriation();
@@ -429,9 +429,105 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
final HashMap loanSummary =
this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec,
loanId);
final Float totalExpectedRepayment = (Float)
loanSummary.get("totalExpectedRepayment");
- assertEquals(12187, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
in Second Reschedule is NOK");
- assertEquals(130750, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED in Second Reschedule REPAYMENT is NOK");
+ assertEquals(10831, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT
in Second Reschedule is NOK");
+ assertEquals(133470, totalExpectedRepayment.intValue(), "TOTAL
EXPECTED in Second Reschedule REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
}
+
+ @Test
+ public void
testCreateLoanInterestGreaterThanEMIFromGapWithRecalculationEnabledAndPrincipalCompoundingOff()
{
+ this.enableConfig();
+ this.enablePrincipalCompoundingConfig();
+ // create all required entities
+
this.createRequiredEntitiesWithRecalculationEnabledWithPrincipalCompoundingOff();
+
this.createApproveLoanRescheduleRequestWithRecalculationEnabledWithPrincipalCompoundingOff();
+ this.disablePrincipalCompoundingConfig();
+ this.disableConfig();
+ }
+
+ private void
createRequiredEntitiesWithRecalculationEnabledWithPrincipalCompoundingOff() {
+ this.createClientEntity();
+ this.createLoanProductWithInterestRecalculation();
+
this.createLoanEntityWithScheduleGapWithInterestGreaterThanEMIAndPrincipalCompoundingOff();
+ }
+
+ /**
+ * enables the configuration
`is-principal-compounding-disabled-for-overdue-loans`
+ **/
+ private void enablePrincipalCompoundingConfig() {
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "43", true);
+ }
+
+ /**
+ * disables the configuration
`is-principal-compounding-disabled-for-overdue-loans`
+ **/
+ private void disablePrincipalCompoundingConfig() {
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "43", false);
+ }
+
+ /**
+ * submit a new loan application, approve and disburse the loan
+ **/
+ private void
createLoanEntityWithScheduleGapWithInterestGreaterThanEMIAndPrincipalCompoundingOff()
{
+ String firstRepaymentDate = "01 January 2015";
+
+ LOG.info("---------------------------------NEW LOAN
APPLICATION------------------------------------------");
+
+ final String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("15000").withLoanTermFrequency("24")
+
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("24").withRepaymentEveryAfter("1")
+
.withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays()
+
.withInterestRatePerPeriod("25").withInterestTypeAsDecliningBalance().withSubmittedOnDate(this.dateString)
+
.withExpectedDisbursementDate(this.dateString).withFirstRepaymentDate(firstRepaymentDate)
+
.withinterestChargedFromDate(this.dateString).build(this.clientId.toString(),
this.loanProductId.toString(), null);
+
+ this.loanId =
this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+ LOG.info("Sucessfully created loan (ID: {} )", this.loanId);
+
+ this.approveLoanApplication();
+ this.disburseLoan();
+ }
+
+ /**
+ * create new loan reschedule request with recalculation enabled in Loan
product
+ **/
+
+ private void
createApproveLoanRescheduleRequestWithRecalculationEnabledWithPrincipalCompoundingOff()
{
+ LOG.info(
+ "---------------------------------CREATING LOAN RESCHEDULE
REQUEST FOR LOAN WITH RECALCULATION------------------------------------");
+
+ final String requestJSON = new
LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
+ .updateExtraTerms(null).updateRescheduleFromDate("01 March
2015").updateAdjustedDueDate("01 July 2015")
+ .updateRecalculateInterest(true).build(this.loanId.toString());
+
+ this.loanRescheduleRequestId =
this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
+
this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
+
+ LOG.info("Successfully created loan reschedule request (ID: {} )",
this.loanRescheduleRequestId);
+
+ final String aproveRequestJSON = new
LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
+
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId,
aproveRequestJSON);
+ final HashMap response = (HashMap)
this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId,
"statusEnum");
+ assertTrue((Boolean) response.get("approved"));
+
+ LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
+
+ final Map repaymentSchedule = (Map)
this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec,
loanId,
+ "repaymentSchedule");
+ final ArrayList periods = (ArrayList) repaymentSchedule.get("periods");
+
+ HashMap period = (HashMap) periods.get(5);
+ Float totalDueForPeriod = (Float) period.get("totalDueForPeriod");
+
+ final HashMap loanSummary =
this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec,
loanId);
+ final Float totalExpectedRepayment = (Float)
loanSummary.get("totalExpectedRepayment");
+
+ assertEquals(798, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is
NOK");
+ assertEquals(22567, totalExpectedRepayment.intValue(), "TOTAL EXPECTED
REPAYMENT is NOK");
+
+ LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
+
+ }
+
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
index 9ff751e..a58551a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
@@ -90,8 +90,8 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> actualGlobalConfigurations =
getAllGlobalConfigurations(requestSpec, responseSpec);
// There are currently 37 global configurations.
- Assertions.assertEquals(37, expectedGlobalConfigurations.size());
- Assertions.assertEquals(37, actualGlobalConfigurations.size());
+ Assertions.assertEquals(38, expectedGlobalConfigurations.size());
+ Assertions.assertEquals(38, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -355,14 +355,6 @@ public class GlobalConfigurationHelper {
isFirstPaydayAllowedOnHoliday.put("trapDoor", false);
defaults.add(isFirstPaydayAllowedOnHoliday);
- HashMap<String, Object> isInterestAppropriationEnabled = new
HashMap<>();
- isInterestAppropriationEnabled.put("id", 34);
- isInterestAppropriationEnabled.put("name",
"is-interest-to-be-appropriated-equally-when-greater-than-emi");
- isInterestAppropriationEnabled.put("value", 0);
- isInterestAppropriationEnabled.put("enabled", false);
- isInterestAppropriationEnabled.put("trapDoor", false);
- defaults.add(isInterestAppropriationEnabled);
-
HashMap<String, Object> isAccountMappedForPayment = new HashMap<>();
isAccountMappedForPayment.put("id", 35);
isAccountMappedForPayment.put("name",
"account-mapping-for-payment-type");
@@ -423,6 +415,22 @@ public class GlobalConfigurationHelper {
isAccountNumberRandomGenerated.put("trapDoor", false);
defaults.add(isAccountNumberRandomGenerated);
+ HashMap<String, Object> isInterestAppropriationEnabled = new
HashMap<>();
+ isInterestAppropriationEnabled.put("id", 42);
+ isInterestAppropriationEnabled.put("name",
"is-interest-to-be-recovered-first-when-greater-than-emi");
+ isInterestAppropriationEnabled.put("value", 0);
+ isInterestAppropriationEnabled.put("enabled", false);
+ isInterestAppropriationEnabled.put("trapDoor", false);
+ defaults.add(isInterestAppropriationEnabled);
+
+ HashMap<String, Object> isPrincipalCompoundingDisabled = new
HashMap<>();
+ isPrincipalCompoundingDisabled.put("id", 43);
+ isPrincipalCompoundingDisabled.put("name",
"is-principal-compounding-disabled-for-overdue-loans");
+ isPrincipalCompoundingDisabled.put("value", 0);
+ isPrincipalCompoundingDisabled.put("enabled", false);
+ isPrincipalCompoundingDisabled.put("trapDoor", false);
+ defaults.add(isPrincipalCompoundingDisabled);
+
return defaults;
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
index a0d17f8..06fb68a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
@@ -77,6 +77,7 @@ public class LoanApplicationTestBuilder {
private List<HashMap<String, Object>> datatables = null;
private List<Map<String, Object>> approvalFormData = null;
private String fixedPrincipalPercentagePerInstallment;
+ private String interestChargedFromDate;
public String build(final String clientID, final String groupID, final
String loanProductId, final String savingsID) {
final HashMap<String, Object> map = new HashMap<>();
@@ -150,6 +151,7 @@ public class LoanApplicationTestBuilder {
map.put("submittedOnDate", this.submittedOnDate);
map.put("loanType", this.loanType);
map.put("collateral", this.collaterals);
+ map.put("interestChargedFromDate", this.interestChargedFromDate);
if (repaymentsStartingFromDate != null) {
map.put("repaymentsStartingFromDate",
this.repaymentsStartingFromDate);
@@ -382,4 +384,9 @@ public class LoanApplicationTestBuilder {
this.fixedPrincipalPercentagePerInstallment =
fixedPrincipalPercentagePerInstallment;
return this;
}
+
+ public LoanApplicationTestBuilder withinterestChargedFromDate(String
interestChargedFromDate) {
+ this.interestChargedFromDate = interestChargedFromDate;
+ return this;
+ }
}