http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/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 2f8b728..d06b3f1 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 @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; 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; @@ -124,7 +125,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); + loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate, + loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); if (!scheduleParams.isPartialUpdate()) { // Set Fixed Principal Amount @@ -203,13 +205,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { throw new ScheduleDateException( "Due date can't be before period start date", scheduledDueDate); } - if (!scheduleParams.getLatePaymentMap().isEmpty()) { - populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, currentDate, loanApplicationTerms, - holidayDetailDTO, scheduleParams.getCompoundingMap(), loanCharges, currency); - scheduleParams.getCompoundingDateVariations().put(scheduleParams.getPeriodStartDate(), - new TreeMap<>(scheduleParams.getCompoundingMap())); - } - if (extendTermForDailyRepayments) { scheduleParams.setActualRepaymentDate(scheduledDueDate); } @@ -220,6 +215,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledDueDate = scheduleParams.getScheduleTillDate(); isNextRepaymentAvailable = false; } + if (loanApplicationTerms.isInterestRecalculationEnabled()) { + populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, + holidayDetailDTO, scheduleParams, loanCharges, currency); + } // populates the collection with transactions till the due date of // the period for interest recalculation enabled loans @@ -316,6 +315,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), totalInstallmentDue, false); + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, @@ -327,7 +327,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, - installment); + installment, lastRestDate); // handle cumulative fields @@ -337,7 +337,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.setPeriodStartDate(scheduledDueDate); scheduleParams.incrementInstalmentNumber(); scheduleParams.incrementPeriodNumber(); - scheduleParams.getCompoundingDateVariations().clear(); if (termVariationParams.isRecalculateAmounts()) { loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null); loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null); @@ -377,6 +376,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO; + updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), @@ -384,6 +384,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalOutstanding); } + private void updateCompoundingDetails(final Collection<LoanScheduleModelPeriod> periods, final LoanScheduleParams params, + final LoanApplicationTerms loanApplicationTerms) { + final Map<LocalDate, Map<LocalDate, Money>> compoundingDetails = params.getCompoundingDateVariations(); + if (compoundingDetails.isEmpty()) { return; } + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod.isRepaymentPeriod() && loanScheduleModelPeriod.getLoanCompoundingDetails().isEmpty()) { + Map<LocalDate, Money> periodCompoundingDetails = compoundingDetails.get(loanScheduleModelPeriod.periodFromDate()); + if (periodCompoundingDetails != null) { + for (Map.Entry<LocalDate, Money> entry : periodCompoundingDetails.entrySet()) { + if (entry.getValue().isGreaterThanZero() && !entry.getKey().isAfter(loanScheduleModelPeriod.periodDueDate())) { + LocalDate effectiveDate = entry.getKey(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveDate = effectiveDate.minusDays(1); + } + LoanInterestRecalcualtionAdditionalDetails additionalDetails = new LoanInterestRecalcualtionAdditionalDetails( + effectiveDate, entry.getValue().getAmount()); + loanScheduleModelPeriod.getLoanCompoundingDetails().add(additionalDetails); + } + } + } + } + } + } + private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams) { PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), @@ -422,13 +446,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams, - LoanScheduleModelPeriod installment) { + LoanScheduleModelPeriod installment, LocalDate lastRestDate) { LocalDate amountApplicableDate = installment.periodDueDate(); if (loanApplicationTerms.isInterestRecalculationEnabled()) { amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); } updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest calculation updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate); @@ -445,7 +470,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LoanScheduleModelPeriod modifiedInstallment = installment; if (scheduleParams.applyInterestRecalculation() && loanRepaymentScheduleTransactionProcessor != null) { Money principalProcessed = Money.zero(currency); - addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), modifiedInstallment); for (RecalculationDetail detail : applicableTransactions) { if (!detail.isProcessed()) { LocalDate transactionDate = detail.getTransactionDate(); @@ -489,10 +513,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + adjustCompoundedAmountWithPaidDetail(scheduleParams, lastRestDate, currentTransactions, loanApplicationTerms, + holidayDetailDTO); } } updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate, - scheduleParams.getInstallments(), true, lastRestDate, scheduleParams.getCompoundingMap()); + scheduleParams.getInstallments(), true, lastRestDate); currentPeriodParams.minusPrincipalForThisPeriod(principalProcessed); } return modifiedInstallment; @@ -600,9 +626,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.getCompoundingMap().clear(); scheduleParams.getCompoundingMap().putAll( scheduleParams.getCompoundingDateVariations().get(periodStartDateApplicableForInterest)); - } else { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); } } @@ -656,10 +679,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date - if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); - } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams @@ -700,6 +719,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, true); periods.add(installment); + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest // calculation as per the rest @@ -716,10 +737,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate(); updateLatePaymentMap = true; scheduleParams.incrementInstalmentNumber(); + populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, + holidayDetailDTO, scheduleParams, loanCharges, currency); // creates and insert Loan repayment schedule // for // the period - addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + } else if (installment == null) { installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1); } @@ -762,12 +785,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // identify late payments and add compounding // details to // map for interest calculation - handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, - periodStartDateApplicableForInterest, detail); + handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); if (updateLatePaymentMap) { updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), - scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate, - scheduleParams.getCompoundingMap()); + scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate); } } else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, @@ -776,9 +797,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail); Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor().handleRepaymentSchedule( currentTransactions, currency, scheduleParams.getInstallments()); - Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction()); + Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { - arrears = getTotalAmount(scheduleParams.getLatePaymentMap(), currency); updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate); currentPeriodParams.plusEarlyPaidAmount(unprocessed); @@ -790,10 +810,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .calculateTillRestFrequencyEnabled()) { LocalDate calculateTill = transactionDate; - if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); - } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams @@ -836,8 +852,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) { - handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, - periodStartDateApplicableForInterest, detail); + handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); } } @@ -862,12 +877,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param detail */ private void handleLatePayments(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate, - LocalDate periodStartDateApplicableForInterest, RecalculationDetail detail) { + final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate, RecalculationDetail detail) { updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, lastRestDate, scheduleParams); - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); } private void updateAmountsBasedOnEarlyPayment(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, @@ -1064,9 +1076,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return isAmountChanged; } - private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + private Money fetchArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, final LoanTransaction transaction) { Money arrears = transaction.getPrincipalPortion(currency); + arrears = arrears.plus(fetchCompoundedArrears(loanApplicationTerms, currency, transaction)); + return arrears; + } + + private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + final LoanTransaction transaction) { + Money arrears = Money.zero(currency); if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { arrears = arrears.plus(transaction.getInterestPortion(currency)); } @@ -1096,7 +1115,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Collection<RecalculationDetail> transactions, final Set<LoanCharge> loanCharges, final LoanScheduleParams params) { boolean isFirstRepayment = false; LocalDate startDate = params.getPeriodStartDate(); - Money outstanding = Money.zero(currency); + Money outstanding = params.getOutstandingBalanceAsPerRest(); Money totalInterest = Money.zero(currency); Money totalCumulativeInterest = Money.zero(currency); double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); @@ -1111,15 +1130,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (params.getActualRepaymentDate().isAfter(currentDate)) { params.setActualRepaymentDate(currentDate); } - outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding); Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod( params.applyInterestRecalculation(), params.getActualRepaymentDate(), transactions); - if (!params.getLatePaymentMap().isEmpty()) { - populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), currentDate, - loanApplicationTerms, holidayDetailDTO, params.getCompoundingMap(), loanCharges, currency); - } + populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), loanApplicationTerms, + holidayDetailDTO, params, loanCharges, currency); for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { @@ -1146,22 +1162,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); totalInterest = totalInterest.zero(); addLoanRepaymentScheduleInstallment(params.getInstallments(), installment); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, params, lastRestDate, transactionDate); + populateCompoundingDatesInPeriod(installment.periodDueDate(), params.getActualRepaymentDate(), loanApplicationTerms, + holidayDetailDTO, params, loanCharges, currency); + params.setCompoundedInLastInstallment(params.getUnCompoundedAmount()); params.setPeriodStartDate(transactionDate); startDate = transactionDate; } loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments()); + updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, + lastRestDate, params); updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, params.getLatePaymentMap(), currentDate, - params.getInstallments(), false, lastRestDate, params.getCompoundingMap()); - outstanding = outstanding.zero(); - outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding); - outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(), - outstanding, false); - if (params.getLatePaymentMap().isEmpty() && !outstanding.isGreaterThanZero()) { + params.getInstallments(), false, lastRestDate); + if (params.getLatePaymentMap().isEmpty() && isCompleted(params.getInstallments())) { + outstanding = outstanding.zero(); + } else { + outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(), + outstanding, false); + } + if (params.getLatePaymentMap().isEmpty() && outstanding.isZero()) { break; } } - if (outstanding.isGreaterThanZero()) { + if (!outstanding.isZero()) { PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, @@ -1169,15 +1193,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener params.getActualRepaymentDate(), applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); - if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { - LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(params.getActualRepaymentDate().minusDays(1), - loanApplicationTerms, holidayDetailDTO); - params.getLatePaymentMap().put(compoundingEffectiveDate, interest); + Money uncompounded = params.getUnCompoundedAmount(); + Money compounded = uncompounded.zero(); + for (Map.Entry<LocalDate, Money> mapEntry : params.getCompoundingMap().entrySet()) { + if (mapEntry.getKey().isAfter(params.getPeriodStartDate())) { + compounded = compounded.plus(mapEntry.getValue()); + } + } + Money compoundedForThisPeriod = compounded.minus(uncompounded); + Money uncompoundedForThisPeriod = interest.minus(compoundedForThisPeriod); + params.setUnCompoundedAmount(uncompoundedForThisPeriod); + LocalDate compoundingDate = params.getPeriodStartDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + compoundingDate = compoundingDate.minusDays(1); + } + compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); + if(compoundingDate.isEqual(params.getActualRepaymentDate())){ + params.getCompoundingMap().put(compoundingDate, uncompoundedForThisPeriod); + params.setUnCompoundedAmount(uncompoundedForThisPeriod.zero()); } + + } params.setPeriodStartDate(params.getActualRepaymentDate()); - } while (params.getActualRepaymentDate().isBefore(currentDate) && outstanding.isGreaterThanZero()); + } while (params.getActualRepaymentDate().isBefore(currentDate) && !outstanding.isZero()); if (totalInterest.isGreaterThanZero()) { LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), @@ -1185,11 +1225,23 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalInterest.zero(), totalInterest.zero(), totalInterest, true); params.incrementInstalmentNumber(); periods.add(installment); + params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap())); totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); } return totalCumulativeInterest; } + private boolean isCompleted(List<LoanRepaymentScheduleInstallment> installments) { + boolean isCompleted = true; + for (LoanRepaymentScheduleInstallment installment : installments) { + if (installment.isNotFullyPaidOff()) { + isCompleted = false; + break; + } + } + return isCompleted; + } + private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation, LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) { Collection<RecalculationDetail> applicableTransactions = new ArrayList<>(); @@ -1221,21 +1273,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener currentTransactions.add(detail.getTransaction()); detail.setProcessed(true); return currentTransactions; - } - private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map<LocalDate, Money> latePaymentMap, Money outstanding) { - Map<LocalDate, Money> retainEntries = new HashMap<>(); - for (Map.Entry<LocalDate, Money> mapEntry : latePaymentMap.entrySet()) { - if (!mapEntry.getKey().isAfter(periodStartDate)) { - outstanding = outstanding.plus(mapEntry.getValue()); - } else { - retainEntries.put(mapEntry.getKey(), mapEntry.getValue()); - } - } - latePaymentMap.clear(); - latePaymentMap.putAll(retainEntries); - retainEntries.clear(); - return outstanding; } /** @@ -1315,22 +1353,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener holidayDetailDTO); Money principalPortion = loanTransaction.getPrincipalPortion(currency); - Money compoundedLatePayments = Money.zero(currency); - if (applicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { - compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getInterestPortion(currency)); - } - if (applicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) { - compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getFeeChargesPortion(currency)).plus( - loanTransaction.getPenaltyChargesPortion(currency)); - } - updateCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, principalPortion, - applicableDate); - updateCompoundingAmount(params.getPrincipalPortionMap(), params.getCompoundingMap(), currency, lastRestDate, - compoundedLatePayments, applicableDate); + updateLatePaymentCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, + principalPortion, applicableDate); + adjustCompoundedAmountWithPaidDetail(params, lastRestDate, applicableDate, loanTransaction, applicationTerms); } - private void updateCompoundingAmount(final Map<LocalDate, Money> principalVariationMap, + private void updateLatePaymentCompoundingAmount(final Map<LocalDate, Money> principalVariationMap, final Map<LocalDate, Money> latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate, Money compoundedPortion, final LocalDate applicableDate) { Money appliedOnPrincipalVariationMap = Money.zero(currency); @@ -1368,17 +1397,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener */ private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Map<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate, - List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate, - final TreeMap<LocalDate, Money> compoundingMap) { + List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate) { latePaymentMap.clear(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); Money totalCompoundingAmount = Money.zero(currency); - Money compoundedMoney = Money.zero(currency); - if (!compoundingMap.isEmpty()) { - compoundedMoney = compoundingMap.get(lastRestDate); - } - boolean clearCompoundingMap = true; for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { if (loanRepaymentScheduleInstallment.isNotFullyPaidOff() && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate) @@ -1395,104 +1418,181 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency)); } - final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, - compoundingMap, loanRepaymentScheduleInstallment, lastRestDate, compoundedMoney, scheduledDueDate); - if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) { - compoundedMoney = changedCompoundedMoney; - clearCompoundingMap = false; - } } } if (totalCompoundingAmount.isGreaterThanZero()) { updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate); } - if (clearCompoundingMap) { - compoundingMap.clear(); - } } - private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final MonetaryCurrency currency, final TreeMap<LocalDate, Money> compoundingMap, - final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate, - final Money compoundedMoney, final LocalDate scheduledDueDate) { - Money ignoreMoney = compoundedMoney; - if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { - LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), - loanApplicationTerms, holidayDetailDTO); + private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) { + if (loanApplicationTerms.isInterestRecalculationEnabled() + && loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + final MonetaryCurrency currency = params.getCurrency(); + Money totalCompoundedAmount = Money.zero(currency); + boolean lastInstallmentIsPastDate = false; + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) { + if (params.getCompoundingDateVariations().containsKey(loanRepaymentScheduleInstallment.getFromDate())) { + lastInstallmentIsPastDate = params.applyInterestRecalculation() + && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); + } else { + final boolean isPastDate = params.applyInterestRecalculation() + && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); + boolean periodHasCompoundingDate = false; + Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + final Map<LocalDate, Money> compoundingMap = params.getCompoundingMap(); + LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate().minusDays(1); + } + LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(effectiveStartDate, loanApplicationTerms, + holidayDetailDTO); + final LocalDate restDate = getNextRestScheduleDate(scheduledDueDate.minusDays(1), loanApplicationTerms, + holidayDetailDTO); + if (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + totalCompoundedAmount = totalCompoundedAmount.minus(params.getUnCompoundedAmount()); + periodHasCompoundingDate = true; + } + while (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + if (compoundingEffectiveDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) { + Money amountToBeCompounding = amountCharged.minus(totalCompoundedAmount); + updateMapWithAmount(compoundingMap, amountToBeCompounding, compoundingEffectiveDate); + totalCompoundedAmount = totalCompoundedAmount.plus(amountToBeCompounding); + + } else if (compoundingMap.containsKey(compoundingEffectiveDate)) { + Money compounedAmount = compoundingMap.get(compoundingEffectiveDate); + totalCompoundedAmount = totalCompoundedAmount.plus(compounedAmount); + } - if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) { - Money amount = Money.zero(currency); - switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { - case INTEREST: - amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); - break; - case FEE: - amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); - break; - case INTEREST_AND_FEE: - amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); - break; - default: - break; - } - if (compoundingEffectiveDate.isBefore(scheduledDueDate)) { - ignoreMoney = ignoreMoney.plus(amount); - if (ignoreMoney.isGreaterThanZero()) { - updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate); - updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); - ignoreMoney = ignoreMoney.zero(); + if (!loanApplicationTerms.allowCompoundingOnEod()) { + compoundingEffectiveDate = compoundingEffectiveDate.plusDays(1); + } + compoundingEffectiveDate = getNextCompoundScheduleDate(compoundingEffectiveDate, loanApplicationTerms, + holidayDetailDTO); } - } else { - if (ignoreMoney.isLessThanZero()) { - LocalDate firstKey = compoundingMap.firstKey(); - updateMapWithAmount(compoundingMap, ignoreMoney, firstKey); - updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); - ignoreMoney = ignoreMoney.zero(); + if (periodHasCompoundingDate) { + if (isPastDate) { + updateMapWithAmount(params.getPrincipalPortionMap(), + totalCompoundedAmount.plus(params.getUnCompoundedAmount()), lastRestDate); + } else { + Money amountToBeEffected = amountCharged; + if (lastInstallmentIsPastDate) { + amountToBeEffected = amountToBeEffected.plus(params.getUnCompoundedAmount()); + } + updateMapWithAmount(params.getPrincipalPortionMap(), amountToBeEffected, restDate); + } + } + if (totalCompoundedAmount.isGreaterThanZero()) { + params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(), + new TreeMap<>(params.getCompoundingMap())); + for (Map.Entry<LocalDate, Money> mapEntry : params.getCompoundingMap().entrySet()) { + if (!mapEntry.getKey().isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + updateMapWithAmount(params.getPrincipalPortionMap(), mapEntry.getValue().negated(), mapEntry.getKey()); + }else if(params.getUnCompoundedAmount().isEqualTo( mapEntry.getValue())){ + totalCompoundedAmount = totalCompoundedAmount.plus(params.getUnCompoundedAmount()); + } + } + params.minusUnCompoundedAmount(params.getUnCompoundedAmount()); + params.getCompoundingMap().clear(); + params.addUnCompoundedAmount(amountCharged.minus(totalCompoundedAmount.minus(params.getCompoundedInLastInstallment()))); + } else { + params.getCompoundingMap().clear(); + params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(), + new TreeMap<>(params.getCompoundingMap())); + params.addUnCompoundedAmount(amountCharged); } - updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate); - updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate); + params.setCompoundedInLastInstallment(amountCharged.zero()); + lastInstallmentIsPastDate = isPastDate; } + } } - return ignoreMoney; + + } + + private Money getIncomeForCompounding(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + Money interestCharged = Money.zero(currency); + Money feeCharged = Money.zero(currency); + Money penaltyCharged = Money.zero(currency); + Money amountCharged = Money.zero(currency); + switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { + case INTEREST: + interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency)); + break; + case FEE: + feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency)); + penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency)); + break; + case INTEREST_AND_FEE: + interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency)); + feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency)); + penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency)); + break; + default: + break; + } + amountCharged = interestCharged.plus(feeCharged).plus(penaltyCharged); + return amountCharged; + } + + private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, + final Collection<LoanTransaction> transactions, final LoanApplicationTerms loanApplicationTerms, + HolidayDetailDTO holidayDetailDTO) { + for (LoanTransaction loanTransaction : transactions) { + final LocalDate amountApplicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), + loanApplicationTerms, holidayDetailDTO); + adjustCompoundedAmountWithPaidDetail(params, lastRestDate, amountApplicableDate, loanTransaction, loanApplicationTerms); + } + } + + private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, + final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) { + adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction, + loanApplicationTerms, params.getCurrency()); + } + + private void adjustCompoundedAmountWithPaidDetail(final Map<LocalDate, Money> principalPortionMap, final LocalDate lastRestDate, + final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms, + final MonetaryCurrency currency) { + if (!amountApplicableDate.isEqual(lastRestDate)) { + Money compoundedIncome = fetchCompoundedArrears(loanApplicationTerms, currency, transaction); + updateMapWithAmount(principalPortionMap, compoundedIncome, amountApplicableDate); + updateMapWithAmount(principalPortionMap, compoundedIncome.negated(), lastRestDate); + } } - private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate, + private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final Map<LocalDate, Money> compoundingMap, final Set<LoanCharge> charges, MonetaryCurrency currency) { + final LoanScheduleParams scheduleParams, final Set<LoanCharge> charges, MonetaryCurrency currency) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + final Map<LocalDate, Money> compoundingMap = scheduleParams.getCompoundingMap(); LocalDate lastCompoundingDate = startDate; LocalDate compoundingDate = startDate; - while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) { + boolean addUncompounded = true; + while (compoundingDate.isBefore(endDate)) { + if (loanApplicationTerms.allowCompoundingOnEod()) { + compoundingDate = compoundingDate.minusDays(1); + } compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); - if (!compoundingDate.isBefore(currentDate)) { - break; - } else if (compoundingDate.isAfter(endDate)) { - updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate); - } else { + + if (compoundingDate.isBefore(endDate)) { Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); - updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate); + Money compoundAmount = feeChargesForInstallment.plus(penaltyChargesForInstallment); + if (addUncompounded) { + compoundAmount = compoundAmount.plus(scheduleParams.getUnCompoundedAmount()); + addUncompounded = false; + } + updateMapWithAmount(compoundingMap, compoundAmount, compoundingDate); } - lastCompoundingDate = compoundingDate; - } - } - } - protected void clearMapDetails(final LocalDate startDate, final Map<LocalDate, Money> compoundingMap) { - Map<LocalDate, Money> temp = new HashMap<>(); - for (LocalDate date : compoundingMap.keySet()) { - if (!date.isBefore(startDate)) { - temp.put(date, compoundingMap.get(date)); + lastCompoundingDate = compoundingDate; } } - compoundingMap.clear(); - compoundingMap.putAll(temp); } /** @@ -1580,16 +1680,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalPaid = map.get(amountApplicableDate).plus(principalPaid); } map.put(amountApplicableDate, principalPaid); - } - private Money getTotalAmount(final Map<LocalDate, Money> map, final MonetaryCurrency currency) { - Money total = Money.zero(currency); - for (Map.Entry<LocalDate, Money> mapEntry : map.entrySet()) { - if (mapEntry.getKey().isBefore(DateUtils.getLocalDateOfTenant())) { - total = total.plus(mapEntry.getValue()); - } - } - return total; } @Override @@ -1737,8 +1828,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // get the loan application terms from the Loan object - final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, - compoundingCalendarInstance, loanCalendar, floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays); + final LoanApplicationTerms loanApplicationTerms = loan + .getLoanApplicationTerms(applicationCurrency, restCalendarInstance, compoundingCalendarInstance, loanCalendar, + floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays, holidayDetailDTO); // for applying variations Collection<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); @@ -2117,6 +2209,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // action will be performed on this value Money reducePrincipal = outstandingBalanceAsPerRest.zero(); + Money uncompoundedAmount = outstandingBalanceAsPerRest.zero(); // principal changes will be added along with date(after applying // rest) // from when these amounts will effect the outstanding balance for @@ -2133,6 +2226,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // from when these amounts will effect the outstanding balance for // interest calculation final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>(); + final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; if (loanApplicationTerms.getRestCalendarInstance() != null) { @@ -2179,6 +2273,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // process the installment only if recalculate from date is // greater than due date if (installment.getDueDate().isAfter(lastInstallmentDate)) { + if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { + break; + } LocalDate previousRepaymentDate = actualRepaymentDate; actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); @@ -2272,6 +2369,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // 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); @@ -2284,7 +2384,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // updates the map with over due amounts updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, - newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap); + newRepaymentScheduleInstallments, true, lastRestDate); // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) { @@ -2293,10 +2393,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, - disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, + uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); + loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); } } @@ -2328,6 +2429,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Map<LocalDate, Money> principalPortionMap, LoanRepaymentScheduleInstallment installment, Collection<RecalculationDetail> applicableTransactions, Money actualPrincipalPortion) { Money unprocessed = Money.zero(currency); + Money totalUnprocessed = Money.zero(currency); for (RecalculationDetail detail : applicableTransactions) { if (!detail.isProcessed()) { Money principalProcessed = installment.getPrincipalCompleted(currency); @@ -2357,10 +2459,53 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); updateMapWithAmount(principalPortionMap, unprocessed, applicableDate); + totalUnprocessed = totalUnprocessed.plus(unprocessed); + + } + } + return totalUnprocessed; + } + private Money updateCompoundingDetailsForPartialScheduleGeneration(final LoanRepaymentScheduleInstallment installment, + LoanApplicationTerms loanApplicationTerms, Map<LocalDate, Money> principalMap, + final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations, final Money uncompoundedAmount, + final Collection<RecalculationDetail> applicableTransactions, LocalDate lastRestDate, HolidayDetailDTO holidayDetailDTO) { + Money uncompounded = uncompoundedAmount; + MonetaryCurrency currency = uncompoundedAmount.getCurrency(); + for (RecalculationDetail detail : applicableTransactions) { + LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + adjustCompoundedAmountWithPaidDetail(principalMap, lastRestDate, applicableDate, detail.getTransaction(), loanApplicationTerms, + currency); + } + Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, installment); + final List<LoanInterestRecalcualtionAdditionalDetails> details = installment.getLoanCompoundingDetails(); + Money totalCompounded = Money.zero(currency); + Map<LocalDate, Money> compoundingMap = new TreeMap<>(); + for (LoanInterestRecalcualtionAdditionalDetails additionalDetails : details) { + LocalDate effectiveDate = additionalDetails.getEffectiveDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveDate = effectiveDate.plusDays(1); + } + compoundingMap.put(effectiveDate, Money.of(currency, additionalDetails.getAmount())); + totalCompounded = totalCompounded.plus(additionalDetails.getAmount()); + updateMapWithAmount(principalMap, Money.of(currency, additionalDetails.getAmount()).negated(), effectiveDate); + } + compoundingDateVariations.put(installment.getFromDate(), compoundingMap); + if (totalCompounded.isGreaterThanZero()) { + final boolean isPastDate = installment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); + final LocalDate restDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + if (isPastDate) { + updateMapWithAmount(principalMap, totalCompounded, lastRestDate); + } else { + updateMapWithAmount(principalMap, totalCompounded, restDate); } + uncompounded = amountCharged.plus(uncompounded).minus(totalCompounded); + } else { + uncompounded = uncompounded.plus(amountCharged); } - return unprocessed; + return uncompounded; } private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber, @@ -2432,7 +2577,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), - scheduledLoanInstallment.isRecalculatedInterestComponent()); + scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails()); installments.add(installment); } return installment; @@ -2473,6 +2618,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } else { CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance(); nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); + if (loanApplicationTerms.allowCompoundingOnEod()) { + nextScheduleDate = nextScheduleDate.plusDays(1); + } } return nextScheduleDate; @@ -2510,9 +2658,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); } } - + final List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null; return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(), - feeCharges.getAmount(), penaltyCharges.getAmount(), false); + feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } /**
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/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 f53a50e..1bbefa4 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 @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.Map; import java.util.TreeMap; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; @@ -70,7 +69,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS LocalDate interestStartDate = periodStartDate; Money interestForThisInstallment = totalCumulativePrincipal.zero(); - Money compoundedMoney = totalCumulativePrincipal.zero(); Money compoundedInterest = totalCumulativePrincipal.zero(); Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; @@ -89,13 +87,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } if (principalVariation != null) { - // identifies rest date after current date for reducing all - // compounding - // values - LocalDate compoundingEndDate = principalVariation.ceilingKey(DateUtils.getLocalDateOfTenant()); - if (compoundingEndDate == null) { - compoundingEndDate = DateUtils.getLocalDateOfTenant(); - } for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) { @@ -120,7 +111,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } // fee compounding will be done after calculation compoundFee = compoundingMap.get(principal.getKey()); - compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); + compoundingMap.put(principal.getKey(), interestToBeCompounded.plus(compoundFee)); } balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee); if (interestRates.containsKey(principal.getKey())) { @@ -129,14 +120,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } - if (!periodEndDate.isBefore(compoundingEndDate)) { - balanceForInterestCalculation = balanceForInterestCalculation.minus(compoundedMoney); - compoundingMap.clear(); - } else if (compoundedMoney.isGreaterThanZero()) { - compoundingMap.put(periodEndDate, compoundedMoney); - compoundingMap.put(compoundingEndDate, compoundedMoney.negated()); - clearMapDetails(periodEndDate, compoundingMap); - } } final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 7afc002..b57f215 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -62,8 +62,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { } else { Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), - loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(), - loanApplicationTerms.getWeekDayType()); + loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null, + null); dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), loanApplicationTerms.getRepaymentPeriodFrequencyType()); if (currentCalendar != null) { @@ -219,7 +219,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType, - final int repaidEvery, final LocalDate firstRepaymentDate) { + final int repaidEvery, final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO, + final LoanApplicationTerms loanApplicationTerms) { LocalDate idealDisbursementDate = null; @@ -231,7 +232,15 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery); break; case MONTHS: - idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery); + if (loanCalendar == null) { + idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery); + } else { + idealDisbursementDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), + firstRepaymentDate.minusMonths(repaidEvery), firstRepaymentDate.minusMonths(repaidEvery), repaidEvery, + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType), + holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), + loanApplicationTerms.getNumberOfdays()); + } break; case YEARS: idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);
