FINERACT-2354: Re-age - Excess payment handling - Default Behavior, interestRecalculation = true

This commit is contained in:
mariiaKraievska
2025-12-01 13:15:42 +01:00
committed by Adam Saghy
parent 2bde43ea83
commit b18912cc9d
11 changed files with 1696 additions and 567 deletions
@@ -62,7 +62,8 @@ public class ProgressivePossibleNextRepaymentCalculationServiceImpl extends Abst
ctx.setWrittenOff(loan.isClosedWrittenOff());
ctx.setContractTerminated(loan.isContractTermination());
advancedPaymentScheduleTransactionProcessor.recalculateInterestForDate(nextPaymentDueDate, ctx, false);
RepaymentPeriod repaymentPeriod = scheduleModel.findRepaymentPeriodByDueDate(nextPaymentDueDate)
RepaymentPeriod repaymentPeriod = scheduleModel
.findRepaymentPeriodByFromAndDueDate(nextInstallment.getFromDate(), nextInstallment.getDueDate())
.orElseGet(scheduleModel::getLastRepaymentPeriod);
return repaymentPeriod.getOutstandingPrincipal().add(repaymentPeriod.getOutstandingInterest())
@@ -45,10 +45,10 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -365,10 +365,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void updateInstallmentIfInterestPeriodPresent(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LoanRepaymentScheduleInstallment installment) {
emiCalculator.findRepaymentPeriod(scheduleModel, installment.getDueDate()).ifPresent(interestRepaymentPeriod -> {
installment.updateInterestCharged(interestRepaymentPeriod.getDueInterest().getAmount());
installment.updatePrincipal(interestRepaymentPeriod.getDuePrincipal().getAmount());
});
emiCalculator.findRepaymentPeriod(scheduleModel, installment.getFromDate(), installment.getDueDate())
.ifPresent(interestRepaymentPeriod -> {
installment.updateInterestCharged(interestRepaymentPeriod.getDueInterest().getAmount());
installment.updatePrincipal(interestRepaymentPeriod.getDuePrincipal().getAmount());
});
}
@Override
@@ -592,7 +593,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
if (repaidAmount.isGreaterThanZero()) {
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
emiCalculator.payPrincipal(model, currentInstallment.getFromDate(), currentInstallment.getDueDate(),
transactionDate, repaidAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
@@ -629,7 +631,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
if (repaidAmount.isGreaterThanZero()) {
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
emiCalculator.payPrincipal(model, currentInstallment.getFromDate(), currentInstallment.getDueDate(),
transactionDate, repaidAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
@@ -1581,7 +1584,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void updateInstallmentsPrincipalAndInterestByModel(ProgressiveTransactionCtx ctx) {
ctx.getModel().repaymentPeriods().forEach(repayment -> {
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
.filter(ri -> !ri.isDownPayment() && Objects.equals(ri.getFromDate(), repayment.getFromDate())) //
.filter(ri -> !ri.isDownPayment() && Objects.equals(ri.getFromDate(), repayment.getFromDate()) //
&& Objects.equals(ri.getDueDate(), repayment.getDueDate()))
.findFirst().orElse(null);
if (installment != null) {
installment.updatePrincipal(repayment.getDuePrincipal().getAmount());
@@ -1774,7 +1778,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
currentInstallment.getDueDate(), transactionDate, true).getAmount();
currentInstallment.getFromDate(), currentInstallment.getDueDate(), transactionDate, true).getAmount();
if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) {
currentInstallment.updateInterestCharged(newInterest);
}
@@ -1887,7 +1891,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
&& installment.getDueDate().isAfter(transactionDate)).forEach(installment -> {
final BigDecimal interestOutstanding = installment.getInterestOutstanding(currency).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
installment.getDueDate(), transactionDate, true).getAmount();
installment.getFromDate(), installment.getDueDate(), transactionDate, true).getAmount();
if (MathUtil.isGreaterThanZero(interestOutstanding) || MathUtil.isGreaterThanZero(newInterest)) {
final BigDecimal interestRemoved = MathUtil.subtract(MathUtil.nullToZero(installment.getInterestCharged()),
newInterest);
@@ -2503,10 +2507,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
loanTransactionToRepaymentScheduleMapping, charges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
if (PRINCIPAL.equals(paymentAllocationType.getAllocationType())) {
emiCalculator.payPrincipal(model, installment.getDueDate(), payDate, paidPortion);
emiCalculator.payPrincipal(model, installment.getFromDate(), installment.getDueDate(), payDate, paidPortion);
updateRepaymentPeriods(loanTransaction, ctx);
} else if (INTEREST.equals(paymentAllocationType.getAllocationType())) {
emiCalculator.payInterest(model, installment.getDueDate(), payDate, paidPortion);
emiCalculator.payInterest(model, installment.getFromDate(), installment.getDueDate(), payDate, paidPortion);
updateRepaymentPeriods(loanTransaction, ctx);
}
return paidPortion;
@@ -2514,8 +2518,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void updateRepaymentPeriods(LoanTransaction loanTransaction, ProgressiveTransactionCtx ctx) {
ctx.getModel().repaymentPeriods().forEach(rm -> {
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
.filter(ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment()).findFirst().orElse(null);
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream().filter(
ri -> ri.getFromDate().equals(rm.getFromDate()) && ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment())
.findFirst().orElse(null);
if (installment != null) {
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
installment.updateInterestCharged(rm.getDueInterest().getAmount());
@@ -2528,7 +2533,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void updateRepaymentPeriodBalances(PaymentAllocationType paymentAllocationType,
LoanRepaymentScheduleInstallment inAdvanceInstallment, ProgressiveTransactionCtx ctx, LocalDate payDate) {
PeriodDueDetails payableDetails = emiCalculator.getDueAmounts(ctx.getModel(), inAdvanceInstallment.getDueDate(), payDate);
PeriodDueDetails payableDetails = emiCalculator.getDueAmounts(ctx.getModel(), inAdvanceInstallment.getFromDate(),
inAdvanceInstallment.getDueDate(), payDate);
if (payableDetails.getDueInterest().isZero() && payableDetails.getDuePrincipal().isZero()) {
ctx.getSkipRepaymentScheduleInstallments().add(inAdvanceInstallment);
@@ -2777,16 +2783,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private Money fee;
private Money penalty;
private Money paidInAdvance;
private BigDecimal paidLate;
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings;
private BalancesWithPaidInAdvance(MonetaryCurrency currency) {
this(Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
new LinkedHashSet<>());
BigDecimal.ZERO, new LinkedHashSet<>());
}
private BalancesWithPaidInAdvance(LoanRepaymentScheduleInstallment i, MonetaryCurrency currency) {
this(i.getPrincipalCompleted(currency), i.getInterestPaid(currency), i.getFeeChargesPaid(currency),
i.getPenaltyChargesPaid(currency), i.getTotalPaidInAdvance(currency),
i.getPenaltyChargesPaid(currency), i.getTotalPaidInAdvance(currency), i.getTotalPaidLate(),
new LinkedHashSet<>(i.getLoanTransactionToRepaymentScheduleMappings()));
}
@@ -2796,7 +2803,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
set.addAll(a.getLoanTransactionToRepaymentScheduleMappings());
set.addAll(b.getLoanTransactionToRepaymentScheduleMappings());
return new BalancesWithPaidInAdvance(a.getPrincipal().add(b.getPrincipal()), a.getInterest().add(b.getInterest()),
a.getFee().add(b.getFee()), a.getPenalty().add(b.getPenalty()), a.getPaidInAdvance().add(b.getPaidInAdvance()), set);
a.getFee().add(b.getFee()), a.getPenalty().add(b.getPenalty()), a.getPaidInAdvance().add(b.getPaidInAdvance()),
MathUtil.nullToZero(a.getPaidLate()).add(MathUtil.nullToZero(b.getPaidLate())), set);
}
}
@@ -2901,58 +2909,118 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
}
private void cleanupInstallmentsAfterReAging(final ProgressiveTransactionCtx ctx) {
private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx,
final LocalDate reAgePeriodStartDate) {
final List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final LocalDate transactionDate = loanTransaction.getTransactionDate();
// Find the last re-aged installment number
final OptionalInt lastReAgedInstallmentNumberOpt = installments.stream().filter(LoanRepaymentScheduleInstallment::isReAged)
.mapToInt(LoanRepaymentScheduleInstallment::getInstallmentNumber).max();
final Optional<RepaymentPeriod> reAgeSpecialRepaymentPeriodOpt = ctx.getModel().repaymentPeriods().stream()
.filter(RepaymentPeriod::isReAgedEarlyRepaymentHolder).findFirst();
final LoanRepaymentScheduleInstallment installmentWithMovedPaidAmounts = reAgeSpecialRepaymentPeriodOpt
.map(repaymentPeriod -> createInstallmentWithMovedPaidAmounts(ctx, loanTransaction, installments, repaymentPeriod))
.orElse(null);
if (lastReAgedInstallmentNumberOpt.isPresent()) {
final int lastReAgedInstallmentNumber = lastReAgedInstallmentNumberOpt.getAsInt();
// Remove installments with numbers greater than the last re-aged installment
final List<LoanRepaymentScheduleInstallment> installmentsToRemove = installments.stream().filter(i -> i != null
&& !i.isAdditional() && i.getInstallmentNumber() != null && i.getInstallmentNumber() > lastReAgedInstallmentNumber)
.toList();
installmentsToRemove.forEach(installments::remove);
}
}
final List<LoanCharge> liftedLoanCharges = new ArrayList<>();
installments.stream().filter(installment -> !installment.getDueDate().isBefore(transactionDate)).forEach(i -> {
liftedLoanCharges.addAll(getLoanChargesOfInstallment(ctx.getCharges(), i, 1));
});
installments.removeIf(i -> i.getInstallmentNumber() != null && !i.isDownPayment() && !i.getDueDate().isBefore(transactionDate));
final ListIterator<LoanRepaymentScheduleInstallment> iterator = installments.listIterator();
final AtomicInteger installmentCounter = new AtomicInteger();
private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) {
ctx.getModel().repaymentPeriods().forEach(rp -> {
final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
.filter(ri -> ri.getFromDate().equals(rp.getFromDate()) && !ri.isDownPayment()).findFirst().orElse(null);
final LocalDate transactionDate = loanTransaction.getTransactionDate();
LoanRepaymentScheduleInstallment installment = null;
while (iterator.hasNext() && (installment == null || installment.isAdditional() || installment.isDownPayment())) {
installment = iterator.next();
installmentCounter.getAndIncrement();
}
if (installment != null) {
installment.setFromDate(rp.getFromDate());
installment.setDueDate(rp.getDueDate());
if (installment.isNotFullyPaidOff() || !installment.getFromDate().isBefore(reAgePeriodStartDate)) {
installment.setFromDate(rp.getFromDate());
installment.setDueDate(rp.getDueDate());
if (rp.getEmi().isZero()) {
installment.updatePrincipal(BigDecimal.ZERO);
installment.updateInterestCharged(BigDecimal.ZERO);
} else {
installment.updatePrincipal(rp.getDuePrincipal().getAmount());
installment.updateInterestCharged(rp.getDueInterest().getAmount());
if (rp.getEmi().isZero()) {
installment.updatePrincipal(ZERO);
installment.updateInterestCharged(ZERO);
} else {
installment.updatePrincipal(rp.getDuePrincipal().getAmount());
installment.updateInterestCharged(rp.getDueInterest().getAmount());
}
if (!installment.getFromDate().isBefore(reAgePeriodStartDate)) {
installment.setReAged(true);
}
installment.setAdditional(false);
installment.updateObligationsMet(ctx.getCurrency(), transactionDate);
}
installment.setReAged(true);
installment.setAdditional(false);
installment.updateObligationsMet(ctx.getCurrency(), transactionDate);
} else {
final LoanRepaymentScheduleInstallment lastInstallment = ctx.getInstallments().getLast();
final LoanRepaymentScheduleInstallment newInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
loanTransaction.getLoan(), lastInstallment.getInstallmentNumber() + 1, rp.getFromDate(), rp.getDueDate(),
rp.getDuePrincipal().getAmount(), null, null, null);
if (rp.isReAgedEarlyRepaymentHolder()) {
iterator.add(installmentWithMovedPaidAmounts);
} else {
installmentCounter.getAndIncrement();
final LoanRepaymentScheduleInstallment newInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
loanTransaction.getLoan(), installmentCounter.get(), rp.getFromDate(), rp.getDueDate(),
rp.getDuePrincipal().getAmount(), null, null, null);
if (rp.getDueInterest().isGreaterThanZero()) {
newInstallment.addToInterest(transactionDate, rp.getDueInterest());
if (rp.getDueInterest().isGreaterThanZero()) {
newInstallment.addToInterest(transactionDate, rp.getDueInterest());
}
newInstallment.updateObligationsMet(ctx.getCurrency(), transactionDate);
iterator.add(newInstallment);
}
newInstallment.updateObligationsMet(ctx.getCurrency(), transactionDate);
ctx.getInstallments().add(newInstallment);
}
});
cleanupInstallmentsAfterReAging(ctx);
for (LoanCharge loanCharge : liftedLoanCharges) {
loanChargeRepaymentScheduleProcessing.reprocess(ctx.getCurrency(), loanTransaction.getLoan().getDisbursementDate(),
installments, loanCharge);
}
}
/**
* Creates a special installment to preserve paid amounts during the re-aging process.
*
* @param loanTransaction
* the re-aging transaction
* @param installments
* list of existing loan repayment schedule installments
* @param rp
* repayment period with moved paid amounts
* @return new installment with moved paid amounts
*/
private LoanRepaymentScheduleInstallment createInstallmentWithMovedPaidAmounts(final ProgressiveTransactionCtx ctx,
final LoanTransaction loanTransaction, final List<LoanRepaymentScheduleInstallment> installments, final RepaymentPeriod rp) {
final MonetaryCurrency currency = loanTransaction.getLoan().loanCurrency();
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final Optional<LoanRepaymentScheduleInstallment> firstReAgedInstallment = installments.stream()
.filter(i -> (i.getInstallmentNumber() != null && !i.isDownPayment() && !i.getDueDate().isBefore(transactionDate)))
.findFirst();
int installmentNumberToInsertPaidInstallment = installments.getLast().getInstallmentNumber() + 1;
if (firstReAgedInstallment.isPresent()) {
installmentNumberToInsertPaidInstallment = firstReAgedInstallment.get().getInstallmentNumber();
}
final LoanRepaymentScheduleInstallment newInstallment = LoanRepaymentScheduleInstallment
.newInstallmentWithMovedPaidAmountDuringReAging(loanTransaction.getLoan(), installmentNumberToInsertPaidInstallment,
rp.getFromDate(), rp.getDueDate(), rp.getDuePrincipal().getAmount(), rp.getDueInterest().getAmount());
newInstallment.payPrincipalComponent(transactionDate, rp.getPaidPrincipal());
newInstallment.payInterestComponent(transactionDate, rp.getPaidInterest());
final BalancesWithPaidInAdvance paidInAdvanceBalances = liftEarlyRepaidBalances(installments, transactionDate, currency,
ctx.getAlreadyProcessedTransactions());
newInstallment.setTotalPaidInAdvance(paidInAdvanceBalances.getPaidInAdvance().getAmount());
newInstallment.setTotalPaidLate(paidInAdvanceBalances.getPaidLate());
paidInAdvanceBalances.loanTransactionToRepaymentScheduleMappings.forEach(m -> m.setInstallment(newInstallment));
newInstallment.updateObligationsMet(currency, transactionDate);
return newInstallment;
}
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
@@ -3030,8 +3098,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
final BigDecimal totalPrincipal = periodsToRemove.stream().map(rp -> rp.getDuePrincipal().getAmount()).reduce(BigDecimal.ZERO,
BigDecimal::add);
final BigDecimal newInterest = emiCalculator
.getPeriodInterestTillDate(transactionCtx.getModel(), lastPeriod.getDueDate(), transactionDate, false).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(transactionCtx.getModel(), lastPeriod.getFromDate(),
lastPeriod.getDueDate(), transactionDate, false).getAmount();
lastPeriod.setEmi(lastPeriod.getDuePrincipal().add(totalPrincipal).add(newInterest));
@@ -3074,9 +3142,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
} else if (isInPeriod) {
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
interest = emiCalculator
.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), installment.getDueDate(), chargeOffDate, true)
.getAmount();
interest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), installment.getFromDate(),
installment.getDueDate(), chargeOffDate, true).getAmount();
} else {
final BigDecimal totalInterest = installment.getInterestOutstanding(currency).getAmount();
if (LoanChargeOffBehaviour.ZERO_INTEREST.equals(loan.getLoanProductRelatedDetail().getChargeOffBehaviour())
@@ -3500,18 +3567,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
final List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final LocalDate reAgingStartDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final List<LoanRepaymentScheduleInstallment> installmentsBeforeReAging = installments.stream()
.filter(rp -> rp.getFromDate().isBefore(reAgingStartDate)).toList();
// Define the date, which must match reAgingStartDate
final LocalDate expectedReAgingDate = installmentsBeforeReAging.isEmpty() ? installments.getFirst().getFromDate()
: installmentsBeforeReAging.getLast().getDueDate();
if (!expectedReAgingDate.isEqual(reAgingStartDate)) {
// TODO: implement logic when re-aging changes the due dates
throw new NotImplementedException("Logic when re-aging changes the due dates not implemented");
}
final Money interestFromZeroedInstallments = emiCalculator.getOutstandingInterestTillDate(ctx.getModel(),
loanTransaction.getTransactionDate());
@@ -3537,13 +3592,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
loan.getLoanProduct().getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation())
.mc(mc).build();
LocalDate reAgePeriodStartDate = calculateFirstReAgedPeriodStartDate(loanTransaction);
LocalDate reageFirstDueDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final LocalDate reAgePeriodStartDate = calculateFirstReAgedPeriodStartDate(loanTransaction);
final LocalDate reAgeFirstDueDate = loanTransaction.getLoanReAgeParameter().getStartDate();
// Update the existing model with re-aged periods
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx.getModel(), reAgePeriodStartDate, reageFirstDueDate,
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx.getModel(), reAgePeriodStartDate, reAgeFirstDueDate,
loanTransaction.getTransactionDate(), loanApplicationTerms, mc);
updateInstallmentsByModelForReAging(loanTransaction, ctx);
updateInstallmentsByModelForReAging(loanTransaction, ctx, reAgePeriodStartDate);
loanTransaction.updateComponentsAndTotal(totalOutstandingPrincipal, interestFromZeroedInstallments, Money.zero(currency),
Money.zero(currency));
@@ -126,19 +126,20 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
chargesDueAtTimeOfDisbursement, false, mc);
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
emiCalculator.findRepaymentPeriod(interestScheduleModel, repaymentPeriod.getDueDate()).ifPresent(interestRepaymentPeriod -> {
final Money principalDue = interestRepaymentPeriod.getDuePrincipal();
final Money interestDue = interestRepaymentPeriod.getDueInterest();
emiCalculator.findRepaymentPeriod(interestScheduleModel, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate())
.ifPresent(interestRepaymentPeriod -> {
final Money principalDue = interestRepaymentPeriod.getDuePrincipal();
final Money interestDue = interestRepaymentPeriod.getDueInterest();
repaymentPeriod.addPrincipalAmount(principalDue);
repaymentPeriod.addInterestAmount(interestDue);
repaymentPeriod.setOutstandingLoanBalance(interestRepaymentPeriod.getOutstandingLoanBalance());
repaymentPeriod.addPrincipalAmount(principalDue);
repaymentPeriod.addInterestAmount(interestDue);
repaymentPeriod.setOutstandingLoanBalance(interestRepaymentPeriod.getOutstandingLoanBalance());
scheduleParams.addTotalCumulativePrincipal(principalDue);
scheduleParams.addTotalCumulativeInterest(interestDue);
// add everything
scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue, mc));
});
scheduleParams.addTotalCumulativePrincipal(principalDue);
scheduleParams.addTotalCumulativeInterest(interestDue);
// add everything
scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue, mc));
});
applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc);
periods.add(repaymentPeriod);
@@ -254,7 +255,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
}
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate);
ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow();
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false);
return emiCalculator.getPeriodInterestTillDate(model, installment.getFromDate(), installment.getDueDate(), targetDate, false);
}
// Private, internal methods
@@ -61,7 +61,8 @@ public interface EMICalculator {
/**
* Find repayment period based on Due Date.
*/
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate,
LocalDate dueDate);
/**
* Applies the disbursement on the interest model. This method recalculates the EMI amounts from the action date.
@@ -95,14 +96,14 @@ public interface EMICalculator {
/**
* This method used for pay interest portion during the repayment transaction.
*/
void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
Money interestAmount);
void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodFromDate,
LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money interestAmount);
/**
* This method used for pay principal portion during the repayment transaction.
*/
void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
Money principalAmount);
void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodFromDate,
LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money principalAmount);
/**
* This method used for credit principal portion. This method increases the outstanding balance. This method creates
@@ -120,15 +121,15 @@ public interface EMICalculator {
* This method gives back the maximum of the due principal and maximum of the due interest for a requested day.
*/
@NotNull
PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate);
PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate);
/**
* Gives back the sum of the interest from the whole model on the given date.
*/
@NotNull
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeChargebackInterest);
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate targetDate);
@@ -112,11 +112,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override
public Optional<RepaymentPeriod> findRepaymentPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate repaymentPeriodDueDate) {
final LocalDate repaymentPeriodFromDate, final LocalDate repaymentPeriodDueDate) {
if (scheduleModel == null) {
return Optional.empty();
}
return scheduleModel.findRepaymentPeriodByDueDate(repaymentPeriodDueDate);
return scheduleModel.findRepaymentPeriodByFromAndDueDate(repaymentPeriodFromDate, repaymentPeriodDueDate);
}
/**
@@ -263,9 +263,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
@Override
public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
Money interestAmount) {
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodFromDate,
LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money interestAmount) {
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodFromDate, repaymentPeriodDueDate);
Optional<RepaymentPeriod> latestNotLastOpenRepaymentPeriodBeforeDate = getLatestNotLastOpenRepaymentPeriodBeforeDate(scheduleModel,
transactionDate);
@@ -280,12 +280,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
@Override
public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate,
LocalDate transactionDate, Money principalAmount) {
public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodFromDate,
LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money principalAmount) {
if (MathUtil.isEmpty(principalAmount)) {
return;
}
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodFromDate, repaymentPeriodDueDate);
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
// If it is paid late, we need to calculate with the period due date
LocalDate balanceCorrectionDate = DateUtils.isBefore(repaymentPeriodDueDate, transactionDate) ? repaymentPeriodDueDate
@@ -379,11 +379,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
*/
@Override
@NotNull
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate) {
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate
.findRepaymentPeriodByFromAndDueDate(periodFromDate, periodDueDate).orElseThrow();
long notFullyRepaidRepaymentPeriodCount = recalculatedScheduleModelTillDate.repaymentPeriods().stream()
.filter(rp -> !rp.isFullyPaid()).count();
boolean multiplePeriodIsUnpaid = notFullyRepaidRepaymentPeriodCount > 1L;
@@ -413,11 +414,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override
@NotNull
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate, boolean includeCreditedInterest) {
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeCreditedInterest) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate
.findRepaymentPeriodByFromAndDueDate(periodFromDate, periodDueDate).orElseThrow();
return includeCreditedInterest ? repaymentPeriod.getCalculatedDueInterest()
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getCreditedInterest(),
recalculatedScheduleModelTillDate.mc());
@@ -572,15 +574,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override
public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate reAgePeriodStartDate, LocalDate reageFirstDueDate, LocalDate targetDate,
final LocalDate reAgePeriodStartDate, final LocalDate reAgeFirstDueDate, final LocalDate targetDate,
final LoanApplicationTerms loanApplicationTerms, final MathContext mc) {
moveOutstandingAmountsFromPeriodsBeforeReAging(scheduleModel.repaymentPeriods(), targetDate);
moveOutstandingAmountsFromPeriodsBeforeReAging(scheduleModel.repaymentPeriods(), reageFirstDueDate);
// calculate already paid balances from transaction date
final OutstandingDetails paidBalancesFromTransactionDate = calculatePaidBalancesAfterDate(scheduleModel, targetDate);
accelerateMaturityDateTo(scheduleModel, targetDate);
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generateTemporaryReAgedScheduleModel(loanApplicationTerms,
mc, reAgePeriodStartDate);
mergeNewInterestScheduleModelWithExistingOne(scheduleModel, temporaryReAgedScheduleModel, reageFirstDueDate, targetDate);
mergeNewInterestScheduleModelWithExistingOne(scheduleModel, temporaryReAgedScheduleModel, reAgeFirstDueDate, targetDate,
paidBalancesFromTransactionDate);
}
@Override
@@ -634,7 +641,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
private List<RepaymentPeriod> findPossiblyOverdueRepaymentPeriods(final LocalDate targetDate,
final ProgressiveLoanInterestScheduleModel model) {
return model.repaymentPeriods().stream() //
.filter(repaymentPeriod -> DateUtils.isAfter(targetDate, repaymentPeriod.getDueDate())).toList();
.filter(repaymentPeriod -> !repaymentPeriod.isReAgedEarlyRepaymentHolder()
&& DateUtils.isAfter(targetDate, repaymentPeriod.getDueDate()))
.toList();
}
private boolean adjustOverduePrincipal(final LocalDate currentDate, final RepaymentPeriod currentInstallment,
@@ -694,8 +703,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* significantly from other periods.
*/
private void mergeNewInterestScheduleModelWithExistingOne(final ProgressiveLoanInterestScheduleModel scheduleModel,
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel, final LocalDate reageFirstDueDate,
LocalDate targetDate) {
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel, final LocalDate reAgeFirstDueDate,
final LocalDate targetDate, final OutstandingDetails paidBalancesFromTransactionDate) {
final List<RepaymentPeriod> newPeriods = temporaryReAgedScheduleModel.repaymentPeriods();
if (newPeriods.isEmpty()) {
@@ -706,46 +715,44 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final RepaymentPeriod firstRepaymentPeriod = existingRepaymentPeriods.getFirst();
final Money disbursedAmount = firstRepaymentPeriod.getTotalDisbursedAmount();
final Optional<RepaymentPeriod> firstExistingReAgedRepaymentPeriodOpt = existingRepaymentPeriods.stream()
.filter(period -> period.getDueDate().equals(reageFirstDueDate)).findFirst();
if (!paidBalancesFromTransactionDate.getOutstandingInterest().isZero()
|| !paidBalancesFromTransactionDate.getOutstandingPrincipal().isZero()) {
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(scheduleModel,
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
false);
}
for (final RepaymentPeriod newPeriod : newPeriods) {
final Optional<RepaymentPeriod> existingRepaymentPeriodOpt = existingRepaymentPeriods.stream().filter(
period -> period.getFromDate().equals(newPeriod.getFromDate()) && period.getDueDate().equals(newPeriod.getDueDate()))
.findFirst();
Optional<RepaymentPeriod> previousExistingRepaymentPeriodOpt = Optional.empty();
if (existingRepaymentPeriodOpt.isPresent() && firstExistingReAgedRepaymentPeriodOpt.isPresent()
&& existingRepaymentPeriodOpt.get().equals(firstExistingReAgedRepaymentPeriodOpt.get())) {
previousExistingRepaymentPeriodOpt = existingRepaymentPeriodOpt.get().getPrevious();
}
final Money newPrincipal = newPeriod.getDuePrincipal();
final Money newInterest = newPeriod.getDueInterest();
final RepaymentPeriod rp = RepaymentPeriod.create(
previousExistingRepaymentPeriodOpt.orElseGet(existingRepaymentPeriods::getLast), newPeriod.getFromDate(),
newPeriod.getDueDate(), newPrincipal.add(newInterest), scheduleModel.mc(), scheduleModel.loanProductRelatedDetail());
rp.setTotalDisbursedAmount(disbursedAmount);
if (reAgeFirstDueDate.isEqual(scheduleModel.getStartDate()) && newPeriod.equals(newPeriods.getFirst())) {
final RepaymentPeriod firstPeriod = existingRepaymentPeriods.getFirst();
firstPeriod.setDueDate(newPeriod.getDueDate());
firstPeriod.getInterestPeriods().getLast().setDueDate(newPeriod.getDueDate());
firstPeriod.setEmi(newPrincipal.add(newInterest));
calculateRateFactorForRepaymentPeriod(firstRepaymentPeriod, scheduleModel);
} else {
final RepaymentPeriod rp = RepaymentPeriod.create(
!existingRepaymentPeriods.isEmpty() ? existingRepaymentPeriods.getLast() : null,
(newPeriod.equals(newPeriods.getFirst()) && !existingRepaymentPeriods.isEmpty())
? existingRepaymentPeriods.getLast().getDueDate()
: newPeriod.getFromDate(),
newPeriod.getDueDate(), newPrincipal.add(newInterest), scheduleModel.mc(),
scheduleModel.loanProductRelatedDetail());
rp.setTotalDisbursedAmount(disbursedAmount);
existingRepaymentPeriodOpt.ifPresent(existingRepaymentPeriods::remove);
existingRepaymentPeriods.add(rp);
calculateRateFactorForRepaymentPeriod(rp, scheduleModel);
existingRepaymentPeriods.add(rp);
calculateRateFactorForRepaymentPeriod(rp, scheduleModel);
}
}
final List<RepaymentPeriod> reAgedRepaymentPeriods = existingRepaymentPeriods.stream()
.filter(repaymentPeriod -> (!repaymentPeriod.getFromDate().isBefore(reageFirstDueDate)
|| repaymentPeriod.getDueDate().isEqual(reageFirstDueDate))
&& !repaymentPeriod.getDueDate().isAfter(newPeriods.getLast().getDueDate()))
.filter(repaymentPeriod -> (repaymentPeriod.getFromDate().isEqual(targetDate)
&& repaymentPeriod.getDueDate().isEqual(reAgeFirstDueDate))
|| !repaymentPeriod.getFromDate().isBefore(reAgeFirstDueDate))
.toList();
// cleanup repayment periods after re-aging
if (reAgedRepaymentPeriods.getLast() != null) {
final List<RepaymentPeriod> repaymentPeriodsToRemove = existingRepaymentPeriods.stream()
.filter(rp -> !rp.getFromDate().isBefore(reAgedRepaymentPeriods.getLast().getDueDate())).toList();
repaymentPeriodsToRemove.forEach(existingRepaymentPeriods::remove);
}
calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, targetDate);
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, reAgedRepaymentPeriods);
@@ -773,13 +780,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* principal that was moved.
*/
private static void moveOutstandingAmountsFromPeriodsBeforeReAging(final List<RepaymentPeriod> existingRepaymentPeriods,
final LocalDate reAgingStartDate) {
final LocalDate transactionDate) {
final List<RepaymentPeriod> periodsBeforeReAging = existingRepaymentPeriods.stream()
.filter(rp -> rp.getFromDate().isBefore(reAgingStartDate) && !rp.isFullyPaid()).toList();
.filter(rp -> !rp.getFromDate().isAfter(transactionDate) && !rp.isFullyPaid()).toList();
periodsBeforeReAging.forEach(rp -> {
rp.getInterestPeriods().stream().filter(ip -> ip.getDueDate().isEqual(transactionDate)).forEach(ip -> {
if (!ip.getBalanceCorrectionAmount().isZero()) {
ip.addBalanceCorrectionAmount(ip.getBalanceCorrectionAmount().negated());
}
});
final InterestPeriod lastInterestPeriod = rp.getInterestPeriods().getLast();
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
if (!lastInterestPeriod.getBalanceCorrectionAmount().isZero()) {
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
}
rp.setEmi(rp.getTotalPaidAmount());
rp.setOutstandingMovedDueToReAging(true);
});
@@ -876,10 +890,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (newScheduleModel == null) {
newScheduleModel = scheduleModel.deepCopy(mc);
}
final LocalDate relatedPeriodsFirstFromDate = relatedRepaymentPeriods.getFirst().getFromDate();
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.getFirst().getDueDate();
newScheduleModel.repaymentPeriods().forEach(period -> {
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) {
if (!period.getFromDate().isBefore(relatedPeriodsFirstFromDate) && !period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())
&& !period.isReAgedEarlyRepaymentHolder()) {
period.setEmi(adjustedEqualMonthlyInstallmentValue);
period.setOriginalEmi(adjustedEqualMonthlyInstallmentValue);
}
@@ -891,7 +907,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
final Iterator<RepaymentPeriod> relatedPeriodFromNewModelIterator = newScheduleModel.repaymentPeriods().stream()//
.filter(period -> !period.getDueDate().isBefore(relatedPeriodsFirstDueDate))//
.filter(period -> !period.getFromDate().isBefore(relatedPeriodsFirstFromDate)
&& !period.getDueDate().isBefore(relatedPeriodsFirstDueDate) && !period.isReAgedEarlyRepaymentHolder())//
.toList().iterator();//
relatedRepaymentPeriods.forEach(relatedRepaymentPeriod -> {
@@ -1071,7 +1088,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
calculatedDate = seedDate.plus(multiplicator, chronoUnit);
multiplicator++;
} while (calculatedDate.isBefore(repaymentPeriod.getDueDate()));
return calculatedDate.equals(repaymentPeriod.getDueDate()) ? seedDate : repaymentPeriod.getFromDate();
return calculatedDate.equals(repaymentPeriod.getDueDate()) && calculatedDate
.minus(scheduleModel.loanProductRelatedDetail().getRepayEvery(), chronoUnit).isEqual(repaymentPeriod.getFromDate())
? seedDate
: repaymentPeriod.getFromDate();
}
/**
@@ -1563,8 +1583,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
*/
@Override
public Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate) {
return scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueDate) //
.map(repaymentPeriodDueDate -> getDueAmounts(scheduleModel, repaymentPeriodDueDate, subjectDate) //
return scheduleModel.repaymentPeriods().stream() //
.map(repaymentPeriod -> getDueAmounts(scheduleModel, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(),
subjectDate) //
.getDueInterest()) //
.reduce(scheduleModel.zero(), Money::add); //
}
@@ -1663,7 +1684,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (!paidBalancesFromTransactionDate.getOutstandingInterest().isZero()
|| !paidBalancesFromTransactionDate.getOutstandingPrincipal().isZero()) {
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule,
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest());
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
true);
addFirstReAgedPeriod(interestSchedule, interestSchedule.getLastRepaymentPeriod());
}
updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods, isAfterOriginalMaturityDate);
@@ -1717,18 +1740,23 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
}
private void createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(ProgressiveLoanInterestScheduleModel interestSchedule,
Money totalPaidPrincipal, Money totalPaidInterest) {
RepaymentPeriod targetPeriod = interestSchedule.getLastRepaymentPeriod();
private void createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(final ProgressiveLoanInterestScheduleModel interestSchedule,
final Money totalPaidPrincipal, final Money totalPaidInterest, final boolean isReAged) {
final RepaymentPeriod targetPeriod = interestSchedule.getLastRepaymentPeriod();
Money paidInterestToAdd = totalPaidInterest.minus(targetPeriod.getPaidInterest());
Money paidPrincipalToAdd = totalPaidPrincipal.minus(targetPeriod.getPaidPrincipal());
final Money paidInterestToAdd = totalPaidInterest.minus(targetPeriod.getPaidInterest());
final Money paidPrincipalToAdd = totalPaidPrincipal.minus(targetPeriod.getPaidPrincipal());
targetPeriod.addPaidInterestAmount(paidInterestToAdd);
targetPeriod.addPaidPrincipalAmount(paidPrincipalToAdd);
targetPeriod.setEmi(targetPeriod.getTotalPaidAmount());
targetPeriod.setReAged(true);
if (isReAged) {
targetPeriod.setReAged(true);
}
targetPeriod.setReAgedEarlyRepaymentHolder(true);
calculateRateFactorForRepaymentPeriod(targetPeriod, interestSchedule);
}
private static void addFirstReAgedPeriod(ProgressiveLoanInterestScheduleModel interestSchedule, RepaymentPeriod targetPeriod) {
RepaymentPeriod repaymentPeriodToInsert = RepaymentPeriod.create(targetPeriod, targetPeriod.getDueDate(),
interestSchedule.getMaturityDate(), interestSchedule.zero(), interestSchedule.mc(),
interestSchedule.loanProductRelatedDetail());
@@ -143,12 +143,14 @@ public class ProgressiveLoanInterestScheduleModel {
interestRates.add(new InterestRate(newInterestEffectiveDate, newInterestRate));
}
public Optional<RepaymentPeriod> findRepaymentPeriodByDueDate(final LocalDate repaymentPeriodDueDate) {
public Optional<RepaymentPeriod> findRepaymentPeriodByFromAndDueDate(final LocalDate repaymentPeriodFromDate,
final LocalDate repaymentPeriodDueDate) {
if (repaymentPeriodDueDate == null) {
return Optional.empty();
}
return repaymentPeriods.stream()//
.filter(repaymentPeriodItem -> DateUtils.isEqual(repaymentPeriodItem.getDueDate(), repaymentPeriodDueDate))//
.filter(repaymentPeriodItem -> DateUtils.isEqual(repaymentPeriodItem.getFromDate(), repaymentPeriodFromDate)
&& DateUtils.isEqual(repaymentPeriodItem.getDueDate(), repaymentPeriodDueDate))//
.findFirst();
}