@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
// create an archive of the current loan schedule installments
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = null;
// get the initial list of repayment installments
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
// sort list by installment number in ASC order
Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<>();
Money outstandingLoanBalance = loan.getPrincpal();
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod
.instance(oldPeriodNumber, oldPeriodNumber, fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue,
feeChargesDue, penaltyChargesDue, totalDue, false);
periods.add(period);
}
Money outstandingBalance = loan.getPrincpal();
Money totalCumulativePrincipal = Money.zero(currency);
Money totalCumulativeInterest = Money.zero(currency);
Money actualTotalCumulativeInterest = Money.zero(currency);
Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);
LocalDate installmentDueDate = null;
LocalDate adjustedInstallmentDueDate = null;
LocalDate installmentFromDate = null;
Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment());
Integer installmentNumber = rescheduleFromInstallmentNo;
Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
Integer numberOfRepayments = repaymentScheduleInstallments.size();
Integer rescheduleNumberOfRepayments = numberOfRepayments;
final Money principal = loan.getPrincpal();
final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
int loanTermInDays = Integer.valueOf(0);
if (rescheduleFromInstallmentNo > 0) {
// this will hold the loan repayment installment that is before the
// reschedule start installment
// (rescheduleFrominstallment)
LoanRepaymentScheduleInstallment previousInstallment = null;
// get the install number of the previous installment
int previousInstallmentNo = rescheduleFromInstallmentNo - 1;
// only fetch the installment if the number is greater than 0
if (previousInstallmentNo > 0) {
previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
}
LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);
// the "installment from date" is equal to the due date of the
// previous installment, if it exists
if (previousInstallment != null) {
installmentFromDate = previousInstallment.getDueDate();
}
else {
installmentFromDate = firstInstallment.getFromDate();
}
installmentDueDate = installmentFromDate;
LocalDate periodStartDateApplicableForInterest = installmentFromDate;
Integer periodNumber = 1;
outstandingLoanBalance = loan.getPrincpal();
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {
totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
rescheduleNumberOfRepayments--;
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
while (graceOnPrincipal > 0 || graceOnInterest > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
if (graceOnPrincipal > 0) {
graceOnPrincipal--;
}
if (graceOnInterest > 0) {
graceOnInterest--;
}
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
while (extraTerms > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
extraTerms--;
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
// get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency);
// update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
if (newInterestRate != null) {
loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
}
graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments);
loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments);
loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest);
Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mathContext);
if (!recalculateInterest && newInterestRate == null) {
totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest);
loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
}
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate())
|| period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) || period.isNew()) {
installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms,
false);
if (adjustedDueDate != null && periodNumber == 1) {
installmentDueDate = adjustedDueDate;
}
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
holidayDetailDTO);
final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
period.updatePeriodNumber(installmentNumber);
period.updatePeriodFromDate(installmentFromDate);
period.updatePeriodDueDate(adjustedInstallmentDueDate);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest,
adjustedInstallmentDueDate, periodStartDateApplicableForInterest,
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
// ========================= Calculate the interest due
// ========================================
// change the principal to => Principal Disbursed - Total
// Principal Paid
// interest calculation is always based on the total
// principal outstanding
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
// determine the interest & principal for the period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
totalOutstandingInterestPaymentDueToGrace, daysInInstallment, outstandingBalance, loanApplicationTerms,
periodNumber, mathContext);
// update the interest due for the period
period.updateInterestDue(principalInterestForThisPeriod.interest());
// =============================================================================================
// ========================== Calculate the principal due
// ======================================
// change the principal to => Principal Disbursed - Total
// cumulative Principal Amount before the reschedule
// installment
loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));
principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator,
interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest,
totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, daysInInstallment,
outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
period.updatePrincipalDue(principalInterestForThisPeriod.principal());
// ==============================================================================================
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
period.updateOutstandingLoanBalance(outstandingLoanBalance);
Money principalDue = Money.of(currency, period.principalDue());
Money interestDue = Money.of(currency, period.interestDue());
Money feeChargesDue = Money.of(currency, period.feeChargesDue());
Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
period.updateTotalDue(totalDue);
// update cumulative fields for principal & interest
totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
installmentFromDate = adjustedInstallmentDueDate;
installmentNumber++;
periodNumber++;
loanTermInDays += daysInInstallment;
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
}
final Money totalRepaymentExpected = principal // get the loan Principal
// amount
.plus(actualTotalCumulativeInterest) // add the actual total
// cumulative interest
.plus(loanSummary.getTotalFeeChargesCharged()) // add the total
// fees charged
.plus(loanSummary.getTotalPenaltyChargesCharged()); // finally
// add the
// total
// penalty
// charged
return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays,
loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(),
actualTotalCumulativeInterest.getAmount(), loanSummary.getTotalFeeChargesCharged(),
loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding());
}