http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/fe617123/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index d9e57a0..b380b34 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -36,11 +35,9 @@ import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; -import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; @@ -48,7 +45,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; @@ -56,10 +52,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -96,7 +88,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(); - LoanScheduleParams scheduleParams = null; if (loanScheduleParams == null) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), @@ -114,14 +105,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = scheduleParams .getLoanRepaymentScheduleTransactionProcessor(); - final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, - loanApplicationTerms, chargesDueAtTimeOfDisbursement); + Collection<LoanScheduleModelPeriod> periods = new ArrayList<>(); + if(!scheduleParams.isPartialUpdate()) { + periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, + loanApplicationTerms, chargesDueAtTimeOfDisbursement); + } // Determine the total interest owed over the full loan for FLAT // interest method . - final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( - this.paymentPeriodsInOneYearCalculator, mc); - + if (!scheduleParams.isPartialUpdate()) { + Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( + this.paymentPeriodsInOneYearCalculator, mc); + loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm); + } + boolean isFirstRepayment = true; LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); @@ -167,6 +164,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); + final Collection<LoanTermVariationsData> interestRatesForInstallments = loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment(); // this block is to start the schedule generation from specified date if (scheduleParams.isPartialUpdate()) { @@ -174,7 +172,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.setPrincipal(scheduleParams.getPrincipalToBeScheduled()); } - applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates); + applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates, interestRatesForInstallments); isFirstRepayment = false; } @@ -185,7 +183,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); - + // calculated interest start date for the period LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, @@ -194,7 +192,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // Loan Schedule Exceptions that need to be applied for Loan Account LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, - previousRepaymentDate, scheduledDueDate); + previousRepaymentDate, scheduledDueDate, interestRatesForInstallments, this.paymentPeriodsInOneYearCalculator, mc); scheduledDueDate = termVariationParams.getScheduledDueDate(); // Updates total days in term @@ -244,7 +242,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // for a loan repayment which falls between the // two periods for interest first repayment strategies handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods, - totalInterestChargedForFullLoanTerm, idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, + loanApplicationTerms.getTotalInterestDue(), idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); if (currentPeriodParams.isSkipCurrentLoop()) { @@ -262,10 +260,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), - scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, + scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, scheduledDueDate, interestRates); + // will check for EMI amount greater than interest calculated if (loanApplicationTerms.getFixedEmiAmount() != null @@ -320,7 +319,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, - loanRepaymentScheduleTransactionProcessor, totalInterestChargedForFullLoanTerm, lastRestDate, scheduledDueDate, + loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); periods.add(installment); @@ -960,11 +959,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param priviousScheduledDueDate * @param previousRepaymentDate * @param scheduledDueDate + * @param interestRatesForInstallments + * @param mc + * @param paymentPeriodsInOneYearCalculator + * @param interestRates + * @param periodsApplicableForGrace * @param scheduleDateForReversal * @return */ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, - final LoanScheduleParams scheduleParams, final LocalDate previousRepaymentDate, final LocalDate scheduledDueDate) { + final LoanScheduleParams scheduleParams, final LocalDate previousRepaymentDate, final LocalDate scheduledDueDate, + Collection<LoanTermVariationsData> interestRatesForInstallments, PaymentPeriodsInOneYearCalculator calculator, + MathContext mc) { boolean skipPeriod = false; boolean recalculateAmounts = false; LocalDate modifiedScheduledDueDate = scheduledDueDate; @@ -981,7 +987,28 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanTermVariationsData.setProcessed(true); } } + + for (LoanTermVariationsData variation : interestRatesForInstallments) { + if (variation.isApplicable(modifiedScheduledDueDate) && variation.getDecimalValue() != null && !variation.isProcessed()) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) { + if (loanApplicationTerms.getActualFixedEmiAmount() == null) { + loanApplicationTerms.setFixedEmiAmount(null); + } + } else { + Money totalInterestDueForLoan = Money.zero(loanApplicationTerms.getCurrency()); + loanApplicationTerms.setTotalPrincipalAccounted(scheduleParams.getTotalCumulativePrincipal()); + totalInterestDueForLoan = loanApplicationTerms.calculateTotalInterestCharged(calculator, mc); + totalInterestDueForLoan = totalInterestDueForLoan.plus(scheduleParams.getTotalCumulativeInterest()); + loanApplicationTerms.updateTotalInterestDue(totalInterestDueForLoan); + // exclude till last period in calculations + loanApplicationTerms.updateExcludePeriodsForCalculation(scheduleParams.getPeriodNumber() - 1); + } + variation.setProcessed(true); + } + } + while (loanApplicationTerms.getLoanTermVariations().hasVariation(modifiedScheduledDueDate)) { LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextVariation(); if (loanTermVariationsData.isProcessed()) { @@ -1026,6 +1053,33 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } loanTermVariationsData.setProcessed(true); break; + case EXTEND_REPAYMENT_PERIOD: + Integer rescheduleNumberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); + rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); + loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); + LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, loanApplicationTerms.getHolidayDetailDTO()); + loanApplicationTerms.updateLoanEndDate(loanEndDate); + loanApplicationTerms.updateAccountedTillPeriod(scheduleParams.getPeriodNumber() - 1, + scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getTotalCumulativeInterest(), + loanTermVariationsData.getDecimalValue().intValue()); + loanApplicationTerms.setFixedEmiAmount(null); + loanTermVariationsData.setProcessed(true); + break; + case GRACE_ON_PRINCIPAL: + loanApplicationTerms.updatePrincipalGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer interestPaymentGrace = 0; + loanApplicationTerms.updateInterestPaymentGrace(interestPaymentGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(scheduleParams.getPeriodNumber()); + loanTermVariationsData.setProcessed(true); + break; + case GRACE_ON_INTEREST: + loanApplicationTerms.updateInterestPaymentGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer principalGrace = 0; + loanApplicationTerms.updatePrincipalGrace(principalGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(scheduleParams.getPeriodNumber()); + loanApplicationTerms.updateTotalInterestAccounted(scheduleParams.getTotalCumulativeInterest()); + loanTermVariationsData.setProcessed(true); + break; default: break; @@ -1040,14 +1094,33 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param loanApplicationTerms * @param scheduledDueDate * @param exceptionDataListIterator + * @param instalmentNumber + * @param totalCumulativePrincipal TODO + * @param totalCumulativeInterest TODO + * @param mc TODO + * @param periodNumber * @return */ private LoanTermVariationParams applyExceptionLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, - final LocalDate scheduledDueDate, final ListIterator<LoanTermVariationsData> exceptionDataListIterator) { + final LocalDate scheduledDueDate, final ListIterator<LoanTermVariationsData> exceptionDataListIterator, int instalmentNumber, + Money totalCumulativePrincipal, Money totalCumulativeInterest, MathContext mc) { boolean skipPeriod = false; boolean recalculateAmounts = false; LocalDate modifiedScheduledDueDate = scheduledDueDate; ArrayList<LoanTermVariationsData> variationsData = new ArrayList<>(); + + for (LoanTermVariationsData variation : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) { + if (variation.isApplicable(modifiedScheduledDueDate) && variation.getDecimalValue() != null && !variation.isProcessed()) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) { + adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, instalmentNumber, mc); + } else { + loanApplicationTerms.setTotalPrincipalAccounted(totalCumulativePrincipal); + loanApplicationTerms.updateExcludePeriodsForCalculation(instalmentNumber - 1); + } + variation.setProcessed(true); + } + } while (loanApplicationTerms.getLoanTermVariations().hasExceptionVariation(modifiedScheduledDueDate, exceptionDataListIterator)) { LoanTermVariationsData loanTermVariationsData = exceptionDataListIterator.next(); @@ -1057,14 +1130,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener switch (loanTermVariationsData.getTermVariationType()) { case INSERT_INSTALLMENT: modifiedScheduledDueDate = loanTermVariationsData.getTermApplicableFrom(); - variationsData.add(loanTermVariationsData) ; + variationsData.add(loanTermVariationsData); break; case DELETE_INSTALLMENT: if (loanTermVariationsData.getTermApplicableFrom().isEqual(modifiedScheduledDueDate)) { skipPeriod = true; - variationsData.add(loanTermVariationsData) ; + variationsData.add(loanTermVariationsData); } - + break; + case GRACE_ON_PRINCIPAL: + loanApplicationTerms.updatePrincipalGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer interestPaymentGrace = 0; + loanApplicationTerms.updateInterestPaymentGrace(interestPaymentGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(instalmentNumber); + variationsData.add(loanTermVariationsData); + break; + case GRACE_ON_INTEREST: + loanApplicationTerms.updateInterestPaymentGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer principalGrace = 0; + loanApplicationTerms.updatePrincipalGrace(principalGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(instalmentNumber); + loanApplicationTerms.updateTotalInterestAccounted(totalCumulativeInterest); + variationsData.add(loanTermVariationsData); + break; + case EXTEND_REPAYMENT_PERIOD: + Integer rescheduleNumberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); + rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); + loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); + // generate list of proposed schedule due dates + LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, + loanApplicationTerms.getHolidayDetailDTO()); + loanApplicationTerms.updateLoanEndDate(loanEndDate); + adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, instalmentNumber, mc); + loanTermVariationsData.setProcessed(true); + loanApplicationTerms.updateAccountedTillPeriod(instalmentNumber-1, totalCumulativePrincipal, totalCumulativeInterest, loanTermVariationsData.getDecimalValue().intValue()); break; default: break; @@ -1080,9 +1179,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param loanApplicationTerms * @param scheduleParams * @param interestRates + * @param interestRatesForInstallments + * @param periodsApplicableForGrace */ private void applyLoanVariationsForPartialScheduleGenerate(final LoanApplicationTerms loanApplicationTerms, - LoanScheduleParams scheduleParams, final Collection<LoanTermVariationsData> interestRates) { + LoanScheduleParams scheduleParams, final Collection<LoanTermVariationsData> interestRates, + final Collection<LoanTermVariationsData> interestRatesForInstallments) { // Applies loan variations while (loanApplicationTerms.getLoanTermVariations().hasVariation(scheduleParams.getPeriodStartDate())) { LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextVariation(); @@ -1109,6 +1211,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); } } + + // Applies interest rate changes for installments + for (LoanTermVariationsData variation : interestRatesForInstallments) { + if (variation.getTermVariationType().isInterestRateFromInstallment() && variation.isApplicable(scheduleParams.getPeriodStartDate()) + && variation.getDecimalValue() != null) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + variation.setProcessed(true); + } + } } /** @@ -1189,6 +1300,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Collection<LoanTermVariationsData> applicableVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); Money uncompoundedFromLastInstallment = params.getUnCompoundedAmount(); LocalDate additionalPeriodsStartDate = params.getPeriodStartDate(); + do { params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(), @@ -1327,18 +1439,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return applicableTransactions; } - private Collection<LoanTermVariationsData> getApplicableTermVariationsForPeriod(final LocalDate fromDate, final LocalDate dueDate, - final Collection<LoanTermVariationsData> variations) { - Collection<LoanTermVariationsData> applicableVariations = new ArrayList<>(); - for (LoanTermVariationsData detail : variations) { - if (detail.isApplicable(fromDate, dueDate)) { - applicableVariations.add(detail); - } - } - variations.removeAll(applicableVariations); - return applicableVariations; - } - private List<LoanTransaction> createCurrentTransactionList(RecalculationDetail detail) { List<LoanTransaction> currentTransactions = new ArrayList<>(2); currentTransactions.add(detail.getTransaction()); @@ -1511,7 +1611,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final boolean isPastDate = params.applyInterestRecalculation() && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); boolean periodHasCompoundingDate = false; - Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + Money amountCharged = Money.zero(currency); + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod() != null) { + amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + } final Map<LocalDate, Money> compoundingMap = params.getCompoundingMap(); LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate(); if (loanApplicationTerms.allowCompoundingOnEod()) { @@ -1756,316 +1859,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } - @Override - public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, - final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) { - - 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, restCalendarInstance, compoundingCalendarInstance, loanCalendar, - floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays, holidayDetailDTO); - - // for applying variations - Collection<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); - - // update the number of repayments - loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); - - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); - LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations().fetchLoanTermDueDateVariationsData( - loanEndDate); - if (lastDueDateVariation != null) { - loanEndDate = lastDueDateVariation.getDateValue(); - } - 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, holidayDetailDTO); - - 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); - - // for applying variations - Collection<LoanTermVariationsData> applicableVariations = getApplicableTermVariationsForPeriod(installmentFromDate, - adjustedInstallmentDueDate, loanTermVariations); - - // determine the interest & principal for the period - PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, - totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, - totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, - null, null, installmentFromDate, adjustedInstallmentDueDate, applicableVariations); - - // 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, outstandingBalance, - loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate, - applicableVariations); - - 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()); - - if (principalDue.isZero() && interestDue.isZero()) { - period.updateFeeChargesDue(Money.zero(currency)); - period.updatePenaltyChargesDue(Money.zero(currency)); - } - - 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()); - } - public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, @@ -2230,28 +2023,28 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * fields to generate the schedule */ @Override - public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) { + public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, Loan loan, + final HolidayDetailDTO holidayDetailDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom) { // Fixed schedule End Date for generating schedule final LocalDate scheduleTillDate = null; - return rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, transactions, - loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom, scheduleTillDate); + return rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, + rescheduleFrom, scheduleTillDate); } private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom, + Loan loan, final HolidayDetailDTO holidayDetailDTO, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final LocalDate rescheduleFrom, final LocalDate scheduleTillDate) { // Loan transactions to process and find the variation on payments Collection<RecalculationDetail> recalculationDetails = new ArrayList<>(); - for (LoanTransaction loanTransaction : transactions) { - recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction - .copyTransactionProperties(loanTransaction))); + for (LoanTransaction loanTransaction : loan.getLoanTransactions()) { + if (loanTransaction.isPaymentTransaction()) { + recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction + .copyTransactionProperties(loanTransaction))); + } } final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled(); @@ -2271,10 +2064,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // this is required to update total fee amounts in the // LoanScheduleModel - final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); + final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.charges()); periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(), loanApplicationTerms, chargesDueAtTimeOfDisbursement); - final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>(); MonetaryCurrency currency = outstandingBalance.getCurrency(); // early payments will be added here and as per the selected @@ -2302,7 +2094,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; - if (loanApplicationTerms.getRestCalendarInstance() != null) { + if (loanApplicationTerms.isInterestRecalculationEnabled()) { lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); @@ -2323,6 +2115,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate(); // Set fixed Amortization Amounts(either EMI or Principal ) updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance); + + // count periods without interest grace to exclude for flat loan calculations final Map<LocalDate, Money> disburseDetailMap = new HashMap<>(); if (loanApplicationTerms.isMultiDisburseLoan()) { @@ -2340,12 +2134,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List<LoanTermVariationsData> exceptionDataList = loanApplicationTerms.getLoanTermVariations().getExceptionData(); final ListIterator<LoanTermVariationsData> exceptionDataListIterator = exceptionDataList.listIterator(); LoanTermVariationParams loanTermVariationParams = null; + + // identify retain installments + final List<LoanRepaymentScheduleInstallment> processInstallmentsInstallments = fetchRetainedInstallments( + loan.getRepaymentScheduleInstallments(), rescheduleFrom, currency); + final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>(); // Block process the installment and creates the period if it falls // before reschedule from date // This will create the recalculation details by applying the // transactions - for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { + for (LoanRepaymentScheduleInstallment installment : processInstallmentsInstallments) { // this will generate the next schedule due date and allows to // process the installment only if recalculate from date is // greater than due date @@ -2353,32 +2152,44 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { break; } - LocalDate previousRepaymentDate = actualRepaymentDate; ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>(); + // check for date changes - while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { - LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); - if (!variation.isSpecificToInstallment()) { - actualRepaymentDate = variation.getDateValue(); - } - dueDateVariationsDataList.add(variation); - } - + do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); + while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { + LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); + if (!variation.isSpecificToInstallment()) { + actualRepaymentDate = variation.getDateValue(); + /*if (!isDueDateChangeApplied) { + previousRepaymentDate = actualRepaymentDate; + isDueDateChangeApplied = true; + }*/ + lastInstallmentDate = actualRepaymentDate; + } + dueDateVariationsDataList.add(variation); + } loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate, - exceptionDataListIterator); + exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc); } while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()); - if (!lastInstallmentDate.isBefore(rescheduleFrom)) { + /*if (!lastInstallmentDate.isBefore(rescheduleFrom)) { actualRepaymentDate = previousRepaymentDate; + if(isDueDateChangeApplied){ + int numberOfDateChangesApplied = dueDateVariationsDataList.size(); + while(numberOfDateChangesApplied >0 ){ + loanApplicationTerms.getLoanTermVariations().previousDueDateVariation(); + numberOfDateChangesApplied--; + } + } break; - } + }*/ periodNumber++; for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) { @@ -2422,66 +2233,81 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency)); instalmentNumber++; loanTermInDays = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays(); + + + if (loanApplicationTerms.isInterestRecalculationEnabled()) { + + // populates the collection with transactions till the due + // date + // of + // the period for interest recalculation enabled loans + Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(applyInterestRecalculation, + installment.getDueDate(), recalculationDetails); + + // calculates the expected principal value for this + // repayment + // schedule + Money principalPortionCalculated = principalToBeScheduled.zero(); + if (!installment.isRecalculatedInterestComponent()) { + principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency), + loanApplicationTerms); + } - // populates the collection with transactions till the due date - // of - // the period for interest recalculation enabled loans - Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(applyInterestRecalculation, - installment.getDueDate(), recalculationDetails); - - // calculates the expected principal value for this repayment - // schedule - Money principalPortionCalculated = principalToBeScheduled.zero(); - if (!installment.isRecalculatedInterestComponent()) { - principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency), - loanApplicationTerms); - } + // expected principal considering the previously paid excess + // amount + Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); + if (actualPrincipalPortion.isLessThanZero()) { + actualPrincipalPortion = principalPortionCalculated.zero(); + } - // expected principal considering the previously paid excess - // amount - Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); - if (actualPrincipalPortion.isLessThanZero()) { - actualPrincipalPortion = principalPortionCalculated.zero(); - } + Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, + installment, applicableTransactions, actualPrincipalPortion); + + // this block is to adjust the period number based on the + // actual + // schedule due date and installment due date + // recalculatedInterestComponent installment shouldn't be + // considered while calculating fixed EMI amounts + int period = periodNumber; + if (!lastInstallmentDate.isEqual(installment.getDueDate())) { + period--; + } + reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, + loanApplicationTerms, totalCumulativePrincipal, period, mc); + // Updates principal paid map with efective date for + // reducing + // the amount from outstanding balance(interest calculation) + LocalDate amountApplicableDate = null; + if (loanApplicationTerms.getRestCalendarInstance() != null) { + amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + } - Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, - installment, applicableTransactions, actualPrincipalPortion); - - // this block is to adjust the period number based on the actual - // schedule due date and installment due date - // recalculatedInterestComponent installment shouldn't be - // considered while calculating fixed EMI amounts - int period = periodNumber; - if (!lastInstallmentDate.isEqual(installment.getDueDate())) { - period--; - } - reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, - loanApplicationTerms, totalCumulativePrincipal, period, mc); - // Updates principal paid map with efective date for reducing - // the amount from outstanding balance(interest calculation) - LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, - holidayDetailDTO); - // updates map with the installment principal amount excluding - // unprocessed amount since this amount is already accounted. - updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); - uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, - principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, - holidayDetailDTO); - // update outstanding balance for interest calculation - outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), - outstandingBalanceAsPerRest, false); - outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), - outstandingBalanceAsPerRest, true); + // updates map with the installment principal amount + // excluding + // unprocessed amount since this amount is already + // accounted. + updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); + uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, + principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, + holidayDetailDTO); + // update outstanding balance for interest calculation + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), + outstandingBalanceAsPerRest, false); + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), + outstandingBalanceAsPerRest, true); + // updates the map with over due amounts + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, + newRepaymentScheduleInstallments, true, lastRestDate); + } else { + outstandingBalanceAsPerRest = outstandingBalance; + } } totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest).plus(totalFeeChargesCharged) .plus(totalPenaltyChargesCharged); - // updates the map with over due amounts - updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, - newRepaymentScheduleInstallments, true, lastRestDate); - // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) { Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); @@ -2494,6 +2320,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); + loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getLoanSummary().getTotalInterestCharged())); + }else{ + loanApplicationTerms.getLoanTermVariations().resetVariations(); } } @@ -2501,9 +2330,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (loanScheduleParams == null) { loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); + periods.clear(); } - - LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleParams); + LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.charges(), holidayDetailDTO, loanScheduleParams); for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { // adding newly created repayment periods to installments @@ -2515,6 +2344,41 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); } + public List<LoanRepaymentScheduleInstallment> fetchRetainedInstallments( + final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom, + MonetaryCurrency currency) { + List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>(); + int lastInterestAvilablePeriod = 0; + int processedPeriod = 0; + for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { + if (installment.getDueDate().isBefore(rescheduleFrom)) { + newRepaymentScheduleInstallments.add(installment); + if(installment.getInterestCharged(currency).isGreaterThanZero()){ + lastInterestAvilablePeriod = installment.getInstallmentNumber(); + } + processedPeriod = installment.getInstallmentNumber(); + } else { + break; + } + } + + // this block is to remove the periods till last interest available + // period. + // if the last retained period is interest grace period then we + // can't get the interest of last period without calculating again + // to fix this adjusting retained periods + if(lastInterestAvilablePeriod != processedPeriod){ + final List<LoanRepaymentScheduleInstallment> retainRepaymentScheduleInstallments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment installment : newRepaymentScheduleInstallments) { + if(installment.getInstallmentNumber() <= lastInterestAvilablePeriod){ + retainRepaymentScheduleInstallments.add(installment); + } + } + newRepaymentScheduleInstallments.retainAll(retainRepaymentScheduleInstallments); + } + return newRepaymentScheduleInstallments; + } + /** * Method identifies the early paid amounts for a installment and update the * principal map for further calculations @@ -2630,7 +2494,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener earlyPaidAmount = earlyPaidAmount.zero(); } - if (isEarlyPaid) { + if (isEarlyPaid && applicationTerms.getRescheduleStrategyMethod() != null) { switch (applicationTerms.getRescheduleStrategyMethod()) { case REDUCE_EMI_AMOUNT: adjustInstallmentOrPrincipalAmount(applicationTerms, totalCumulativePrincipal, periodNumber, mc); @@ -2727,21 +2591,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener */ @Override public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate, - final LoanApplicationTerms loanApplicationTerms, final MathContext mc, final Set<LoanCharge> charges, - final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> loanTransactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) { + final LoanApplicationTerms loanApplicationTerms, final MathContext mc, Loan loan, final HolidayDetailDTO holidayDetailDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { LocalDate calculateTill = onDate; if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { calculateTill = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } - LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, charges, holidayDetailDTO, loanTransactions, - loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, onDate, calculateTill); + LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, + onDate, calculateTill); + List<LoanTransaction> loanTransactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, - currency, loanScheduleDTO.getInstallments(), charges); + currency, loanScheduleDTO.getInstallments(), loan.charges()); Money feeCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency); Money totalPrincipal = Money.zero(currency); @@ -2759,15 +2622,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } - /** - * set the value to zero if the provided value is null - * - * @return integer value equal/greater than 0 - **/ - private Integer defaultToZeroIfNull(Integer value) { - - return (value != null) ? value : 0; - } private final class LoanTermVariationParams {
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/fe617123/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index 1bbefa4..1bbf261 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -73,6 +73,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size()); + for (LoanTermVariationsData loanTermVariation : termVariations) { if (loanTermVariation.getTermVariationType().isInterestRateVariation() && loanTermVariation.isApplicable(periodStartDate, periodEndDate)) { @@ -86,6 +87,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } } + if (principalVariation != null) { for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) { @@ -99,6 +101,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); interestStartDate = principal.getKey(); + } Money compoundFee = totalCumulativePrincipal.zero(); if (compoundingMap.containsKey(principal.getKey())) { @@ -118,13 +121,14 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); } } - } } + final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, balanceForInterestCalculation, interestStartDate, periodEndDate); + interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/fe617123/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java index 31cdbe5..5f3d8d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java @@ -32,11 +32,12 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener @Override public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, - final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, + Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, @SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation, @SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, @SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) { + Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance, periodNumber, mc, null); @@ -54,6 +55,11 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment, totalCumulativePrincipalToDate, periodNumber); + // totalCumulativeInterest from partial schedule generation for multi rescheduling + /*if (loanApplicationTerms.getPartialTotalCumulativeInterest() != null && loanApplicationTerms.getTotalInterestDue() != null) { + totalInterestDueForLoan = loanApplicationTerms.getTotalInterestDue(); + totalInterestDueForLoan = totalInterestDueForLoan.plus(loanApplicationTerms.getPartialTotalCumulativeInterest()); + }*/ interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment, totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber);
