FINERACT-1443-loan-interest-recover-policy-fix-when-interest-more-than-EMI (#2033)

This commit is contained in:
Manoj
2022-02-18 23:52:51 +05:30
committed by GitHub
parent fbdceea384
commit 7c235c583b
16 changed files with 310 additions and 148 deletions
@@ -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();
@@ -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
@@ -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;
}
}
@@ -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);
}
/**
@@ -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());
}
if (retainedInstallments.size() > 0
&& retainedInstallments.get(retainedInstallments.size() - 1).getRescheduleInterestPortion() != null) {
loanApplicationTerms.setInterestTobeApproppriated(
Money.of(loan.getCurrency(), retainedInstallments.get(retainedInstallments.size() - 1).getRescheduleInterestPortion()));
}
BigDecimal rescheuleInterestPortionTotal = BigDecimal.ZERO;
for (LoanRepaymentScheduleInstallment inst : loan.getRepaymentScheduleInstallments()) {
if (inst.getRescheduleInterestPortion() != null) {
rescheuleInterestPortionTotal = rescheuleInterestPortionTotal.add(inst.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);
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -43,4 +43,7 @@ public interface ScheduledDateGenerator {
LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
HolidayDetailDTO holidayDetailDTO);
LocalDate generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate lastRepaymentDate,
LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
}
@@ -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,
@@ -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;
}
@@ -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');