FINERACT-2152: Interest pause during one period and between two periods
This commit is contained in:
committed by
Adam Saghy
parent
a9d76ef3dd
commit
af37e07f31
+14
-4
@@ -261,7 +261,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
final LoanTermVariationsData interestRateChange, final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
final LocalDate interestRateChangeSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
||||
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
||||
emiCalculator.changeInterestRate(scheduleModel, interestRateChangeSubmittedOnDate, newInterestRate);
|
||||
if (interestRateChange.getTermVariationType().isInterestPauseVariation()) {
|
||||
final LocalDate pauseEndDate = interestRateChange.getDateValue();
|
||||
emiCalculator.applyInterestPause(scheduleModel, interestRateChangeSubmittedOnDate, pauseEndDate);
|
||||
} else {
|
||||
emiCalculator.changeInterestRate(scheduleModel, interestRateChangeSubmittedOnDate, newInterestRate);
|
||||
}
|
||||
processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments);
|
||||
}
|
||||
|
||||
@@ -820,9 +825,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
@NotNull
|
||||
private List<ChangeOperation> createSortedChangeList(final LoanTermVariationsDataWrapper loanTermVariations,
|
||||
final List<LoanTransaction> loanTransactions, final Set<LoanCharge> charges) {
|
||||
List<ChangeOperation> changeOperations = new ArrayList<>();
|
||||
if (loanTermVariations != null && !loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
|
||||
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
|
||||
final List<ChangeOperation> changeOperations = new ArrayList<>();
|
||||
if (loanTermVariations != null) {
|
||||
if (!loanTermVariations.getInterestPauseVariations().isEmpty()) {
|
||||
changeOperations.addAll(loanTermVariations.getInterestPauseVariations().stream().map(ChangeOperation::new).toList());
|
||||
}
|
||||
if (!loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
|
||||
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
|
||||
}
|
||||
}
|
||||
if (charges != null) {
|
||||
changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList());
|
||||
|
||||
+9
-15
@@ -23,6 +23,7 @@ import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -34,6 +35,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@Getter
|
||||
@ToString(exclude = { "repaymentPeriod" })
|
||||
@EqualsAndHashCode(exclude = { "repaymentPeriod" })
|
||||
@AllArgsConstructor
|
||||
public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
|
||||
private final RepaymentPeriod repaymentPeriod;
|
||||
@@ -49,25 +51,13 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
private Money balanceCorrectionAmount;
|
||||
private Money outstandingLoanBalance;
|
||||
private final MathContext mc;
|
||||
|
||||
public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor,
|
||||
BigDecimal rateFactorTillPeriodDueDate, Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance,
|
||||
MathContext mc) {
|
||||
this.repaymentPeriod = repaymentPeriod;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.rateFactor = rateFactor;
|
||||
this.rateFactorTillPeriodDueDate = rateFactorTillPeriodDueDate;
|
||||
this.disbursementAmount = disbursementAmount;
|
||||
this.balanceCorrectionAmount = balanceCorrectionAmount;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
this.mc = mc;
|
||||
}
|
||||
private final boolean isPaused;
|
||||
|
||||
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod) {
|
||||
this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(),
|
||||
interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getDisbursementAmount(),
|
||||
interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc());
|
||||
interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(),
|
||||
interestPeriod.isPaused());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +74,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
}
|
||||
|
||||
public Money getCalculatedDueInterest() {
|
||||
if (isPaused) {
|
||||
return Money.zero(outstandingLoanBalance.getCurrencyData(), mc);
|
||||
}
|
||||
|
||||
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
||||
final BigDecimal interestDueTillRepaymentDueDate = lengthTillPeriodDueDate == 0 //
|
||||
? BigDecimal.ZERO //
|
||||
|
||||
+77
-15
@@ -160,6 +160,24 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
.findFirst();//
|
||||
}
|
||||
|
||||
public Optional<RepaymentPeriod> updateInterestPeriodsForInterestPause(final LocalDate fromDate, final LocalDate endDate) {
|
||||
if (fromDate == null || endDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final List<RepaymentPeriod> affectedPeriods = repaymentPeriods.stream()//
|
||||
.filter(period -> isPeriodInRange(period, fromDate, endDate))//
|
||||
.toList();
|
||||
affectedPeriods.forEach(period -> insertInterestPausePeriods(period, fromDate, endDate));
|
||||
|
||||
return affectedPeriods.stream().findFirst();
|
||||
}
|
||||
|
||||
private boolean isPeriodInRange(final RepaymentPeriod repaymentPeriod, final LocalDate fromDate, final LocalDate endDate) {
|
||||
return DateUtils.isDateInRangeFromExclusiveToInclusive(fromDate, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate())
|
||||
|| DateUtils.isDateInRangeFromExclusiveToInclusive(endDate, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate());
|
||||
}
|
||||
|
||||
Optional<RepaymentPeriod> findRepaymentPeriodForBalanceChange(final LocalDate balanceChangeDate) {
|
||||
if (balanceChangeDate == null) {
|
||||
return Optional.empty();
|
||||
@@ -192,7 +210,8 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
};
|
||||
}
|
||||
|
||||
Optional<InterestPeriod> findInterestPeriodForBalanceChange(final RepaymentPeriod repaymentPeriod, final LocalDate balanceChangeDate) {
|
||||
private Optional<InterestPeriod> findInterestPeriodForBalanceChange(final RepaymentPeriod repaymentPeriod,
|
||||
final LocalDate balanceChangeDate) {
|
||||
if (repaymentPeriod == null || balanceChangeDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -203,26 +222,63 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
|
||||
void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate balanceChangeDate, final Money disbursedAmount,
|
||||
final Money correctionAmount) {
|
||||
final InterestPeriod previousInterestPeriod;
|
||||
if (balanceChangeDate.isAfter(repaymentPeriod.getFromDate())) {
|
||||
previousInterestPeriod = repaymentPeriod.getInterestPeriods().get(repaymentPeriod.getInterestPeriods().size() - 1);//
|
||||
} else {
|
||||
previousInterestPeriod = repaymentPeriod.getInterestPeriods().stream()
|
||||
.filter(ip -> balanceChangeDate.isAfter(ip.getFromDate()) && !balanceChangeDate.isAfter(ip.getDueDate()))//
|
||||
.reduce((first, second) -> second)//
|
||||
.orElse(repaymentPeriod.getInterestPeriods().get(0));
|
||||
}
|
||||
LocalDate originalDueDate = previousInterestPeriod.getDueDate();
|
||||
LocalDate newDueDate = balanceChangeDate.isBefore(previousInterestPeriod.getFromDate()) ? previousInterestPeriod.getFromDate()
|
||||
: balanceChangeDate.isAfter(previousInterestPeriod.getDueDate()) ? previousInterestPeriod.getDueDate() : balanceChangeDate;
|
||||
final InterestPeriod previousInterestPeriod = findPreviousInterestPeriod(repaymentPeriod, balanceChangeDate);
|
||||
final LocalDate originalDueDate = previousInterestPeriod.getDueDate();
|
||||
final LocalDate newDueDate = calculateNewDueDate(previousInterestPeriod, balanceChangeDate);
|
||||
|
||||
previousInterestPeriod.setDueDate(newDueDate);
|
||||
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
|
||||
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
|
||||
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, mc);
|
||||
|
||||
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, false);
|
||||
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||
}
|
||||
|
||||
private void insertInterestPausePeriods(final RepaymentPeriod repaymentPeriod, final LocalDate fromDate, final LocalDate endDate) {
|
||||
final InterestPeriod previousInterestPeriod = findPreviousInterestPeriod(repaymentPeriod, fromDate);
|
||||
final LocalDate originalFromDate = previousInterestPeriod.getFromDate();
|
||||
final LocalDate originalDueDate = previousInterestPeriod.getDueDate();
|
||||
final LocalDate newDueDate = calculateNewDueDate(previousInterestPeriod, fromDate.minusDays(1));
|
||||
|
||||
if (fromDate.isAfter(originalFromDate) && endDate.isBefore(originalDueDate)) {
|
||||
previousInterestPeriod.setDueDate(newDueDate);
|
||||
final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, endDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, true);
|
||||
repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
|
||||
final InterestPeriod interestAfterPausePeriod = new InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, false);
|
||||
repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod);
|
||||
}
|
||||
|
||||
if (fromDate.isAfter(originalFromDate) && endDate.isAfter(originalDueDate)) {
|
||||
previousInterestPeriod.setDueDate(newDueDate);
|
||||
final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, true);
|
||||
repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
|
||||
}
|
||||
|
||||
if (fromDate.isBefore(originalFromDate) && endDate.isBefore(originalDueDate)) {
|
||||
repaymentPeriod.getInterestPeriods().clear();
|
||||
final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, true);
|
||||
repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
|
||||
InterestPeriod interestAfterPausePeriod = new InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO,
|
||||
BigDecimal.ZERO, zero, zero, zero, mc, false);
|
||||
repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod);
|
||||
}
|
||||
}
|
||||
|
||||
private InterestPeriod findPreviousInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate date) {
|
||||
if (date.isAfter(repaymentPeriod.getFromDate())) {
|
||||
return repaymentPeriod.getInterestPeriods().get(repaymentPeriod.getInterestPeriods().size() - 1);
|
||||
} else {
|
||||
return repaymentPeriod.getInterestPeriods().stream()
|
||||
.filter(ip -> date.isAfter(ip.getFromDate()) && !date.isAfter(ip.getDueDate())).reduce((first, second) -> second)
|
||||
.orElse(repaymentPeriod.getInterestPeriods().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public Money getTotalDueInterest() {
|
||||
return repaymentPeriods().stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest))
|
||||
.reduce(zero(), Money::plus);
|
||||
@@ -282,4 +338,10 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate calculateNewDueDate(final InterestPeriod previousInterestPeriod, final LocalDate date) {
|
||||
return date.isBefore(previousInterestPeriod.getFromDate()) ? previousInterestPeriod.getFromDate()
|
||||
: date.isAfter(previousInterestPeriod.getDueDate()) ? previousInterestPeriod.getDueDate() : date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ public class RepaymentPeriod {
|
||||
this.interestPeriods = new ArrayList<>();
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
getInterestPeriods().add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, BigDecimal.ZERO, getZero(mc),
|
||||
getZero(mc), getZero(mc), mc));
|
||||
getZero(mc), getZero(mc), mc, false));
|
||||
this.paidInterest = getZero(mc);
|
||||
this.paidPrincipal = getZero(mc);
|
||||
}
|
||||
|
||||
+15
-9
@@ -209,16 +209,22 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private void applyInterestRateChangesOnPeriod(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleModelRepaymentPeriod repaymentPeriod, final ProgressiveLoanInterestScheduleModel interestScheduleModel) {
|
||||
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
||||
for (var interestRateChange : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) {
|
||||
final LocalDate interestRateSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
||||
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
||||
if (interestRateSubmittedOnDate.isAfter(repaymentPeriod.getFromDate())
|
||||
&& !interestRateSubmittedOnDate.isAfter(repaymentPeriod.getDueDate())) {
|
||||
emiCalculator.changeInterestRate(interestScheduleModel, interestRateSubmittedOnDate, newInterestRate);
|
||||
}
|
||||
}
|
||||
if (loanApplicationTerms.getLoanTermVariations() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment().stream()
|
||||
.filter(change -> isDateWithinPeriod(change.getTermVariationApplicableFrom(), repaymentPeriod))
|
||||
.forEach(change -> emiCalculator.changeInterestRate(interestScheduleModel, change.getTermVariationApplicableFrom(),
|
||||
change.getDecimalValue()));
|
||||
|
||||
loanApplicationTerms.getLoanTermVariations().getInterestPauseVariations().stream()
|
||||
.filter(pause -> isDateWithinPeriod(pause.getTermVariationApplicableFrom(), repaymentPeriod)).forEach(pause -> emiCalculator
|
||||
.applyInterestPause(interestScheduleModel, pause.getTermVariationApplicableFrom(), pause.getDateValue()));
|
||||
}
|
||||
|
||||
private boolean isDateWithinPeriod(final LocalDate date, final LoanScheduleModelRepaymentPeriod period) {
|
||||
return date.isAfter(period.getFromDate()) && !date.isAfter(period.getDueDate());
|
||||
}
|
||||
|
||||
private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTerms loanApplicationTerms) {
|
||||
|
||||
+2
@@ -77,4 +77,6 @@ public interface EMICalculator {
|
||||
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
|
||||
|
||||
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate);
|
||||
|
||||
void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate);
|
||||
}
|
||||
|
||||
+14
@@ -792,6 +792,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.reduce(scheduleModel.zero(), Money::add); //
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyInterestPause(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate fromDate,
|
||||
final LocalDate endDate) {
|
||||
scheduleModel.updateInterestPeriodsForInterestPause(fromDate, endDate)
|
||||
.ifPresent(repaymentPeriod -> calculateRateFactorsForInterestPause(scheduleModel, repaymentPeriod.getFromDate()));
|
||||
}
|
||||
|
||||
private void calculateRateFactorsForInterestPause(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate startDate) {
|
||||
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(startDate);
|
||||
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
}
|
||||
|
||||
private long getUncountablePeriods(final List<RepaymentPeriod> relatedRepaymentPeriods, final Money originalEmi) {
|
||||
return relatedRepaymentPeriods.stream() //
|
||||
.filter(repaymentPeriod -> originalEmi.isLessThan(repaymentPeriod.getTotalPaidAmount())) //
|
||||
|
||||
Reference in New Issue
Block a user