diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java index 1c6b55931..8cc788d2b 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -3389,18 +3389,20 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.54", "46.37", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2517.24", "11.67", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "11.79", "0.05", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); loanSchedule = this.loanTransactionHelper.getLoanFutureRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); - addRepaymentValues(expectedvalues, todaysDate, 0, "4965.31", "92.51", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 0, "4965.3", "92.52", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2517.24", "11.67", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "11.79", "0.05", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, 0); @@ -3495,9 +3497,10 @@ public class ClientLoanIntegrationTest { List> expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2486.04", "42.87", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2497.5", "31.41", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2508.98", "19.93", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "24.72", "8.38", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------"); @@ -3513,9 +3516,10 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2490.75", "38.16", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2505.71", "23.2", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2517.28", "11.63", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.57", "46.34", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2497.49", "31.42", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2508.96", "19.95", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "28.22", "8.4", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3529,9 +3533,10 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2486.04", "42.87", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2497.5", "31.41", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2508.98", "19.93", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "24.72", "8.38", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); Float earlyPayment = new Float("5100"); @@ -3544,9 +3549,10 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "5100", "27.96", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "0", "11.16", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2417.24", "11.16", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "5057.13", "42.87", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "0", "19.55", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2349.13", "11.35", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "110.98", "8.26", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3614,11 +3620,14 @@ public class ClientLoanIntegrationTest { loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); + + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.54", "46.37", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2482.33", "46.58", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2517.13", "11.78", "0.0", "0.0"); - + addRepaymentValues(expectedvalues, todaysDate, 1, "35.24", "0.16", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); String JobName = "Apply penalty to overdue loans"; @@ -3813,9 +3822,10 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.54", "46.37", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2517.24", "11.67", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "11.79", "0.05", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); todaysDate = Calendar.getInstance(); @@ -3889,9 +3899,10 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(); addRepaymentValues(expectedvalues, todaysDate, -1, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.54", "46.37", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2505.67", "23.24", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, "2517.24", "11.67", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "11.79", "0.05", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); todaysDate = Calendar.getInstance(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java index 1d74bd427..c174f003f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java @@ -22,12 +22,13 @@ public class ScheduleGeneratorDTO { LocalDate lastTransactionDate; public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, - final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO) { + final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, + final CalendarInstance calendarInstanceForInterestRecalculation) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; - this.calendarInstanceForInterestRecalculation = null; + this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; this.recalculateFrom = null; this.overdurPenaltyWaitPeriod = null; this.lastTransactionDate = null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 09ba88801..1f5f13eeb 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -102,6 +102,7 @@ import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; @@ -2363,12 +2364,23 @@ public class Loan extends AbstractPersistable { loanVariationTermsData.add(data); } + CalendarInstance restCalendarInstance = null; + RecalculationFrequencyType recalculationFrequencyType = null; + InterestRecalculationCompoundingMethod compoundingMethod = null; + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); + recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct.getProductInterestRecalculationDetails() + .getInterestRecalculationCompoundingMethod()); + } + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), - this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf()); + this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod); final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(), scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -4300,17 +4312,15 @@ public class Loan extends AbstractPersistable { final Set loanChargePerInstallments = new HashSet<>(); if (loanCharge.isInstalmentFee()) { for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - if (!installment.isRecalculatedInterestComponent()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isFlat()) { - amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); - } else { - amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), - installment).getAmount(); - } - final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment); - loanChargePerInstallments.add(loanInstallmentCharge); + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isFlat()) { + amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); + } else { + amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment) + .getAmount(); } + final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment); + loanChargePerInstallments.add(loanInstallmentCharge); } } return loanChargePerInstallments; @@ -4636,17 +4646,6 @@ public class Loan extends AbstractPersistable { if (loanSchedule == null) { return; } updateLoanSchedule(loanSchedule, currentUser); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); - - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategy); - final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); - final List copyTransactions = new ArrayList<>(); - for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { - copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); - } - loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, - getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); - Set charges = this.charges(); for (LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { @@ -4662,6 +4661,17 @@ public class Loan extends AbstractPersistable { } } } + + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List copyTransactions = new ArrayList<>(); + for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { + copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); + } + loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, + getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + updateLoanSummaryDerivedFields(); } @@ -4682,8 +4692,7 @@ public class Loan extends AbstractPersistable { final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), - loanRepaymentScheduleTransactionProcessor, this.repaymentScheduleInstallments, generatorDTO.getRecalculateFrom(), - generatorDTO.getLastTransactionDate(), generatorDTO.getPenaltyWaitPeriod()); + loanRepaymentScheduleTransactionProcessor, generatorDTO.getLastTransactionDate()); return loanSchedule; } @@ -4723,7 +4732,8 @@ public class Loan extends AbstractPersistable { return loanApplicationTerms; } - public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) { + public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory, + CalendarInstance restCalendarInstance, HolidayDetailDTO holidayDetailDTO) { LoanRepaymentScheduleInstallment installment = null; if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { @@ -4759,14 +4769,23 @@ public class Loan extends AbstractPersistable { loanVariationTermsData.add(data); } + RecalculationFrequencyType recalculationFrequencyType = null; + InterestRecalculationCompoundingMethod compoundingMethod = null; + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct + .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod()); + } + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), - this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf()); + this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod); installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), - LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc, charges()); + DateUtils.getLocalDateOfTenant(), loanApplicationTerms, mc, charges(), holidayDetailDTO); } else { installment = this.getTotalOutstandingOnLoan(); } @@ -4903,12 +4922,15 @@ public class Loan extends AbstractPersistable { } /** + * @param restCalendarInstance + * TODO * @param loan * the Loan object * @return application terms of the Loan object **/ @SuppressWarnings({ "unused" }) - public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency) { + public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, + final CalendarInstance restCalendarInstance) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -4961,11 +4983,20 @@ public class Loan extends AbstractPersistable { LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount); loanVariationTermsData.add(data); + RecalculationFrequencyType recalculationFrequencyType = null; + InterestRecalculationCompoundingMethod compoundingMethod = null; + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct.getProductInterestRecalculationDetails() + .getInterestRecalculationCompoundingMethod()); + } + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, - this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf()); + this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod); } /** diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 0a497e550..ab9d7d8b2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -546,9 +546,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); - + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); return scheduleGeneratorDTO; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index bc19378b5..d9d9ec961 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -33,20 +33,20 @@ public class LoanRepaymentScheduleProcessingWrapper { for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, - currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), + currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true, lastTransactionDate); final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, - currency, !period.isRecalculatedInterestComponent()); + currency, true); final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), - loanCharges, currency, !period.isRecalculatedInterestComponent()); + loanCharges, currency, true); final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, - !period.isRecalculatedInterestComponent(), lastTransactionDate); + true, lastTransactionDate); final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), - loanCharges, currency, !period.isRecalculatedInterestComponent()); + loanCharges, currency, true); final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, - period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent()); + period.getDueDate(), loanCharges, currency, true); period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index e538c674e..003b7d3c5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -11,7 +11,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.joda.time.LocalDate; @@ -29,7 +28,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail; /** * Abstract implementation of {@link LoanRepaymentScheduleTransactionProcessor} @@ -140,16 +138,17 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } for (final LoanTransaction loanTransaction : transactionstoBeProcessed) { - - if(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) { - final Comparator byDate = new Comparator() { - @Override - public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) { - return ord1.getDueDate().compareTo(ord2.getDueDate()); - } - }; - Collections.sort(installments,byDate); - } + + if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) { + final Comparator byDate = new Comparator() { + + @Override + public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) { + return ord1.getDueDate().compareTo(ord2.getDueDate()); + } + }; + Collections.sort(installments, byDate); + } if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { // pass through for new transactions @@ -183,12 +182,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } else if (loanTransaction.isWriteOff()) { loanTransaction.resetDerivedComponents(); handleWriteOff(loanTransaction, currency, installments); - } - else if(loanTransaction.isRefundForActiveLoan()) { + } else if (loanTransaction.isRefundForActiveLoan()) { loanTransaction.resetDerivedComponents(); - - handleRefund(loanTransaction, currency, installments,charges); - } + + handleRefund(loanTransaction, currency, installments, charges); + } } return changedTransactionDetail; } @@ -470,47 +468,18 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } @Override - public List handleRepaymentSchedule(final List transactionsPostDisbursement, - final MonetaryCurrency currency, final List installments, - final LoanRepaymentScheduleInstallment installment, final Map recalculationDates, - final LoanTransaction preCloseTransaction) { - List earlypaymentDetail = new ArrayList<>(); - Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency); - LocalDate maxTransactionDate = null; - Set transaDates = recalculationDates.keySet(); - for (LocalDate transactionDate : transaDates) { - if (maxTransactionDate == null || transactionDate.isAfter(maxTransactionDate)) { - maxTransactionDate = transactionDate; - } - } + public Money handleRepaymentSchedule(final List transactionsPostDisbursement, final MonetaryCurrency currency, + final List installments) { + Money unProcessed = Money.zero(currency); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { Money amountToProcess = null; - LocalDate transactionDate = loanTransaction.getTransactionDate(); - if (loanTransaction != preCloseTransaction) { - transactionDate = recalculationDates.get(transactionDate); - } final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); if (newLoanTransaction.isRepayment() || newLoanTransaction.isInterestWaiver() || newLoanTransaction.isRecoveryRepayment()) { newLoanTransaction.resetDerivedComponents(); } - - Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); - if (loanTransaction.getTransactionDate().isEqual(maxTransactionDate) && transactionDate.isAfter(installment.getFromDate()) - && transactionDate.isBefore(installment.getDueDate())) { - Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency)); - if (earlyPayment.isGreaterThanZero() - && (newLoanTransaction.isRepayment() || newLoanTransaction.isInterestWaiver() || newLoanTransaction - .isRecoveryRepayment())) { - earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, earlyPayment, false)); - } - currentInstallmentOutstanding = installment.getTotalOutstanding(currency); - } - - if (unProcessed.isGreaterThanZero()) { - earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, unProcessed, false)); - } + unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); } - return earlypaymentDetail; + return unProcessed; } @Override @@ -519,114 +488,85 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } @Override - public void applyTransaction(List transactionsPostDisbursement, MonetaryCurrency currency, - List installments) { - for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { - Money amountToProcess = null; - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); - newLoanTransaction.resetDerivedComponents(); - processTransaction(newLoanTransaction, currency, installments, amountToProcess); + public void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments, final Set charges) { + // TODO Auto-generated method stub + + final Comparator byDate = new Comparator() { + + @Override + public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) { + return ord1.getDueDate().compareTo(ord2.getDueDate()); + } + }; + Collections.sort(installments, Collections.reverseOrder(byDate)); + Money transactionAmountUnprocessed = loanTransaction.getAmount(currency); + + for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { + Money outstanding = currentInstallment.getTotalOutstanding(currency); + Money due = currentInstallment.getDue(currency); + + if (outstanding.isLessThan(due)) { + transactionAmountUnprocessed = handleRefundTransactionPaymentOfInstallment(currentInstallment, loanTransaction, + transactionAmountUnprocessed); + + } + + if (transactionAmountUnprocessed.isZero()) break; } + + final Set loanFees = extractFeeCharges(charges); + final Set loanPenalties = extractPenaltyCharges(charges); + Integer installmentNumber = null; + + final Money feeCharges = loanTransaction.getFeeChargesPortion(currency); + if (feeCharges.isGreaterThanZero()) { + undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber); + } + + final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); + if (penaltyCharges.isGreaterThanZero()) { + undoChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber); + } + } - - @Override - public void handleRefund(LoanTransaction loanTransaction, - MonetaryCurrency currency, - List installments, - final Set charges) { - // TODO Auto-generated method stub - final Comparator byDate = new Comparator() { - @Override - public int compare(LoanRepaymentScheduleInstallment ord1, - LoanRepaymentScheduleInstallment ord2) { - return ord1.getDueDate().compareTo(ord2.getDueDate()); - } - }; - Collections.sort(installments, Collections.reverseOrder(byDate)); - Money transactionAmountUnprocessed = loanTransaction - .getAmount(currency); + /** + * Invoked when a there is a refund of an active loan or undo of an active + * loan + * + * Undoes principal, interest, fees and charges of this transaction based on + * the repayment strategy + * + */ + protected abstract Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed); - for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { - Money outstanding = currentInstallment - .getTotalOutstanding(currency); - Money due = currentInstallment.getDue(currency); + private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set charges, + final Integer installmentNumber) { - if (outstanding.isLessThan(due)) { - transactionAmountUnprocessed = handleRefundTransactionPaymentOfInstallment( - currentInstallment, loanTransaction, - transactionAmountUnprocessed); + Money amountRemaining = feeCharges; + while (amountRemaining.isGreaterThanZero()) { + final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency()); - } + if (paidCharge != null) { + Money feeAmount = feeCharges.zero(); - if (transactionAmountUnprocessed.isZero()) - break; + final Money amountDeductedTowardsCharge = paidCharge.undoPaidOrPartiallyAmountBy(amountRemaining, installmentNumber, + feeAmount); + if (amountDeductedTowardsCharge.isGreaterThanZero()) { - } + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, paidCharge, amountDeductedTowardsCharge + .getAmount().multiply(new BigDecimal(-1)), null); + loanTransaction.getLoanChargesPaid().add(loanChargePaidBy); - final Set loanFees = extractFeeCharges(charges); - final Set loanPenalties = extractPenaltyCharges(charges); - Integer installmentNumber = null; + amountRemaining = amountRemaining.minus(amountDeductedTowardsCharge); + } + } + } - final Money feeCharges = loanTransaction.getFeeChargesPortion(currency); - if (feeCharges.isGreaterThanZero()) { - undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, - installmentNumber); - } - - final Money penaltyCharges = loanTransaction - .getPenaltyChargesPortion(currency); - if (penaltyCharges.isGreaterThanZero()) { - undoChargesPaidAmountBy(loanTransaction, penaltyCharges, - loanPenalties, installmentNumber); - } - - } - - /** - * Invoked when a there is a refund of an active loan or undo of an active - * loan - * - * Undoes principal, interest, fees and charges of this transaction based on - * the repayment strategy - * - */ - protected abstract Money handleRefundTransactionPaymentOfInstallment( - final LoanRepaymentScheduleInstallment currentInstallment, - final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed); - - private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, - final Money feeCharges, final Set charges, - final Integer installmentNumber) { - - Money amountRemaining = feeCharges; - while (amountRemaining.isGreaterThanZero()) { - final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet( - charges, feeCharges.getCurrency()); - - if (paidCharge != null) { - Money feeAmount = feeCharges.zero(); - - final Money amountDeductedTowardsCharge = paidCharge - .undoPaidOrPartiallyAmountBy(amountRemaining, - installmentNumber, feeAmount); - if (amountDeductedTowardsCharge.isGreaterThanZero()) { - - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy( - loanTransaction, paidCharge, - amountDeductedTowardsCharge.getAmount().multiply( - new BigDecimal(-1)), null); - loanTransaction.getLoanChargesPaid().add(loanChargePaidBy); - - amountRemaining = amountRemaining - .minus(amountDeductedTowardsCharge); - } - } - } - - } + } @Override public ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(final LocalDate disbursementDate, @@ -636,48 +576,36 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom, reprocessCharges); } - - private LoanCharge findLatestPaidChargeFromUnOrderedSet( - final Set charges, MonetaryCurrency currency) { - LoanCharge latestPaidCharge = null; - LoanCharge installemntCharge = null; - LoanInstallmentCharge chargePerInstallment = null; - for (final LoanCharge loanCharge : charges) { - boolean isPaidOrPartiallyPaid = loanCharge - .isPaidOrPartiallyPaid(currency); - if (isPaidOrPartiallyPaid && !loanCharge.isDueAtDisbursement()) { - if (loanCharge.isInstalmentFee()) { - LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge - .getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency); - if (chargePerInstallment == null - || (paidLoanChargePerInstallment != null && chargePerInstallment - .getRepaymentInstallment() - .getDueDate() - .isBefore( - paidLoanChargePerInstallment - .getRepaymentInstallment() - .getDueDate()))) { - installemntCharge = loanCharge; - chargePerInstallment = paidLoanChargePerInstallment; - } - } else if (latestPaidCharge == null - || (loanCharge.isPaidOrPartiallyPaid(currency)) - && loanCharge.getDueLocalDate().isAfter( - latestPaidCharge.getDueLocalDate())) { - latestPaidCharge = loanCharge; - } - } - } - if (latestPaidCharge == null - || (chargePerInstallment != null && latestPaidCharge - .getDueLocalDate().isAfter( - chargePerInstallment.getRepaymentInstallment() - .getDueDate()))) { - latestPaidCharge = installemntCharge; - } - return latestPaidCharge; - } - + private LoanCharge findLatestPaidChargeFromUnOrderedSet(final Set charges, MonetaryCurrency currency) { + LoanCharge latestPaidCharge = null; + LoanCharge installemntCharge = null; + LoanInstallmentCharge chargePerInstallment = null; + for (final LoanCharge loanCharge : charges) { + boolean isPaidOrPartiallyPaid = loanCharge.isPaidOrPartiallyPaid(currency); + if (isPaidOrPartiallyPaid && !loanCharge.isDueAtDisbursement()) { + if (loanCharge.isInstalmentFee()) { + LoanInstallmentCharge paidLoanChargePerInstallment = loanCharge + .getLastPaidOrPartiallyPaidInstallmentLoanCharge(currency); + if (chargePerInstallment == null + || (paidLoanChargePerInstallment != null && chargePerInstallment.getRepaymentInstallment().getDueDate() + .isBefore(paidLoanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + installemntCharge = loanCharge; + chargePerInstallment = paidLoanChargePerInstallment; + } + } else if (latestPaidCharge == null || (loanCharge.isPaidOrPartiallyPaid(currency)) + && loanCharge.getDueLocalDate().isAfter(latestPaidCharge.getDueLocalDate())) { + latestPaidCharge = loanCharge; + } + } + } + if (latestPaidCharge == null + || (chargePerInstallment != null && latestPaidCharge.getDueLocalDate().isAfter( + chargePerInstallment.getRepaymentInstallment().getDueDate()))) { + latestPaidCharge = installemntCharge; + } + + return latestPaidCharge; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java index 6280133fa..cedaa90f4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java @@ -6,16 +6,15 @@ package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; import java.util.List; -import java.util.Map; import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail; public interface LoanRepaymentScheduleTransactionProcessor { @@ -29,9 +28,8 @@ public interface LoanRepaymentScheduleTransactionProcessor { void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); - List handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, - List installments, LoanRepaymentScheduleInstallment currentInstallment, - Map recalculationDates, LoanTransaction preCloseTransaction); + Money handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, + List installments); /** * Used in interest recalculation to introduce new interest only @@ -39,16 +37,11 @@ public interface LoanRepaymentScheduleTransactionProcessor { */ boolean isInterestFirstRepaymentScheduleTransactionProcessor(); - void applyTransaction(List transactionsPostDisbursement, MonetaryCurrency currency, - List installments); - ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate, List transactionsPostDisbursement, MonetaryCurrency currency, List installments, Set charges, LocalDate recalculateChargesFrom); - - void handleRefund(LoanTransaction loanTransaction, - MonetaryCurrency currency, - List installments, - final Set charges); + + void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments, + final Set charges); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java index 95616049e..28c4b2fcb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java @@ -110,7 +110,7 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); final Money principalPortion = Money.zero(currency); - loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency)); + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion,penaltyChargesPortion); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 9f3fa0d48..9a875aeef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -19,6 +19,7 @@ import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -32,7 +33,6 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInsta import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; -import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; @@ -51,15 +51,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { - final List diffAmt = null; - final LocalDate prepayDate = null; + final Collection diffAmt = null; + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; final LocalDate lastTransactionDate = loanApplicationTerms.getExpectedDisbursementDate(); - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, prepayDate, lastTransactionDate); + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, lastTransactionDate, + loanRepaymentScheduleTransactionProcessor); } private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, List diffAmt, - final LocalDate prepayDate, final LocalDate lastTransactionDate) { + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final Collection transactions, + final LocalDate lastTransactionDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // 1. generate list of proposed schedule due dates @@ -72,19 +73,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // 3. setup variables for tracking important facts required for loan // schedule generation. Money principalDisbursed = loanApplicationTerms.getPrincipal(); - final Money expectedPrincipalDisburse = loanApplicationTerms.getPrincipal(); final MonetaryCurrency currency = principalDisbursed.getCurrency(); final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); // variables for cumulative totals int loanTermInDays = Integer.valueOf(0); - BigDecimal totalPrincipalExpected = BigDecimal.ZERO; final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; - BigDecimal totalInterestCharged = BigDecimal.ZERO; BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement; BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO; BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement; final BigDecimal totalOutstanding = BigDecimal.ZERO; + Money totalCumulativePrincipal = principalDisbursed.zero(); + Money totalCumulativeInterest = principalDisbursed.zero(); + Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero(); final Collection periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms, chargesDueAtTimeOfDisbursement); @@ -104,161 +105,237 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate periodStartDateApplicableForInterest = periodStartDate; + // Actual period Number as per the schedule int periodNumber = 1; + // Actual period Number and interest only repayments int instalmentNumber = 1; - Money totalCumulativePrincipal = principalDisbursed.zero(); - Money totalCumulativeInterest = principalDisbursed.zero(); - Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero(); + Money outstandingBalance = principalDisbursed; + // disbursement map for tranche details(will added to outstanding + // balance as per the start date) + final Map disburseDetailMap = new HashMap<>(); if (loanApplicationTerms.isMultiDisburseLoan()) { - BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, periodStartDate, periods, chargesDueAtTimeOfDisbursement); + // fetches the first tranche amount and also updates other tranche + // details to map + BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, periodStartDate, periods, chargesDueAtTimeOfDisbursement, + disburseDetailMap, isInterestRecalculationRequired(loanApplicationTerms, transactions)); principalDisbursed = principalDisbursed.zero().plus(disburseAmt); loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt)); outstandingBalance = outstandingBalance.zero().plus(disburseAmt); } + + // charges which depends on total loan interest will be added to this + // set and handled separately after all installments generated + final Set nonCompoundingCharges = seperateTotalCompoundingPercentageCharges(loanCharges); + + // total outstanding balance as per rest for interest calculation. + Money outstandingBalanceAsPerRest = outstandingBalance; + // early payments will be added here and as per the selected strategy + // action will be performed on this value Money reducePrincipal = totalCumulativePrincipal.zero(); - int daysCalcForInstallmentNumber = 0; - LocalDate scheduleStartDateAsPerFrequency = periodStartDate; - while (!outstandingBalance.isZero()) { - // to insert a new schedule between actual schedule to collect - // interest(till the transaction date) first on a payment - boolean recalculatedInterestComponent = false; - RecalculationDetail interestonlyPeriodDetail = null; - if (diffAmt != null) { - for (RecalculationDetail recalculationDetail : diffAmt) { - if (recalculationDetail.isInterestompound() && recalculationDetail.getStartDate().isEqual(periodStartDate)) { - interestonlyPeriodDetail = recalculationDetail; - break; - } - } - } - LocalDate scheduledDueDate = null; - LocalDate scheduledDueDateAsPerFrequency = null; - int daysInPeriodApplicableInFullInstallment = 0; - // will change the schedule dates as per the interest only Period - if (interestonlyPeriodDetail == null) { - actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isFirstRepayment); - isFirstRepayment = false; - scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, - holidayDetailDTO); - scheduledDueDateAsPerFrequency = scheduledDueDate; - } else { - recalculatedInterestComponent = true; - scheduledDueDate = interestonlyPeriodDetail.getToDate(); - daysCalcForInstallmentNumber = periodNumber; - LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, - loanApplicationTerms, isFirstRepayment); - scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms, - holidayDetailDTO); - } + // principal changes will be added along with date(after applying rest) + // from when these amounts will effect the outstanding balance for + // interest calculation + final Map principalPortionMap = new HashMap<>(); + // compounding(interest and fee) amounts will be added along with + // date(after applying compounding frequency) + // from when these amounts will effect the outstanding balance for + // interest calculation + final Map latePaymentMap = new HashMap<>(); + final List installments = new ArrayList<>(); + while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) { - final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); - int daysInPeriodApplicableForInterest = daysInPeriod; + actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, + isFirstRepayment); + isFirstRepayment = false; + LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, + holidayDetailDTO); - if (periodStartDate.isBefore(idealDisbursementDate)) { - if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { - periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); - } else { - periodStartDateApplicableForInterest = idealDisbursementDate; - } - daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); - } + // calculated interest start date for the period + periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, + idealDisbursementDate, periodStartDateApplicableForInterest); + int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); - // this block identifies full payment period as per the - // schedule(excluding interest payments) for interest calculation - daysInPeriodApplicableInFullInstallment = daysInPeriodApplicableForInterest; - if (daysCalcForInstallmentNumber == periodNumber) { - periodStartDateApplicableForInterest = scheduleStartDateAsPerFrequency; - if (scheduleStartDateAsPerFrequency.isBefore(idealDisbursementDate)) { - if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { - periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); - } else { - periodStartDateApplicableForInterest = idealDisbursementDate; - } - } + // populates the collection with transactions till the due date of + // the period for interest recalculation enabled loans + Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, + scheduledDueDate, transactions); - daysInPeriodApplicableInFullInstallment = Days.daysBetween(periodStartDateApplicableForInterest, - scheduledDueDateAsPerFrequency).getDays(); - } double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator - .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, - scheduledDueDateAsPerFrequency, loanApplicationTerms.getInterestChargedFromLocalDate(), - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); - BigDecimal interestToBeAdded = BigDecimal.ZERO; - BigDecimal disburseAmt = BigDecimal.ZERO; + .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, + loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), + loanApplicationTerms.getRepaymentEvery()); if (loanApplicationTerms.isMultiDisburseLoan()) { + // Updates fixed emi amount as the date if multiple amounts + // provided loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate); - final Collection disbursementDatas = new ArrayList<>(); - LocalDate tillDate = scheduledDueDate; - if (prepayDate != null && prepayDate.isBefore(tillDate)) { - tillDate = prepayDate; - } - disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, tillDate, disbursementDatas, diffAmt != null); - principalDisbursed = principalDisbursed.plus(disburseAmt); - loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt)); - outstandingBalance = outstandingBalance.plus(disburseAmt); - if (loanApplicationTerms.getMaxOutstandingBalance() != null - && outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) { - String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance(); - throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance() - .getAmount(), disburseAmt); - } - for (DisbursementData disbursementData : disbursementDatas) { - Money disbursedAmt = Money.of(currency, disbursementData.amount()); - final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( - disbursementData.disbursementDate(), disbursedAmt, chargesDueAtTimeOfDisbursement); - periods.add(disbursementPeriod); - if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest) - && (prepayDate == null || prepayDate.isAfter(disbursementData.disbursementDate()) || disbursementData - .isDisbursed())) { - interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, periodNumber, - totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableInFullInstallment, - interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, disbursementData.disbursementDate(), - tillDate)); + for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { + if (disburseDetail.getKey().isAfter(periodStartDate) && !disburseDetail.getKey().isAfter(scheduledDueDate)) { + // validation check for amount not exceeds specified max + // amount as per the configuration + if (loanApplicationTerms.getMaxOutstandingBalance() != null + && outstandingBalance.plus(disburseDetail.getValue()).isGreaterThan( + loanApplicationTerms.getMaxOutstandingBalance())) { + String errorMsg = "Outstanding balance must not exceed the amount: " + + loanApplicationTerms.getMaxOutstandingBalance(); + throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance() + .getAmount(), disburseDetail.getValue()); + } + + // creates and add disbursement detail to the repayments + // period + final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( + disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); + periods.add(disbursementPeriod); + // updates actual outstanding balance with new + // disbursement detail + outstandingBalance = outstandingBalance.plus(disburseDetail.getValue()); + principalDisbursed = principalDisbursed.plus(disburseDetail.getValue()); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue())); } } } - Money balanceForcalculation = outstandingBalance.minus(disburseAmt); - // reduce principal is the early payment, will processed as per the - // reschedule strategy - if (reducePrincipal.isGreaterThanZero()) { - switch (loanApplicationTerms.getRescheduleStrategyMethod()) { - case REDUCE_EMI_AMOUNT: - if (!loanApplicationTerms.isMultiDisburseLoan()) { - loanApplicationTerms.setFixedEmiAmount(null); + // Adds new interest repayment to the schedule as per the repayment + // transaction processor configuration + // will be added only if there is a loan repayment between the + // period for interest first repayment strategies + if (isInterestRecalculationRequired(loanApplicationTerms, transactions) && loanRepaymentScheduleTransactionProcessor != null + && loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { + LoanScheduleModelPeriod installment = null; + for (RecalculationDetail detail : applicableTransactions) { + if (detail.isProcessed()) { + continue; + } + if (detail.getTransactionDate().isBefore(scheduledDueDate)) { + List currentTransactions = createCurrentTransactionList(detail); + if (!detail.getTransactionDate().isEqual(periodStartDate)) { + int periodDays = Days.daysBetween(periodStartDate, detail.getTransactionDate()).getDays(); + // calculates period start date for interest + // calculation as per the configuration + periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, + periodStartDate, idealDisbursementDate, periodStartDateApplicableForInterest); + + int daysInPeriodApplicable = Days + .daysBetween(periodStartDateApplicableForInterest, detail.getTransactionDate()).getDays(); + Money interestForThisinstallment = Money.zero(currency); + if (daysInPeriodApplicable > 0) { + // 5 determine interest till the transaction + // date + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, + totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, + totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, + periodNumber, mc, mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), + periodStartDateApplicableForInterest, detail.getTransactionDate(), + daysInPeriodApplicableForInterest); + interestForThisinstallment = principalInterestForThisPeriod.interest(); + + totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); + + } + + Money principalForThisPeriod = principalDisbursed.zero(); + + // applies all the applicable charges to the newly + // created installment + PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment, + null); + Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, detail.getTransactionDate(), + loanCharges, currency, principalInterest, principalDisbursed, totalCumulativeInterest, + numberOfRepayments, true, lastTransactionDate); + Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, + detail.getTransactionDate(), loanCharges, currency, principalInterest, principalDisbursed, + totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + + // 8. sum up real totalInstallmentDue from + // components + final Money totalInstallmentDue = principalForThisPeriod.plus(interestForThisinstallment) + .plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); + // 9. create repayment period from parts + installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, periodStartDate, + detail.getTransactionDate(), principalForThisPeriod, outstandingBalance, interestForThisinstallment, + feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, true); + periods.add(installment); + + // update outstanding balance for interest + // calculation as per the rest + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, + detail.getTransactionDate(), outstandingBalanceAsPerRest, false); + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, + detail.getTransactionDate(), outstandingBalanceAsPerRest, true); + + // handle cumulative fields + loanTermInDays += periodDays; + totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount()); + totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); + totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); + totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); + + periodStartDate = detail.getTransactionDate(); + periodStartDateApplicableForInterest = periodStartDate; + instalmentNumber++; + // creates and insert Loan repayment schedule for + // the period + addLoanRepaymentScheduleInstallment(installments, installment); + } else if (installment == null) { + installment = ((List) periods).get(periods.size() - 1); } - outstandingBalance = outstandingBalance.minus(reducePrincipal); - balanceForcalculation = balanceForcalculation.minus(reducePrincipal); - totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); - reducePrincipal = reducePrincipal.zero(); - break; - case REDUCE_NUMBER_OF_INSTALLMENTS: - outstandingBalance = outstandingBalance.minus(reducePrincipal); - balanceForcalculation = balanceForcalculation.minus(reducePrincipal); - totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); - reducePrincipal = reducePrincipal.zero(); - break; - case RESCHEDULE_NEXT_REPAYMENTS: - balanceForcalculation = balanceForcalculation.minus(reducePrincipal); - break; - default: - break; + // applies the transaction as per transaction strategy + // on scheduled installments to identify the + // unprocessed(early payment ) amounts + Money unprocessed = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, + currency, installments); + if (unprocessed.isGreaterThanZero()) { + // reduces actual outstanding balance + outstandingBalance = outstandingBalance.minus(unprocessed); + // if outstanding balance becomes less than zero + // then adjusts the princiapal + Money addToPrinciapal = Money.zero(currency); + if (outstandingBalance.isLessThanZero()) { + addToPrinciapal = addToPrinciapal.plus(outstandingBalance); + outstandingBalance = outstandingBalance.zero(); + } + // updates principal portion map with the early + // payment amounts and applicable date as per rest + updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO, principalPortionMap, installment, + detail, unprocessed.plus(addToPrinciapal), installments); + totalRepaymentExpected = totalRepaymentExpected.add(unprocessed.plus(addToPrinciapal).getAmount()); + totalCumulativePrincipal = totalCumulativePrincipal.plus(unprocessed.plus(addToPrinciapal)); + + // method applies early payment strategy + reducePrincipal = reducePrincipal.plus(unprocessed); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); + } + // identify late payments and add compounding details to + // map for interest calculation + if (!reducePrincipal.isZero() || !installment.periodDueDate().isBefore(DateUtils.getLocalDateOfTenant())) { + latePaymentMap.clear(); + } else { + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, scheduledDueDate, + installments, true); + } + } + + } + if (outstandingBalance.isZero() && disburseDetailMap.isEmpty()) { + continue; } } - if (!balanceForcalculation.isGreaterThanZero()) { - break; - } + int periodDays = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); + periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, + idealDisbursementDate, periodStartDateApplicableForInterest); // 5 determine principal,interest of repayment period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, - daysInPeriodApplicableInFullInstallment, balanceForcalculation, loanApplicationTerms, periodNumber, mc); + outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, + mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), periodStartDateApplicableForInterest, + scheduledDueDate, daysInPeriodApplicableForInterest); if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { @@ -266,275 +343,520 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(), loanApplicationTerms.getFixedEmiAmount()); } + // update cumulative fields for principal & interest Money interestForThisinstallment = principalInterestForThisPeriod.interest(); - if (daysCalcForInstallmentNumber == periodNumber) { - interestForThisinstallment = interestForThisinstallment.zero().plus( - calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment.getAmount(), - daysInPeriodApplicableForInterest)); - } totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); + Money principalForThisPeriod = principalInterestForThisPeriod.principal(); - if (interestForThisinstallment.isGreaterThanZero()) { - interestForThisinstallment = interestForThisinstallment.plus(interestToBeAdded); - } else if (principalInterestForThisPeriod.interestPaymentDueToGrace().isGreaterThanZero()) { - totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.minus(interestToBeAdded); + if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment() && loanApplicationTerms.getFixedEmiAmount() != null) { + Money principalToBeAdjust = Money.of(currency, loanApplicationTerms.getFixedEmiAmount()).minus(principalForThisPeriod) + .minus(interestForThisinstallment); + principalForThisPeriod = principalForThisPeriod.plus(principalToBeAdjust); } + principalForThisPeriod = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisPeriod, + totalCumulativePrincipal.plus(principalForThisPeriod), periodNumber); - Money principalForThisPeriod = principalDisbursed.zero(); - Money outstandingForThePeriod = outstandingBalance; - Money reducePrincipalForCurrentInstallment = reducePrincipal; - - totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); - - Money feeChargesForInstallment = principalDisbursed.zero(); - Money penaltyChargesForInstallment = principalDisbursed.zero(); - - Money extraPrincipal = reducePrincipal.zero(); - Money canBeReducedFromReducePrincipal = reducePrincipal.zero(); - // this block is to identify interest based on late/early payment - if (diffAmt != null && !diffAmt.isEmpty()) { - BigDecimal interestDueToLatePayment = BigDecimal.ZERO; - BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; - - for (RecalculationDetail detail : diffAmt) { - if (!detail.isInterestompound()) { - // will increase the principal portion and reduces - // interest - // as per the number of days. and also identifies reduce - // principal for reschedule strategy - if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) - && !detail.getStartDate().isAfter(scheduledDueDate)) { - reducePrincipal = reducePrincipal.plus(detail.getAmount()); - extraPrincipal = extraPrincipal.plus(detail.getAmount()); - int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); - if (diffDays > 0) { - canBeReducedFromReducePrincipal = canBeReducedFromReducePrincipal.plus(detail.getAmount()); - } - - LocalDate startDate = detail.getStartDate(); - if (loanApplicationTerms.getInterestChargedFromLocalDate() != null - && startDate.isBefore(loanApplicationTerms.getInterestChargedFromLocalDate())) { - startDate = loanApplicationTerms.getInterestChargedFromLocalDate(); - } - LocalDate endDate = scheduledDueDate; - if (startDate.isBefore(endDate)) { - Money amountForInterestCalculation = detail.getAmount(); - Money balanceDiff = outstandingForThePeriod.minus(extraPrincipal); - if (balanceDiff.isLessThanZero()) { - amountForInterestCalculation = amountForInterestCalculation.plus(balanceDiff); - } - interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(calculateInterestForSpecificDays( - mc, loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace, - daysInPeriodApplicableInFullInstallment, interestCalculationGraceOnRepaymentPeriodFraction, - amountForInterestCalculation, startDate, endDate)); - } - - } - // calculates the interest for late payment and increase - // the interest for the installment - else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) - && periodStartDate.isBefore(LocalDate.now())) { - LocalDate fromDate = periodStartDate; - LocalDate toDate = scheduledDueDate; - if (!detail.getStartDate().isBefore(periodStartDate)) { - fromDate = detail.getStartDate(); - } - if (!detail.getToDate().isAfter(scheduledDueDate)) { - toDate = detail.getToDate(); - } - if (toDate.isAfter(LocalDate.now())) { - toDate = LocalDate.now(); - } - if (fromDate.isAfter(LocalDate.now())) { - fromDate = LocalDate.now(); - } - if (loanApplicationTerms.getInterestChargedFromLocalDate() != null - && fromDate.isBefore(loanApplicationTerms.getInterestChargedFromLocalDate())) { - fromDate = loanApplicationTerms.getInterestChargedFromLocalDate(); - } - if (fromDate.isBefore(toDate)) { - interestDueToLatePayment = interestDueToLatePayment.add(calculateInterestForSpecificDays(mc, - loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace, - daysInPeriodApplicableInFullInstallment, interestCalculationGraceOnRepaymentPeriodFraction, - detail.getAmount(), fromDate, toDate)); - } - } - } - } - - if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) { - totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment) - .minus(interestReducedDueToEarlyPayment); - } else { - totalCumulativeInterest = totalCumulativeInterest.plus(interestDueToLatePayment) - .minus(interestReducedDueToEarlyPayment); - interestForThisinstallment = interestForThisinstallment.plus(interestDueToLatePayment).minus( - interestReducedDueToEarlyPayment); - } - - } - - // Exclude principal portion for interest only installments - if (!recalculatedInterestComponent) { - principalForThisPeriod = principalInterestForThisPeriod.principal(); - if (((diffAmt != null && !diffAmt.isEmpty()) || interestToBeAdded.compareTo(BigDecimal.ZERO) == 1) - && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { - Money principalToBeAdjust = Money.of(currency, loanApplicationTerms.getFixedEmiAmount()).minus(principalForThisPeriod) - .minus(interestForThisinstallment); - principalForThisPeriod = principalForThisPeriod.plus(principalToBeAdjust); - principalForThisPeriod = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisPeriod, - totalCumulativePrincipal.plus(principalForThisPeriod), periodNumber); - } - totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod); - - // 6. update outstandingLoanBlance using correct 'principalDue' - outstandingBalance = outstandingBalance.minus(principalForThisPeriod); - if (outstandingBalance.isLessThanZero()) { - principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance); - outstandingBalance = outstandingBalance.zero(); - } - } - - reducePrincipal = reducePrincipal.minus(reducePrincipalForCurrentInstallment); - - if (principalForThisPeriod.isGreaterThan(reducePrincipalForCurrentInstallment)) { - principalForThisPeriod = principalForThisPeriod.minus(reducePrincipalForCurrentInstallment); - reducePrincipalForCurrentInstallment = reducePrincipalForCurrentInstallment.zero(); + // applies early payments on principal portion + if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { + principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); + reducePrincipal = reducePrincipal.zero(); } else { - reducePrincipalForCurrentInstallment = reducePrincipalForCurrentInstallment.minus(principalForThisPeriod); + reducePrincipal = reducePrincipal.minus(principalForThisPeriod); principalForThisPeriod = principalForThisPeriod.zero(); } - Money actualOutstandingbalance = outstandingBalance; - if (diffAmt != null && !diffAmt.isEmpty()) { - reducePrincipal = reducePrincipal.minus(canBeReducedFromReducePrincipal); - Money pricipalAfterCurrentPrincipal = canBeReducedFromReducePrincipal.minus(principalForThisPeriod); - if (pricipalAfterCurrentPrincipal.isGreaterThanZero()) { - reducePrincipal = reducePrincipal.plus(pricipalAfterCurrentPrincipal); - } + // 6. update outstandingLoanBlance using current period + // 'principalDue' + outstandingBalance = outstandingBalance.minus(principalForThisPeriod); - if (reducePrincipal.isLessThanZero()) { - reducePrincipal = reducePrincipal.zero(); - } else { - Money actualOutstanding = outstandingBalance.minus(reducePrincipal); - principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); - if (actualOutstanding.isLessThanZero()) { - principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); - } - } + if (outstandingBalance.isLessThanZero()) { + principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance); + outstandingBalance = outstandingBalance.zero(); } - reducePrincipal = reducePrincipal.plus(reducePrincipalForCurrentInstallment); - - actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal); - if (actualOutstandingbalance.isLessThanZero()) { - actualOutstandingbalance = actualOutstandingbalance.zero(); - } + // applies charges for the period + PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment, null); + Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency, + principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, + currency, principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); + totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); // 8. sum up real totalInstallmentDue from components - final Money totalInstallmentDue = principalForThisPeriod// - .plus(interestForThisinstallment) // - .plus(feeChargesForInstallment) // + final Money totalInstallmentDue = principalForThisPeriod.plus(interestForThisinstallment).plus(feeChargesForInstallment) .plus(penaltyChargesForInstallment); // 9. create repayment period from parts - final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, periodStartDate, - scheduledDueDate, principalForThisPeriod, actualOutstandingbalance, interestForThisinstallment, - feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, recalculatedInterestComponent); + LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, periodStartDate, + scheduledDueDate, principalForThisPeriod, outstandingBalance, interestForThisinstallment, feeChargesForInstallment, + penaltyChargesForInstallment, totalInstallmentDue, false); + + // apply loan transactions on installments to identify early/late + // payments for interest recalculation + if (isInterestRecalculationRequired(loanApplicationTerms, transactions)) { + addLoanRepaymentScheduleInstallment(installments, installment); + + for (RecalculationDetail detail : applicableTransactions) { + if (!detail.isProcessed()) { + List currentTransactions = new ArrayList<>(2); + currentTransactions.add(detail.getTransaction()); + // applies the transaction as per transaction strategy + // on scheduled installments to identify the + // unprocessed(early payment ) amounts + Money unprocessed = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, + currency, installments); + if (unprocessed.isGreaterThanZero()) { + outstandingBalance = outstandingBalance.minus(unprocessed); + // pre closure check and processing + if (outstandingBalance.isLessThan(interestForThisinstallment) + && !scheduledDueDate.equals(detail.getTransactionDate())) { + PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( + this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, + totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, + totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, + periodNumber, mc, mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), + periodStartDateApplicableForInterest, detail.getTransactionDate(), + daysInPeriodApplicableForInterest); + Money diff = interestForThisinstallment.minus(interestTillDate.interest()); + if (!outstandingBalance.minus(diff).isGreaterThanZero()) { + outstandingBalance = outstandingBalance.minus(diff); + interestForThisinstallment = interestForThisinstallment.minus(diff); + principalForThisPeriod = principalForThisPeriod.plus(diff); + final Money totalDue = principalForThisPeriod// + .plus(interestForThisinstallment); + + // 9. create and replaces repayment period + // from parts + installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, periodStartDate, + detail.getTransactionDate(), principalForThisPeriod, outstandingBalance, + interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalDue, + false); + } + + } + Money addToPrinciapal = Money.zero(currency); + if (outstandingBalance.isLessThanZero()) { + addToPrinciapal = addToPrinciapal.plus(outstandingBalance); + outstandingBalance = outstandingBalance.zero(); + } + // updates principal portion map with the early + // payment amounts and applicable date as per rest + updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO, principalPortionMap, installment, + detail, unprocessed.plus(addToPrinciapal), installments); + totalRepaymentExpected = totalRepaymentExpected.add(unprocessed.plus(addToPrinciapal).getAmount()); + totalCumulativePrincipal = totalCumulativePrincipal.plus(unprocessed.plus(addToPrinciapal)); + + reducePrincipal = reducePrincipal.plus(unprocessed); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); + + } + } + + } + + // identify late payments and adds compounding details to map + if (!reducePrincipal.isZero() || !installment.periodDueDate().isBefore(DateUtils.getLocalDateOfTenant())) { + latePaymentMap.clear(); + } else { + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, scheduledDueDate, + installments, true); + } + } + periods.add(installment); + // Updates principal paid map with efective date for reducing + // the amount from outstanding balance(interest calculation) + LocalDate amountApplicableDate = installment.periodDueDate(); + if (loanApplicationTerms.isInterestRecalculationEnabled()) { + amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + } + updateMapWithAmount(principalPortionMap, principalForThisPeriod, amountApplicableDate); + + // update outstanding balance for interest calculation + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, scheduledDueDate, + outstandingBalanceAsPerRest, false); + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, scheduledDueDate, + outstandingBalanceAsPerRest, true); + // handle cumulative fields - loanTermInDays += daysInPeriod; - totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount()); - totalInterestCharged = totalInterestCharged.add(interestForThisinstallment.getAmount()); - totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); - totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); + loanTermInDays += periodDays; + totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod); + totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount()); periodStartDate = scheduledDueDate; periodStartDateApplicableForInterest = periodStartDate; - - if (!recalculatedInterestComponent) { - periodNumber++; - scheduleStartDateAsPerFrequency = periodStartDate; - } instalmentNumber++; + periodNumber++; } - // 7. determine fees and penalties + // 7. determine fees and penalties for charges which depends on total + // loan interest for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()), Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), - loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, + loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), lastTransactionDate); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), - loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, + loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), lastTransactionDate); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); + totalRepaymentExpected = totalRepaymentExpected.add(feeChargesForInstallment.getAmount()).add( + penaltyChargesForInstallment.getAmount()); loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount()); } } // this block is to add extra re-payment schedules with interest portion - // if the last payment is missed - if (diffAmt != null && !diffAmt.isEmpty() && !periodStartDate.isAfter(LocalDate.now())) { - boolean recalculatedInterestComponent = true; - Map processDetails = new TreeMap<>(); - for (RecalculationDetail detail : diffAmt) { - if (detail.isLatePayment() && periodStartDate.isAfter(detail.getStartDate()) && detail.getToDate().isAfter(periodStartDate)) { - detail.updateStartDate(periodStartDate); - } - if (!periodStartDate.isAfter(detail.getStartDate()) && detail.isLatePayment() && !detail.isInterestompound()) { - if (processDetails.containsKey(detail.getToDate())) { - RecalculationDetail recalculationDetail = processDetails.get(detail.getToDate()); - RecalculationDetail updatedDetail = new RecalculationDetail(recalculationDetail.isLatePayment(), - recalculationDetail.getStartDate(), recalculationDetail.getToDate(), recalculationDetail.getAmount().plus( - detail.getAmount()), detail.isInterestompound()); - processDetails.put(updatedDetail.getToDate(), updatedDetail); - } else { - processDetails.put(detail.getToDate(), detail); - } - } - } - - for (RecalculationDetail detail : processDetails.values()) { - LocalDate fromDate = detail.getStartDate(); - LocalDate toDate = detail.getToDate(); - if (fromDate.isBefore(LocalDate.now())) { - BigDecimal interestForLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, - detail.getAmount(), fromDate, toDate); - Money interestDueToLatePayment = Money.of(detail.getAmount().getCurrency(), interestForLatePayment); - if (interestDueToLatePayment.isGreaterThanZero()) { - totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount()); - totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount()); - - final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, fromDate, - toDate, interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, - interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, - recalculatedInterestComponent); - periods.add(installment); - instalmentNumber++; - } - } - } + // if the loan not paid with in loan term + LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + if (isInterestRecalculationRequired(loanApplicationTerms, transactions) && latePaymentMap.size() > 0 + && currentDate.isAfter(periodStartDate)) { + Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms, holidayDetailDTO, currency, + periods, periodStartDate, actualRepaymentDate, instalmentNumber, latePaymentMap, currentDate, + loanRepaymentScheduleTransactionProcessor, transactions, installments); + totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); } loanApplicationTerms.resetFixedEmiAmount(); - return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, - totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, - totalOutstanding); + return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, + totalCumulativePrincipal.getAmount(), totalPrincipalPaid, totalCumulativeInterest.getAmount(), totalFeeChargesCharged, + totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); + } + + private boolean isInterestRecalculationRequired(final LoanApplicationTerms loanApplicationTerms, + Collection transactions) { + return loanApplicationTerms.isInterestRecalculationEnabled() && transactions != null; + } + + /** + * Method calculates interest on not paid outstanding principal and interest + * (if compounding is enabled) till current date and adds new repayment + * schedule detail + * + */ + private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Collection periods, + LocalDate periodStartDate, LocalDate actualRepaymentDate, int instalmentNumber, Map latePaymentMap, + LocalDate currentDate, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final Collection transactions, List installments) { + boolean isFirstRepayment = false; + LocalDate startDate = periodStartDate; + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, currentDate, installments, false); + Money outstanding = Money.zero(currency); + Money totalInterest = Money.zero(currency); + Map retainEntries = new HashMap<>(); + double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); + int periodNumberTemp = 1; + do { + + actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, + isFirstRepayment); + int daysInPeriod = Days.daysBetween(periodStartDate, actualRepaymentDate).getDays(); + if (actualRepaymentDate.isAfter(currentDate)) { + actualRepaymentDate = currentDate; + } + outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding, retainEntries); + + Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, + actualRepaymentDate, transactions); + + for (RecalculationDetail detail : applicableTransactions) { + if (detail.isProcessed()) { + continue; + } + if (detail.getTransactionDate().isBefore(actualRepaymentDate)) { + List currentTransactions = createCurrentTransactionList(detail); + + if (!periodStartDate.isEqual(detail.getTransactionDate())) { + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, + totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, + loanApplicationTerms, periodNumberTemp, mc, latePaymentMap, periodStartDate, detail.getTransactionDate(), + daysInPeriod); + + Money interest = principalInterestForThisPeriod.interest(); + totalInterest = totalInterest.plus(interest); + + LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, + startDate, detail.getTransactionDate(), totalInterest.zero(), totalInterest.zero(), totalInterest, + totalInterest.zero(), totalInterest.zero(), totalInterest, true); + periods.add(installment); + totalInterest = totalInterest.zero(); + addLoanRepaymentScheduleInstallment(installments, installment); + periodStartDate = detail.getTransactionDate(); + startDate = detail.getTransactionDate(); + } + loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, installments); + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, currentDate, installments, + false); + outstanding = outstanding.zero(); + outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding, retainEntries); + if (latePaymentMap.isEmpty() && !outstanding.isGreaterThanZero()) { + break; + } + } + } + + if (outstanding.isGreaterThanZero()) { + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), + totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, + periodNumberTemp, mc, latePaymentMap, periodStartDate, actualRepaymentDate, daysInPeriod); + Money interest = principalInterestForThisPeriod.interest(); + totalInterest = totalInterest.plus(interest); + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { + LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(actualRepaymentDate.minusDays(1), + loanApplicationTerms, holidayDetailDTO); + latePaymentMap.put(compoundingEffectiveDate, interest); + + } + } + periodStartDate = actualRepaymentDate; + } while (actualRepaymentDate.isBefore(currentDate) && outstanding.isGreaterThanZero()); + + LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, startDate, + actualRepaymentDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(), totalInterest.zero(), + totalInterest, true); + periods.add(installment); + return totalInterest; + } + + private Collection getApplicableTransactionsForPeriod(final LoanApplicationTerms loanApplicationTerms, + LocalDate repaymentDate, final Collection transactions) { + Collection applicableTransactions = new ArrayList<>(); + if (isInterestRecalculationRequired(loanApplicationTerms, transactions)) { + for (RecalculationDetail detail : transactions) { + if (!detail.getTransactionDate().isAfter(repaymentDate)) { + applicableTransactions.add(detail); + } + } + transactions.removeAll(applicableTransactions); + } + return applicableTransactions; + } + + private List createCurrentTransactionList(RecalculationDetail detail) { + List currentTransactions = new ArrayList<>(2); + currentTransactions.add(detail.getTransaction()); + detail.setProcessed(true); + return currentTransactions; + } + + private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map latePaymentMap, Money outstanding, + Map retainEntries) { + for (Map.Entry 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; + } + + /** + * method applies early payment strategy as per the configurations provided + */ + private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms, Money reducePrincipal) { + if (reducePrincipal.isGreaterThanZero()) { + switch (loanApplicationTerms.getRescheduleStrategyMethod()) { + case REDUCE_EMI_AMOUNT: + // in this case emi amount will be reduced but number of + // installments won't change + if (!loanApplicationTerms.isMultiDisburseLoan()) { + loanApplicationTerms.setFixedEmiAmount(null); + } + reducePrincipal = reducePrincipal.zero(); + break; + case REDUCE_NUMBER_OF_INSTALLMENTS: + // number of installments will reduce but emi amount won't + // get effected + reducePrincipal = reducePrincipal.zero(); + break; + case RESCHEDULE_NEXT_REPAYMENTS: + // will reduce principal from the reduce Principal for each + // installment(means installments will have less emi amount) + // until this + // amount becomes zero + break; + default: + break; + } + } + return reducePrincipal; + } + + /** + * Identifies all the past date principal changes and apply them on + * outstanding balance for future calculations + */ + private Money updateBalanceForInterestCalculation(final Map principalPortionMap, final LocalDate scheduledDueDate, + final Money outstandingBalanceAsPerRest, boolean addMapDetails) { + List removeFromprincipalPortionMap = new ArrayList<>(); + Money outstandingBalance = outstandingBalanceAsPerRest; + for (Map.Entry principal : principalPortionMap.entrySet()) { + if (!principal.getKey().isAfter(scheduledDueDate)) { + if (addMapDetails) { + outstandingBalance = outstandingBalance.plus(principal.getValue()); + } else { + outstandingBalance = outstandingBalance.minus(principal.getValue()); + } + removeFromprincipalPortionMap.add(principal.getKey()); + } + } + for (LocalDate date : removeFromprincipalPortionMap) { + principalPortionMap.remove(date); + } + return outstandingBalance; + } + + /** + * this Method updates late/ not paid installment components to Map with + * effective date as per REST(for principal portion ) and compounding + * (interest or fee or interest and fee portions) frequency + * + */ + private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final MonetaryCurrency currency, final Map latePaymentMap, final LocalDate scheduledDueDate, + List installments, boolean applyRestFrequencyForPrincipal) { + latePaymentMap.clear(); + Money totalCompoundingAmount = Money.zero(currency); + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { + if (loanRepaymentScheduleInstallment.isNotFullyPaidOff() + && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate) + && !loanRepaymentScheduleInstallment.isRecalculatedInterestComponent()) { + LocalDate principalEffectiveDate = loanRepaymentScheduleInstallment.getDueDate(); + if (applyRestFrequencyForPrincipal) { + principalEffectiveDate = getNextRestScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), + loanApplicationTerms, holidayDetailDTO); + } + updateMapWithAmount(latePaymentMap, loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency), + principalEffectiveDate); + totalCompoundingAmount = totalCompoundingAmount.plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency)); + totalCompoundingAmount = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, + totalCompoundingAmount, loanRepaymentScheduleInstallment); + } + } + + if (totalCompoundingAmount.isGreaterThanZero()) { + updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), DateUtils.getLocalDateOfTenant()); + } + } + + private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final MonetaryCurrency currency, final Map latePaymentMap, Money totalCompoundingAmount, + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), + loanApplicationTerms, holidayDetailDTO); + 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; + } + updateMapWithAmount(latePaymentMap, amount, compoundingEffectiveDate); + totalCompoundingAmount = totalCompoundingAmount.plus(amount); + } + return totalCompoundingAmount; + } + + /** + * This Method updates principal paid component to map with effective date + * as per the REST + * + */ + private void updatePrincipalPaidPortionToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + Map principalPortionMap, final LoanScheduleModelPeriod installment, final RecalculationDetail detail, + final Money unprocessed, final List installments) { + LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); + updateMapWithAmount(principalPortionMap, unprocessed, applicableDate); + installment.addPrincipalAmount(unprocessed); + LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1); + lastInstallment.updatePrincipal(lastInstallment.getPrincipal(unprocessed.getCurrency()).plus(unprocessed).getAmount()); + lastInstallment.payPrincipalComponent(detail.getTransactionDate(), unprocessed); + } + + /** + * merges all the applicable amounts(disbursements, late payment compounding + * and principal change as per rest) changes to single map for interest + * calculation + */ + private Map mergeLateAndPaymentMaps(final Map princiaplPaidMap, + final Map latePaymentMap, Map disburseDetailsMap) { + Map map = new TreeMap<>(); + map.putAll(latePaymentMap); + + for (Map.Entry mapEntry : disburseDetailsMap.entrySet()) { + Money value = mapEntry.getValue(); + if (map.containsKey(mapEntry.getKey())) { + value = value.plus(map.get(mapEntry.getKey())); + } + map.put(mapEntry.getKey(), value); + } + + for (Map.Entry mapEntry : princiaplPaidMap.entrySet()) { + Money value = mapEntry.getValue().negated(); + if (map.containsKey(mapEntry.getKey())) { + value = value.plus(map.get(mapEntry.getKey())); + } + map.put(mapEntry.getKey(), value); + } + return map; + } + + /** + * calculates Interest stating date as per the settings + */ + private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate, + final LocalDate idealDisbursementDate, LocalDate periodStartDateApplicableForInterest) { + if (periodStartDate.isBefore(idealDisbursementDate)) { + if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { + periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); + } else { + periodStartDateApplicableForInterest = idealDisbursementDate; + } + } + return periodStartDateApplicableForInterest; + } + + private void updateMapWithAmount(final Map map, final Money amount, final LocalDate amountApplicableDate) { + Money principalPaid = amount; + if (map.containsKey(amountApplicableDate)) { + principalPaid = map.get(amountApplicableDate).plus(principalPaid); + } + map.put(amountApplicableDate, principalPaid); } @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO) { + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, + final CalendarInstance restCalendarInstance) { final Loan loan = loanRescheduleRequest.getLoan(); final LoanSummary loanSummary = loan.getSummary(); @@ -675,7 +997,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // get the loan application terms from the Loan object - final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency); + final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance); // update the number of repayments loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); @@ -748,8 +1070,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, - totalOutstandingInterestPaymentDueToGrace, daysInInstallment, outstandingBalance, loanApplicationTerms, - periodNumber, mathContext); + totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, + null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); // update the interest due for the period period.updateInterestDue(principalInterestForThisPeriod.interest()); @@ -766,8 +1088,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, - totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, daysInInstallment, - outstandingBalance, loanApplicationTerms, periodNumber, mathContext); + totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, + loanApplicationTerms, periodNumber, mathContext, null, installmentFromDate, adjustedInstallmentDueDate, + daysInInstallment); period.updatePrincipalDue(principalInterestForThisPeriod.principal()); @@ -825,28 +1148,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding()); } - private BigDecimal calculateInterestForSpecificDays(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - int periodNumber, Money totalOutstandingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest, - double interestCalculationGraceOnRepaymentPeriodFraction, Money amount, LocalDate startDate, LocalDate endDate) { - PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod(this.paymentPeriodsInOneYearCalculator, - interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, totalOutstandingInterestPaymentDueToGrace.zero(), - daysInPeriodApplicableForInterest, amount); - - int days = Days.daysBetween(startDate, endDate).getDays(); - double interest = calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest.interest().getAmount(), days); - - return BigDecimal.valueOf(interest); - } - - private double calculateInterestForDays(int daysInPeriodApplicableForInterest, BigDecimal interest, int days) { + protected double calculateInterestForDays(int daysInPeriodApplicableForInterest, BigDecimal interest, int days) { if (interest.doubleValue() == 0) { return 0; } return ((interest.doubleValue()) / daysInPeriodApplicableForInterest) * days; } public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, - Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest, - Money outstandingBalance, LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc); + Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, + LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, Map principalVariation, + LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod); protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { return periodNumber == numberOfRepayments; @@ -862,37 +1173,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return chargesDueAtTimeOfDisbursement; } - private BigDecimal disbursementForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate startDate, LocalDate endDate, - final Collection disbursementDatas, boolean excludePastUndisbursed) { - BigDecimal principal = BigDecimal.ZERO; - for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { - if (!excludePastUndisbursed - || (excludePastUndisbursed && (disbursementData.isDisbursed() || !disbursementData.disbursementDate().isBefore( - LocalDate.now())))) { - if (disbursementData.isDueForDisbursement(startDate, endDate)) { - disbursementDatas.add(disbursementData); - principal = principal.add(disbursementData.amount()); - } - } - } - return principal; - } - - private BigDecimal disbursementAfterPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate endDate, - LocalDate lastinstallmentDueDate) { - BigDecimal principal = BigDecimal.ZERO; - for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { - - if (disbursementData.disbursementDate().isAfter(endDate) && lastinstallmentDueDate.isAfter(disbursementData.disbursementDate())) { - principal = principal.add(disbursementData.amount()); - } - } - - return principal; - } - private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate, - final Collection periods, final BigDecimal chargesDueAtTimeOfDisbursement) { + final Collection periods, final BigDecimal chargesDueAtTimeOfDisbursement, + final Map disurseDetail, final boolean excludePastUndisbursed) { BigDecimal principal = BigDecimal.ZERO; MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { @@ -901,6 +1184,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount()), chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); principal = principal.add(disbursementData.amount()); + } else if (!excludePastUndisbursed || disbursementData.isDisbursed() + || !disbursementData.disbursementDate().isBefore(DateUtils.getLocalDateOfTenant())) { + disurseDetail.put(disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount())); } } return principal; @@ -922,6 +1208,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return periods; } + private Set seperateTotalCompoundingPercentageCharges(final Set loanCharges) { + Set interestCharges = new HashSet<>(); + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isSpecifiedDueDate() + && (loanCharge.getChargeCalculation().isPercentageOfInterest() || loanCharge.getChargeCalculation() + .isPercentageOfAmountAndInterest())) { + interestCharges.add(loanCharge); + } + } + loanCharges.removeAll(interestCharges); + return interestCharges; + } + private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, @@ -932,21 +1231,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add( - principalInterestForThisPeriod.interest().getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(principalInterestForThisPeriod.interest().getAmount()); - } else { - amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); - } else { - cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments))); - } + cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments, cumulative, loanCharge); } else if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -954,16 +1239,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount()); - } else { - amount = amount.add(principalDisbursed.getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); + cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, + cumulative, loanCharge); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { cumulative = cumulative.plus(loanCharge.amount()); } @@ -973,6 +1250,41 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return cumulative; } + private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed, + final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount()); + } else { + amount = amount.add(principalDisbursed.getAmount()); + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + cumulative = cumulative.plus(loanChargeAmt); + return cumulative; + } + + private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, int numberOfRepayments, + Money cumulative, final LoanCharge loanCharge) { + if (loanCharge.getChargeCalculation().isPercentageBased()) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { + amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add( + principalInterestForThisPeriod.interest().getAmount()); + } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { + amount = amount.add(principalInterestForThisPeriod.interest().getAmount()); + } else { + amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + cumulative = cumulative.plus(loanChargeAmt); + } else { + cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments))); + } + return cumulative; + } + private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, @@ -984,21 +1296,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add( - principalInterestForThisPeriod.interest().getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(principalInterestForThisPeriod.interest().getAmount()); - } else { - amount = amount.add(principalInterestForThisPeriod.principal().getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); - } else { - cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments))); - } + cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments, cumulative, loanCharge); } else if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -1006,16 +1304,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount()); - } else { - amount = amount.add(principalDisbursed.getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); + cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, + cumulative, loanCharge); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { cumulative = cumulative.plus(loanCharge.amount()); } @@ -1033,498 +1323,26 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List previousSchedule, LocalDate recalculateFrom, final LocalDate lastTransactionDate, - final int penaltyWaitPeriod) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate lastTransactionDate) { - LoanScheduleModel loanScheduleModel = null; - List installments = previousSchedule; - List removeInstallments = new ArrayList<>(); - MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); - LoanTransaction preCloseTransaction = getPreclosureTransaction(transactions, installments, currency, loanApplicationTerms, mc, - loanCharges); - Integer updatedInstallmentNumber = 1; - // temp fix to calculate from start need to fix properly - recalculateFrom = loanApplicationTerms.getExpectedDisbursementDate(); - for (LoanRepaymentScheduleInstallment installment : installments) { - if (installment.isRecalculatedInterestComponent() && recalculateFrom != null - && !recalculateFrom.isAfter(installment.getDueDate())) { - removeInstallments.add(installment); - } else { - installment.updateInstallmentNumber(updatedInstallmentNumber++); - } - } - installments.removeAll(removeInstallments); - if (previousSchedule == null) { - loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO); - installments = retrieveRepaymentSchedule(loanScheduleModel); - } - - if (recalculateFrom == null) { - recalculateFrom = loanApplicationTerms.getExpectedDisbursementDate(); - } - - final List recalculationDetails = new ArrayList<>(); - final Map retainRecalculationDetails = new HashMap<>(); - LocalDate processTransactionsForInterestCompound = loanApplicationTerms.getExpectedDisbursementDate(); - while (recalculateFrom.isAfter(processTransactionsForInterestCompound) && !transactions.isEmpty() - && loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { - - int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound); - List processTransactions = processTransactions(transactions, processTransactionsForInterestCompound); - if (processTransactions.size() > 0) { - loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency, - recalculationDetails, retainRecalculationDetails, false, installmentNumber, processTransactions, - preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); - } - recalculationDetails.retainAll(retainRecalculationDetails.values()); - processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound); - } - - while (!recalculateFrom.isAfter(LocalDate.now())) { - - int installmentNumber = findLastProcessedInstallmentNumber(installments, recalculateFrom); - List processTransactions = processTransactions(transactions, recalculateFrom); - if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor() - && processTransactions.size() > 0) { - loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency, - recalculationDetails, retainRecalculationDetails, true, installmentNumber, processTransactions, - preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); - if (loanScheduleModel != null) { - installments = retrieveRepaymentSchedule(loanScheduleModel); - } - } - final List earlypayments = new ArrayList<>(); - for (RecalculationDetail detail : recalculationDetails) { - if (!detail.isLatePayment()) { - earlypayments.add(detail); - } - } - - recalculationDetails.retainAll(retainRecalculationDetails.values()); - recalculationDetails.addAll(earlypayments); - loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, processTransactions, - installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); - if (loanScheduleModel != null) { - installments = retrieveRepaymentSchedule(loanScheduleModel); - } - recalculateFrom = getNextRecalculateFromDate(transactions, recalculateFrom); - - } - return loanScheduleModel; - - } - - /** - * Method identifies interest only repayment periods - */ - private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List previousSchedule, LoanScheduleModel loanScheduleModel, - List installments, MonetaryCurrency currency, - final List recalculationDetails, final Map retainRecalculationDetails, - boolean recalculate, int installmentNumber, List processTransactions, LoanTransaction preCloseTransaction, - final LocalDate lastTransactionDate, final int penaltyWaitPeriod, final List actualTransactions) { - LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1); - if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) { - processTransactions.add(loanTransaction); - return loanScheduleModel; - } - LocalDate processTransactionsForInterestCompound = loanTransaction.getTransactionDate(); - List scheduleInstallments = getInstallmentsForInterestCompound(installments, - processTransactionsForInterestCompound, retainRecalculationDetails.keySet()); - - if (!scheduleInstallments.isEmpty()) { - for (LoanRepaymentScheduleInstallment installment : installments) { - installment.resetDerivedComponents(); - installment.updateDerivedFields(currency, loanApplicationTerms.getExpectedDisbursementDate()); - } - loanRepaymentScheduleTransactionProcessor.applyTransaction(processTransactions, currency, installments); - - Money principalUnprocessed = Money.zero(currency); - Money interestUnprocessed = Money.zero(currency); - Money feeUnprocessed = Money.zero(currency); - boolean isBeforeFirstInstallment = false; - if (scheduleInstallments.size() == 1) { - LoanRepaymentScheduleInstallment installment = scheduleInstallments.get(0); - if (installment.getInstallmentNumber() == 1 - && (installment.getDueDate().isAfter(processTransactionsForInterestCompound) || (installment - .isRecalculatedInterestComponent() && installment.getDueDate().isEqual( - processTransactionsForInterestCompound)))) { - isBeforeFirstInstallment = true; - } - } - if (!isBeforeFirstInstallment) { - for (LoanRepaymentScheduleInstallment installment : scheduleInstallments) { - principalUnprocessed = principalUnprocessed.plus(installment.getPrincipalOutstanding(currency)); - interestUnprocessed = interestUnprocessed.plus(installment.getInterestOutstanding(currency)); - feeUnprocessed = feeUnprocessed.plus(installment.getFeeChargesOutstanding(currency)); - feeUnprocessed = feeUnprocessed.plus(installment.getPenaltyChargesOutstanding(currency)); - } - } - if (interestUnprocessed.isLessThan(loanTransaction.getAmount(currency))) { - LoanRepaymentScheduleInstallment lastProcessedInstallment = scheduleInstallments.get(scheduleInstallments.size() - 1); - LocalDate startDate = lastProcessedInstallment.getDueDate(); - if (isBeforeFirstInstallment) { - startDate = loanApplicationTerms.getExpectedDisbursementDate(); - } - RecalculationDetail recalculationDetail = new RecalculationDetail(false, startDate, processTransactionsForInterestCompound, - null, true); - retainRecalculationDetails.put(processTransactionsForInterestCompound, recalculationDetail); - recalculationDetails.add(recalculationDetail); - if (recalculate) { - recalculationDetails.retainAll(retainRecalculationDetails.values()); - loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, - processTransactions, installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, - penaltyWaitPeriod, actualTransactions); - - } - } - } - processTransactions.add(loanTransaction); - return loanScheduleModel; - } - - private LocalDate getNextRecalculateFromDate(List processTransactions, LocalDate preCalculationDate) { - LocalDate recalculateFrom = null; - for (LoanTransaction loanTransaction : processTransactions) { - if (preCalculationDate.isBefore(loanTransaction.getTransactionDate()) - && (recalculateFrom == null || recalculateFrom.isAfter(loanTransaction.getTransactionDate()))) { - recalculateFrom = loanTransaction.getTransactionDate(); - } - } - if (recalculateFrom == null) { - recalculateFrom = LocalDate.now().plusDays(1); - } - return recalculateFrom; - } - - /** - * Method calls regenerate schedule for a particular set of transactions - * till all the installments are processed to identify early or late - * payments and will be used in regeneration of schedule - * - * @param preCloseTransaction - * TODO - */ - private LoanScheduleModel recalculateInstallment(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List previousSchedule, LoanScheduleModel loanScheduleModel, - List installments, List processTransactions, final int skipRecalculation, - final List recalculationDetails, LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate, - final int penaltyWaitPeriod, final List actualTransactions) { - int processInstallmentsFrom = 0; - Integer lastInstallmentNumber = previousSchedule.size(); - while (processInstallmentsFrom < lastInstallmentNumber) { - RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, - processTransactions, loanRepaymentScheduleTransactionProcessor, processInstallmentsFrom, installments, - recalculationDetails, skipRecalculation, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, - actualTransactions); - processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber(); - if (recalculatedSchedule.getLoanScheduleModel() != null) { - loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); - installments = retrieveRepaymentSchedule(loanScheduleModel); - lastInstallmentNumber = installments.size(); - } - } - return loanScheduleModel; - } - - /** - * Method identifies late or early payments for schedule recalculation. - * - * @param preCloseTransaction - * TODO - */ - private RecalculatedSchedule recalculateInterest(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, - final List installments, final List recalculationDetails, - final int skipRecalculation, final LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate, - final int penaltyWaitPeriod, final List actualTransactions) { - boolean processRecalculate = false; - int processedInstallmentNumber = installmentNumber; - - final List diffAmt = new ArrayList<>(); - Money unpaidPricipal = loanApplicationTerms.getPrincipal(); - - MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); - final List processinstallmets = new ArrayList<>(); - for (LoanRepaymentScheduleInstallment installment : installments) { - installment.resetDerivedComponents(); - installment.updateDerivedFields(currency, loanApplicationTerms.getExpectedDisbursementDate()); - } - for (LoanRepaymentScheduleInstallment installment : installments) { - processinstallmets.add(installment); - unpaidPricipal = unpaidPricipal.minus(installment.getPrincipal(currency)); - if (installment.getInstallmentNumber() <= installmentNumber) { - continue; - } - processedInstallmentNumber = installment.getInstallmentNumber(); - if (installment.getInstallmentNumber() == installments.size()) { - processRecalculate = true; - } - - List transactionsForInstallment = new ArrayList<>(); - Map recalculationDates = new HashMap<>(); - LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, - holidayDetailDTO); - for (LoanTransaction loanTransaction : transactions) { - LocalDate loantransactionDate = loanTransaction.getTransactionDate(); - if (!loantransactionDate.isAfter(transactionsDate)) { - transactionsForInstallment.add(loanTransaction); - recalculationDates.put(loantransactionDate, - getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO)); - } - } - - /* - * if (installment.isRecalculatedInterestComponent() && - * installment.getPrincipal(currency).isGreaterThanZero()) { - * diffAmt.add(new RecalculationDetail(false, - * installment.getDueDate(), null, - * installment.getPrincipal(currency), false)); } - */ - List earlypaymentDetail = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule( - transactionsForInstallment, currency, processinstallmets, installment, recalculationDates, preCloseTransaction); - - // this block is to create early payment entries for schedule - // generation - for (RecalculationDetail recalculationDetail : earlypaymentDetail) { - if (!recalculationDetail.getStartDate().isAfter(installment.getDueDate()) - && recalculationDetail.getStartDate().isAfter(installment.getFromDate())) { - diffAmt.add(recalculationDetail); - } - } - - if (installment.getDueDate().isBefore(LocalDate.now())) { - LocalDate startDate = installment.getDueDate(); - Money totalOutstanding = installment.getTotalOutstanding(currency); - boolean reduceStartDate = false; - - // this block is to identify late payment based on the rest - // calculation date - while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) { - LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); - LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, holidayDetailDTO); - if (reduceStartDate) { - startDate = startDate.minusDays(1); - } - applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency, - loanApplicationTerms, holidayDetailDTO, installments, preCloseTransaction); - totalOutstanding = installment.getTotalOutstanding(currency); - if (totalOutstanding.isGreaterThanZero()) { - Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency); - switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { - case INTEREST: - latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency)); - break; - case INTEREST_AND_FEE: - latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency)) - .plus(installment.getFeeChargesOutstanding(currency)) - .plus(installment.getPenaltyChargesOutstanding(currency)); - break; - case FEE: - latepaymentoutstanding = latepaymentoutstanding.plus(installment.getFeeChargesOutstanding(currency)).plus( - installment.getPenaltyChargesOutstanding(currency)); - break; - - default: - break; - } - RecalculationDetail recalculationDetail = new RecalculationDetail(true, recalculateFrom, recalcualteTill, - latepaymentoutstanding, false); - diffAmt.add(recalculationDetail); - } - startDate = recalculateFrom.plusDays(1); - reduceStartDate = true; - } - } - - if ((!diffAmt.isEmpty() && skipRecalculation < installment.getInstallmentNumber()) - || skipRecalculation == installment.getInstallmentNumber()) { - processRecalculate = true; - } - break; - - } - LoanScheduleModel model = null; - recalculationDetails.addAll(diffAmt); - if (processRecalculate) { - LocalDate prepayDate = null; - if (preCloseTransaction != null) { - prepayDate = preCloseTransaction.getTransactionDate(); - } - model = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, prepayDate, lastTransactionDate); - model = updateOverDueCharges(loanCharges, model, lastTransactionDate, currency, loanRepaymentScheduleTransactionProcessor, - loanApplicationTerms.getExpectedDisbursementDate(), penaltyWaitPeriod, actualTransactions); - } - return new RecalculatedSchedule(model, processedInstallmentNumber); - } - - private LoanScheduleModel updateOverDueCharges(final Set loanCharges, final LoanScheduleModel model, - LocalDate lastTransactionDate, final MonetaryCurrency currency, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate disbursementDate, - final int penaltyWaitPeriod, final List transactions) { - Set chargesForUpdate = new HashSet<>(); - LoanScheduleModel loanScheduleModel = model; - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isActive() && loanCharge.isOverdueInstallmentCharge() && loanCharge.isPenaltyCharge() - && loanCharge.getChargeCalculation().isPercentageBased() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { - chargesForUpdate.add(loanCharge); - } - } - - if (!chargesForUpdate.isEmpty()) { - LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod); - Collection periods = model.getPeriods(); - BigDecimal totalPenaltyChargesCharged = model.getTotalPenaltyChargesCharged(); - for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { - if (loanScheduleModelPeriod.isRepaymentPeriod()) { - Money overdueChargeForInstallment = overDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), - loanScheduleModelPeriod.periodDueDate(), chargesForUpdate, currency); - totalPenaltyChargesCharged = totalPenaltyChargesCharged.subtract(overdueChargeForInstallment.getAmount()); - BigDecimal feeChargesForInstallment = BigDecimal.ZERO; - loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.negated().getAmount()); - } - } - - List installments = retrieveRepaymentSchedule(model); - List loanTransactions = copyTransactions(transactions); - loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(disbursementDate, loanTransactions, currency, - installments, loanCharges, lastTransactionDate); - Map installmentMap = getRepaymentsAsMap(installments); - - for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { - if (loanScheduleModelPeriod.isRepaymentPeriod()) { - LoanRepaymentScheduleInstallment installment = installmentMap.get(loanScheduleModelPeriod.periodNumber()); - Money overdueChargeForInstallment = Money.zero(currency); - overdueChargeForInstallment = cumulativeOverDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), - loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, installment.getPrincipalOutstanding(currency) - .getAmount(), installment.getInterestOutstanding(currency).getAmount(), graceDate.isAfter(installment - .getDueDate())); - totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(overdueChargeForInstallment.getAmount()); - BigDecimal feeChargesForInstallment = BigDecimal.ZERO; - loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.getAmount()); - } - } - loanScheduleModel = LoanScheduleModel.withOverdueChargeUpdation(periods, model, totalPenaltyChargesCharged); - } - return loanScheduleModel; - } - - private Map getRepaymentsAsMap(List installments) { - Map installmentMap = new HashMap<>(); - for (LoanRepaymentScheduleInstallment installment : installments) { - installmentMap.put(installment.getInstallmentNumber(), installment); - } - return installmentMap; - } - - private List copyTransactions(List transactions) { - List loanTransactions = new ArrayList<>(); + Collection recalculationDetails = new ArrayList<>(); for (LoanTransaction loanTransaction : transactions) { - if (loanTransaction.isNotReversed() && !loanTransaction.isAccrual()) { - LoanTransaction transaction = LoanTransaction.copyTransactionProperties(loanTransaction); - loanTransactions.add(transaction); - } + recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), loanTransaction)); } - return loanTransactions; + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, lastTransactionDate, + loanRepaymentScheduleTransactionProcessor); } - private Money cumulativeOverDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency monetaryCurrency, final BigDecimal principalOverdue, - final BigDecimal interestOverdue, final boolean recalculate) { - - Money cumulative = Money.zero(monetaryCurrency); - - for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { - BigDecimal amount = BigDecimal.ZERO; - if (recalculate) { - switch (loanCharge.getChargeCalculation()) { - case PERCENT_OF_AMOUNT: - amount = amount.add(principalOverdue); - break; - case PERCENT_OF_AMOUNT_AND_INTEREST: - amount = amount.add(principalOverdue).add(interestOverdue); - break; - case PERCENT_OF_INTEREST: - amount = amount.add(interestOverdue); - break; - default: - break; - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - cumulative = cumulative.plus(loanChargeAmt); - } - } - } + private void addLoanRepaymentScheduleInstallment(final List installments, + final LoanScheduleModelPeriod scheduledLoanInstallment) { + if (scheduledLoanInstallment.isRepaymentPeriod()) { + final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null, + scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), + scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), + scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); + installments.add(installment); } - - return cumulative; - } - - private Money overDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, - final MonetaryCurrency monetaryCurrency) { - Money cumulative = Money.zero(monetaryCurrency); - for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { - cumulative = cumulative.plus(loanCharge.chargeAmount()); - } - } - } - return cumulative; - } - - private List retrieveRepaymentSchedule(LoanScheduleModel model) { - final List installments = new ArrayList<>(); - for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) { - if (scheduledLoanInstallment.isRepaymentPeriod()) { - final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null, - scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), - scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), - scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); - installments.add(installment); - } - } - return installments; - } - - /** - * Method to identify which transaction did the payment for current - * installment - * - * @param preCloseTransaction - * TODO - */ - private void applyRest(List loanTransactions, LocalDate from, LocalDate to, - LoanRepaymentScheduleInstallment installment, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, - final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final List installments, LoanTransaction preCloseTransaction) { - List transactions = new ArrayList<>(); - Map recalculationDates = new HashMap<>(); - for (LoanTransaction transaction : loanTransactions) { - LocalDate loantransactionDate = transaction.getTransactionDate(); - if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) { - transactions.add(transaction); - recalculationDates.put(loantransactionDate, - getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO)); - } - } - - loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(transactions, currency, installments, installment, - recalculationDates, preCloseTransaction); } private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, @@ -1541,162 +1359,130 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return nextScheduleDate; } + private LocalDate getNextCompoundScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, + final HolidayDetailDTO holidayDetailDTO) { + LocalDate nextScheduleDate = null; + if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { + nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate, + loanApplicationTerms, holidayDetailDTO); + } else { + CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); + nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); + } + + return nextScheduleDate; + } + /** * Method returns the amount payable to close the loan account as of today. */ @Override public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final List installments, - MonetaryCurrency currency, final LocalDate onDate, LocalDate interestChargedFromLocalDate, - LoanApplicationTerms loanApplicationTerms, MathContext mc, Set charges) { + MonetaryCurrency currency, final LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, + Set charges, final HolidayDetailDTO holidayDetailDTO) { Money feeCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency); Money totalPrincipal = Money.zero(currency); Money totalInterest = Money.zero(currency); LocalDate calculateInterestFrom = onDate; - LocalDate dueDate = onDate; - LocalDate repaymentDueDate = null; LocalDate periodStartDate = onDate; - boolean isInterestRepayment = false; - Money inerestForCurrentInstallment = Money.zero(currency); Money principalOutstanding = Money.zero(currency); - Integer periodNumber = 0; - LocalDate lastDueDate = loanApplicationTerms.getExpectedDisbursementDate(); - for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { - if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) { - calculateInterestFrom = currentInstallment.getFromDate(); - if (currentInstallment.isRecalculatedInterestComponent()) { - isInterestRepayment = true; - } else { - isInterestRepayment = false; + Integer periodNumber = 1; + // for multi disburse loan to identify all disbursed principal for + // interest calculation + Map differenceMap = new HashMap<>(); + Map disburseAmtMap = new HashMap<>(); + if (loanApplicationTerms.isMultiDisburseLoan()) { + for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { + if (disbursementData.isDisbursed()) { + principalOutstanding = principalOutstanding.plus(disbursementData.amount()); + disburseAmtMap.put(disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount())); } } + } else { + principalOutstanding = loanApplicationTerms.getPrincipal(); + } + + // adds all the unpaid principal till date and identifies the total + // outstanding for interest calculation + for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { if (currentInstallment.isNotFullyPaidOff()) { if (!currentInstallment.getDueDate().isAfter(onDate)) { totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency)); feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency)); penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); + principalOutstanding = principalOutstanding.minus(currentInstallment.getPrincipal(currency)); + updateMapWithAmount(differenceMap, currentInstallment.getPrincipalOutstanding(currency), + currentInstallment.getDueDate()); + updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, differenceMap, Money.zero(currency), + currentInstallment); } else { - principalOutstanding = principalOutstanding.plus(currentInstallment.getPrincipal(currency)); totalPrincipal = totalPrincipal.minus(currentInstallment.getPrincipalCompleted(currency)); totalInterest = totalInterest.minus(currentInstallment.getInterestPaid(currency)).minus( currentInstallment.getInterestWaived(currency)); - if (currentInstallment.getFromDate().isBefore(periodStartDate)) { - if (!isInterestRepayment) { - calculateInterestFrom = currentInstallment.getFromDate(); + if (!currentInstallment.getFromDate().isAfter(periodStartDate)) { + if (!currentInstallment.isRecalculatedInterestComponent()) { + periodStartDate = currentInstallment.getFromDate(); } - periodStartDate = currentInstallment.getFromDate(); - dueDate = currentInstallment.getDueDate(); - inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency); - if (currentInstallment.isRecalculatedInterestComponent()) { - isInterestRepayment = true; - } else { - isInterestRepayment = false; - } - repaymentDueDate = null; - periodNumber = currentInstallment.getInstallmentNumber(); - } - if (isInterestRepayment && repaymentDueDate == null && !currentInstallment.isRecalculatedInterestComponent()) { - repaymentDueDate = currentInstallment.getDueDate(); + calculateInterestFrom = currentInstallment.getFromDate(); + } } - } - if (!currentInstallment.isRecalculatedInterestComponent() && currentInstallment.getDueDate().isAfter(lastDueDate)) { - lastDueDate = currentInstallment.getDueDate(); + } else { + principalOutstanding = principalOutstanding.minus(currentInstallment.getPrincipal(currency)); } } - if (interestChargedFromLocalDate != null && calculateInterestFrom.isBefore(interestChargedFromLocalDate)) { - calculateInterestFrom = interestChargedFromLocalDate; + + totalPrincipal = totalPrincipal.plus(principalOutstanding); + + for (Map.Entry disburseDetail : disburseAmtMap.entrySet()) { + if (disburseDetail.getKey().isAfter(periodStartDate)) { + differenceMap.put(disburseDetail.getKey(), disburseDetail.getValue()); + principalOutstanding = principalOutstanding.minus(disburseDetail.getValue()); + } } + + // interest calculation on outstanding amount till on date from the + // period start of installment where on date falls Money interest = Money.zero(currency); LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, true); final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); if (calculateInterestFrom.isBefore(onDate) && !periodStartDate.isBefore(idealDisbursementDate)) { - boolean useDailyInterest = false; - if (repaymentDueDate == null) { - if (isInterestRepayment) { - useDailyInterest = true; - } - repaymentDueDate = dueDate; + + boolean isFirstRepayment = false; + if (periodStartDate.isBefore(firstRepaymentdate)) { + isFirstRepayment = true; } - int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, repaymentDueDate).getDays(); - int days = Days.daysBetween(periodStartDate, onDate).getDays(); - int actualPeriodDays = Days.daysBetween(periodStartDate, repaymentDueDate).getDays(); - BigDecimal interestForInstallment = BigDecimal.ZERO; - if (useDailyInterest) { - interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, - principalOutstanding, calculateInterestFrom, repaymentDueDate); - interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment)); - interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days)); + LocalDate scheduledDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms, + isFirstRepayment); + isFirstRepayment = false; - } else { + LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, + idealDisbursementDate, periodStartDate); + int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); - LocalDate periodStartDateApplicableForInterest = calculateInterestFrom; - if (calculateInterestFrom.isBefore(idealDisbursementDate)) { - if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { - periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); - } else { - periodStartDateApplicableForInterest = idealDisbursementDate; - } - daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, repaymentDueDate).getDays(); - } - Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); - double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator - .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, repaymentDueDate, - loanApplicationTerms.getInterestChargedFromLocalDate(), - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); - Money calculateInterestOnPrincipal = principalOutstanding; - final Collection disbursementDatas = new ArrayList<>(); - if (loanApplicationTerms.isMultiDisburseLoan()) { - BigDecimal reducePrincipal = disbursementAfterPeriod(loanApplicationTerms, repaymentDueDate, lastDueDate); - principalOutstanding = principalOutstanding.minus(reducePrincipal); - calculateInterestOnPrincipal = principalOutstanding; - BigDecimal currentPeriodDisbursal = disbursementForPeriod(loanApplicationTerms, periodStartDate, repaymentDueDate, - disbursementDatas, true); - calculateInterestOnPrincipal = calculateInterestOnPrincipal.minus(currentPeriodDisbursal); - } - - PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, - totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, calculateInterestOnPrincipal); - interestForInstallment = BigDecimal.valueOf(calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest - .interest().getAmount(), actualPeriodDays)); - BigDecimal interestToBeAdded = BigDecimal.ZERO; - BigDecimal interestToBeReduced = BigDecimal.ZERO; - if (loanApplicationTerms.isMultiDisburseLoan()) { - for (DisbursementData disbursementData : disbursementDatas) { - Money disbursedAmt = Money.of(currency, disbursementData.amount()); - LocalDate calculateTill = disbursementData.disbursementDate(); - if (!onDate.isAfter(calculateTill)) { - principalOutstanding = principalOutstanding.minus(disbursedAmt); - } else { - interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, - periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, - interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateTill, onDate)); - } - - if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)) { - interestToBeReduced = interestToBeReduced.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, - periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, - interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateTill, repaymentDueDate)); - } - - } - } - interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment).minus(interestToBeReduced)); - BigDecimal interestForCloseDate = BigDecimal.valueOf(calculateInterestForDays(actualPeriodDays, interestForInstallment, - days)); - interest = interest.plus(interestForCloseDate).plus(interestToBeAdded); + if (calculateInterestFrom.isBefore(periodStartDateApplicableForInterest)) { + calculateInterestFrom = periodStartDateApplicableForInterest; } + if (calculateInterestFrom.isBefore(onDate)) { + double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), + totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), principalOutstanding, loanApplicationTerms, + periodNumber, mc, differenceMap, calculateInterestFrom, onDate, daysInPeriodApplicableForInterest); + interest = interest.plus(principalInterestForThisPeriod.interest()); + } } totalInterest = totalInterest.plus(interest); - totalPrincipal = totalPrincipal.plus(principalOutstanding); + PrincipalInterest principalInterest = new PrincipalInterest(principalOutstanding, interest, null); + // apply changes applicable in the period for (LoanCharge loanCharge : charges) { if (loanCharge.isActive() && loanCharge.isNotFullyPaid() && !loanCharge.isWaived() && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStartDate, onDate)) { @@ -1704,6 +1490,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener feeCharges = feeCharges.plus(loanCharge.amountOutstanding()); } else { penaltyCharges = penaltyCharges.plus(loanCharge.amountOutstanding()); + + } + } else if (loanCharge.isActive() && interest.isGreaterThanZero() && loanCharge.isInstalmentFee()) { + if (loanCharge.isFeeCharge()) { + feeCharges = feeCharges.plus(calculateInstallmentCharge(principalInterest, + loanApplicationTerms.getNumberOfRepayments(), feeCharges.zero(), loanCharge)); + } else { + penaltyCharges = penaltyCharges.plus(calculateInstallmentCharge(principalInterest, + loanApplicationTerms.getNumberOfRepayments(), feeCharges.zero(), loanCharge)); } } } @@ -1712,77 +1507,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener feeCharges.getAmount(), penaltyCharges.getAmount(), false); } - private Integer findLastProcessedInstallmentNumber(final List installments, LocalDate date) { - int installmentNumber = 0; - for (LoanRepaymentScheduleInstallment installment : installments) { - if (!installment.getDueDate().isAfter(date) && installmentNumber < installment.getInstallmentNumber()) { - installmentNumber = installment.getInstallmentNumber(); - } - } - - return installmentNumber; - } - - private List getInstallmentsForInterestCompound( - final List installments, final LocalDate date, Collection processedDates) { - List repaymentScheduleInstallments = new ArrayList<>(); - for (LoanRepaymentScheduleInstallment installment : installments) { - if (installment.getDueDate().isEqual(date) - && (!installment.isRecalculatedInterestComponent() || (processedDates.contains(date) && installment - .isRecalculatedInterestComponent()))) { - repaymentScheduleInstallments.clear(); - break; - } else if (installment.getDueDate().isBefore(date)) { - repaymentScheduleInstallments.add(installment); - } else { - if (installment.getInstallmentNumber() == 1) { - repaymentScheduleInstallments.add(installment); - } - break; - } - } - return repaymentScheduleInstallments; - } - - private List processTransactions(final List transactions, final LocalDate tillDate) { - List toProcess = new ArrayList<>(); - for (LoanTransaction loanTransaction : transactions) { - if (!loanTransaction.getTransactionDate().isAfter(tillDate)) { - toProcess.add(loanTransaction); - } - } - return toProcess; - } - - private LoanTransaction getPreclosureTransaction(List loanTransactions, - List installments, MonetaryCurrency currency, final LoanApplicationTerms applicationTerms, - MathContext mc, final Set loanCharges) { - LoanTransaction precloseTransaction = null; - Money collectedPrincipal = Money.zero(currency); - for (LoanTransaction loanTransaction : loanTransactions) { - if (precloseTransaction == null - || (!precloseTransaction.getTransactionDate().isAfter(loanTransaction.getTransactionDate()) && (loanTransaction.getId() == null || (precloseTransaction - .getId() != null && loanTransaction.getId().compareTo(precloseTransaction.getId()) == 1)))) { - precloseTransaction = loanTransaction; - } - collectedPrincipal = collectedPrincipal.plus(loanTransaction.getPrincipalPortion()); - } - if (precloseTransaction != null) { - LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency, - precloseTransaction.getTransactionDate(), applicationTerms.getInterestChargedFromLocalDate(), applicationTerms, mc, - loanCharges); - Money pendingPrinciapl = applicationTerms.getPrincipal().minus(collectedPrincipal) - .plus(precloseTransaction.getPrincipalPortion()); - if (pendingPrinciapl.isGreaterThan(loanRepaymentScheduleInstallment.getPrincipal(currency)) - || precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) { - precloseTransaction = null; - } - } - - return precloseTransaction; - - } - /** * set the value to zero if the provided value is null * diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index 261ced038..096495b1c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -6,7 +6,10 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; +import java.util.Map; +import org.joda.time.Days; +import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; @@ -42,14 +45,48 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, @SuppressWarnings("unused") final Money totalCumulativeInterest, @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, - final int daysInPeriodApplicableForInterest, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, - final int periodNumber, final MathContext mc) { + final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, + final Map principalVariation, final LocalDate periodStartDate, final LocalDate periodEndDate, + final int daysForInterestInFullPeriod) { + + LocalDate interestStartDate = periodStartDate; + Money interestForThisInstallment = totalCumulativePrincipal.zero(); + Money balanceForInterestCalculation = outstandingBalance; + Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; + final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); + if(principalVariation != null){ + for (Map.Entry principal : principalVariation.entrySet()) { + if (!principal.getKey().isAfter(periodEndDate)) { + int interestForDays = Days.daysBetween(interestStartDate, principal.getKey()).getDays(); + if (interestForDays > 0) { + final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, + interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, + interestForDays, balanceForInterestCalculation); + if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) { + interestForThisInstallment = interestForThisInstallment.plus(result.interest()); + } else { + interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, + result.interest().getAmount(), interestForDays)); + } + cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); + interestStartDate = principal.getKey(); + } + balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()); + } + + } + } + int interestForDays = Days.daysBetween(interestStartDate, periodEndDate).getDays(); final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, - interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace, - daysInPeriodApplicableForInterest, outstandingBalance); - - final Money interestForThisInstallment = result.interest(); + interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, interestForDays, + balanceForInterestCalculation); + if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) { + interestForThisInstallment = interestForThisInstallment.plus(result.interest()); + } else { + interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result + .interest().getAmount(), interestForDays)); + } Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java index e51cf73b7..18240cd27 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java @@ -6,7 +6,10 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; +import java.util.Map; +import org.joda.time.Days; +import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.Money; public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator { @@ -15,9 +18,10 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, - final int daysInPeriodApplicableForInterest, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, - final int periodNumber, final MathContext mc) { - + final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, + @SuppressWarnings("unused") Map principalVariation, LocalDate periodStartDate, LocalDate periodEndDate, + @SuppressWarnings("unused") int daysForInterestInFullPeriod) { + final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index fae0abd22..b82ad74be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -150,12 +150,11 @@ public final class LoanApplicationTerms { final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, - BigDecimal principalThresholdForLastInstalment, Integer installmentAmountInMultiplesOf) { + final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, + final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - final CalendarInstance restCalendarInstance = null; - final RecalculationFrequencyType recalculationFrequencyType = null; return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, @@ -173,7 +172,9 @@ public final class LoanApplicationTerms { final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, final LocalDate interestChargedFromDate, - final BigDecimal principalThresholdForLastInstalment, Integer installmentAmountInMultiplesOf) { + final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, + final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, + final InterestRecalculationCompoundingMethod compoundingMethod) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -197,9 +198,6 @@ public final class LoanApplicationTerms { final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - final CalendarInstance restCalendarInstance = null; - final RecalculationFrequencyType recalculationFrequencyType = null; return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay.getValue(), dayOfWeek, amortizationMethod, interestMethod, @@ -208,7 +206,7 @@ public final class LoanApplicationTerms { graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, + rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf); } @@ -1170,4 +1168,8 @@ public final class LoanApplicationTerms { public ApplicationCurrency getApplicationCurrency() { return this.currency; } + + public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { + return this.interestCalculationPeriodMethod; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index b624e7aa8..df40340d5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -12,6 +12,7 @@ import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -28,13 +29,12 @@ public interface LoanScheduleGenerator { LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, final HolidayDetailDTO holidayDetailDTO, List transactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - List previousSchedule, LocalDate recalculateFrom, LocalDate lastTransactionDate, - int penaltyWaitPeriod); + LocalDate lastTransactionDate); LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, - MonetaryCurrency currency, LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, - MathContext mc, Set charges); + MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, + Set charges, HolidayDetailDTO holidayDetailDTO); LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO); + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index 5e944f380..dad390711 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -99,4 +99,9 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public boolean isRecalculatedInterestComponent() { return false; } + + @Override + public void addPrincipalAmount(@SuppressWarnings("unused") Money principalDue) { + return; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java index 89ecefae8..1b8278b2c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; public interface LoanScheduleModelPeriod { @@ -33,4 +34,6 @@ public interface LoanScheduleModelPeriod { void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge); boolean isRecalculatedInterestComponent(); + + void addPrincipalAmount(Money principalDue); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java index 7b856bc94..876e929b0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java @@ -20,7 +20,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private final int periodNumber; private final LocalDate fromDate; private final LocalDate dueDate; - private final Money principalDue; + private Money principalDue; private final Money outstandingLoanBalance; private final Money interestDue; private Money feeChargesDue; @@ -124,6 +124,12 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge); this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge); } + + @Override + public void addPrincipalAmount(final Money principalDue){ + this.principalDue = this.principalDue.plus(principalDue); + this.totalDue = this.totalDue.plus(principalDue); + } @Override public boolean isRecalculatedInterestComponent() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java index 32c042dfb..e724f8ed1 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java @@ -6,55 +6,32 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import org.joda.time.LocalDate; -import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; public class RecalculationDetail { - private boolean isLatePayment; - private boolean isInterestompound; - private LocalDate startDate; - private LocalDate toDate; - private Money amount; + private LocalDate transactionDate; + private boolean isProcessed; + private LoanTransaction transaction; - public RecalculationDetail(final boolean isLatePayment, final LocalDate startDate, final LocalDate toDate, final Money amount, - final boolean isInterestompound) { - this.isLatePayment = isLatePayment; - this.startDate = startDate; - this.toDate = toDate; - this.amount = amount; - this.isInterestompound = isInterestompound; + public RecalculationDetail(final LocalDate transactionDate, final LoanTransaction transaction) { + this.transactionDate = transactionDate; + this.transaction = transaction; } - public boolean isLatePayment() { - return this.isLatePayment; + public LocalDate getTransactionDate() { + return this.transactionDate; } - public LocalDate getStartDate() { - return this.startDate; + public LoanTransaction getTransaction() { + return this.transaction; } - public LocalDate getToDate() { - return this.toDate; + public boolean isProcessed() { + return this.isProcessed; } - public Money getAmount() { - return this.amount; - } - - public boolean isOverlapping(LocalDate startDate, LocalDate endDate) { - return !this.startDate.isAfter(endDate) && !startDate.isAfter(this.toDate); - } - - public void updateAmount(Money amount) { - this.amount = amount; - } - - public boolean isInterestompound() { - return this.isInterestompound; - } - - - public void updateStartDate(LocalDate startDate) { - this.startDate = startDate; + public void setProcessed(boolean isProcessed) { + this.isProcessed = isProcessed; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index d36b7f9ef..9ae25a888 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -30,7 +30,11 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWr import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; @@ -66,6 +70,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; +import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.springframework.beans.factory.annotation.Autowired; @@ -249,9 +254,24 @@ public class LoanScheduleAssembler { final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); + RecalculationFrequencyType recalculationFrequencyType = null; + CalendarInstance restCalendarInstance = null; + + if (isInterestRecalculationEnabled) { + recalculationFrequencyType = loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); + if (!recalculationFrequencyType.isSameAsRepayment()) { + LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationRestFrequencyDateParamName, element); + if (calendarStartDate == null) { + calendarStartDate = expectedDisbursementDate; + } + restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType, + loanProduct.getProductInterestRecalculationDetails().getRestInterval()); + } + } final BigDecimal principalThresholdForLastInstalment = loanProduct.getPrincipalThresholdForLastInstalment(); - final Integer installmentAmountInMultiplesOf= loanProduct.getInstallmentAmountInMultiplesOf(); + final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf(); return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, @@ -259,8 +279,33 @@ public class LoanScheduleAssembler { principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, - graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf); + graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, + restCalendarInstance, principalThresholdForLastInstalment, installmentAmountInMultiplesOf); + } + + private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, + final RecalculationFrequencyType recalculationFrequencyType, final Integer frequency) { + final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + + CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; + switch (recalculationFrequencyType) { + case DAILY: + calendarFrequencyType = CalendarFrequencyType.DAILY; + break; + case MONTHLY: + calendarFrequencyType = CalendarFrequencyType.MONTHLY; + break; + case WEEKLY: + calendarFrequencyType = CalendarFrequencyType.WEEKLY; + break; + default: + break; + } + + final String title = "loan_recalculation_detail"; + final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), + calendarFrequencyType, frequency, repeatsOnDay); + return CalendarInstance.from(calendar, null, CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); } private List fetchDisbursementData(final JsonObject command) { @@ -387,8 +432,7 @@ public class LoanScheduleAssembler { public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, List transactions, final Set loanCharges, - final List previousSchedule, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateDueDateChargesFrom) { final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); @@ -400,24 +444,25 @@ public class LoanScheduleAssembler { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - final Long waitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); - int penaltyWaitPeriod = 0; - if (waitPeriod != null) { - penaltyWaitPeriod = waitPeriod.intValue(); - } return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, - loanRepaymentScheduleTransactionProcessor, previousSchedule, recalculateFrom, recalculateDueDateChargesFrom, - penaltyWaitPeriod); + loanRepaymentScheduleTransactionProcessor, recalculateDueDateChargesFrom); } public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, - MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Set loanCharges) { + MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Set loanCharges, + final Long officeId) { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms + .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, onDate, - loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms, mc, loanCharges); + loanApplicationTerms, mc, loanCharges, holidayDetailDTO); } private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index dcda24532..504b39a78 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -164,7 +164,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( - loan.fetchRepaymentScheduleInstallments(), currency, LocalDate.now(), loanApplicationTerms, loan.charges()); + loan.fetchRepaymentScheduleInstallments(), currency, LocalDate.now(), loanApplicationTerms, loan.charges(), + loan.getOfficeId()); Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); Money interestDue = Money.zero(currency); @@ -185,8 +186,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), - modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor, - LocalDate.now(), LocalDate.now()); + modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, LocalDate.now()); LoanScheduleData scheduleDate = model.toData(); Collection periodDatas = scheduleDate.getPeriods(); for (LoanSchedulePeriodData periodData : periodDatas) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java index 6bd84fae3..ec819dc09 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator; @@ -18,19 +19,19 @@ public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory { @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, - final HolidayDetailDTO holidayDetailDTO) { + final HolidayDetailDTO holidayDetailDTO,final CalendarInstance restCalendarInstance) { LoanRescheduleModel loanRescheduleModel = null; switch (interestMethod) { case DECLINING_BALANCE: loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO); + applicationCurrency, holidayDetailDTO, restCalendarInstance); break; case FLAT: loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO); + applicationCurrency, holidayDetailDTO, restCalendarInstance); break; case INVALID: diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java index b5bb26cf5..9e06cc66d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -15,5 +16,5 @@ public interface LoanReschedulerFactory { public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, - final HolidayDetailDTO holidayDetailDTO); + final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index b6e15e69f..d286ab9b6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -18,6 +18,9 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; @@ -41,19 +44,22 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP private final HolidayRepository holidayRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; + private final CalendarInstanceRepository calendarInstanceRepository; @Autowired public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, - final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService) { + final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, + final CalendarInstanceRepository calendarInstanceRepository) { this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.applicationCurrencyRepository = applicationCurrencyRepository; this.configurationDomainService = configurationDomainService; this.holidayRepository = holidayRepository; this.workingDaysRepository = workingDaysRepository; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; + this.calendarInstanceRepository = calendarInstanceRepository; } @Override @@ -78,8 +84,14 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP List oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance); LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, oldPeriods); return loanRescheduleModelWithOldPeriods; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 96656e9ff..4d1597082 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -321,8 +321,13 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance); final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 5487829c4..cf308cfd6 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -24,6 +24,7 @@ import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.DateUtils; @@ -32,6 +33,9 @@ import org.mifosplatform.infrastructure.core.service.PaginationHelper; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.SearchParameters; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.holiday.domain.Holiday; +import org.mifosplatform.organisation.holiday.domain.HolidayRepository; +import org.mifosplatform.organisation.holiday.domain.HolidayStatusType; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; @@ -39,11 +43,15 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; +import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.mifosplatform.portfolio.account.data.AccountTransferData; import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations; import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; @@ -59,6 +67,7 @@ import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.data.GroupRoleData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanApplicationTimelineData; import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData; @@ -125,6 +134,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final CalendarInstanceRepository calendarInstanceRepository; + private final HolidayRepository holidayRepository; + private final ConfigurationDomainService configurationDomainService; + private final WorkingDaysRepositoryWrapper workingDaysRepository; @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -135,7 +148,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource, final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService, - final LoanScheduleGeneratorFactory loanScheduleFactory) { + final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository, + final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, + final WorkingDaysRepositoryWrapper workingDaysRepository) { this.context = context; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -152,6 +167,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.jdbcTemplate = new JdbcTemplate(dataSource); this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); this.loanScheduleFactory = loanScheduleFactory; + this.calendarInstanceRepository = calendarInstanceRepository; + this.holidayRepository = holidayRepository; + this.configurationDomainService = configurationDomainService; + this.workingDaysRepository = workingDaysRepository; } @Override @@ -391,7 +410,20 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate earliestUnpaidInstallmentDate = LocalDate.now(); - final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory); + CalendarInstance restCalendarInstance = null; + HolidayDetailDTO holidayDetailDTO = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan + .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); + holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + } + + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory, + restCalendarInstance, holidayDetailDTO); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final Collection paymentOptions = this.codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); @@ -838,8 +870,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, - fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, - daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement); + fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement); } } @@ -907,7 +939,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); this.lastDueDate = this.disbursement.disbursementDate(); - this.outstandingLoanPrincipalBalance = this.disbursement.amount(); + this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.disbursementData = disbursementData; this.excludePastUndisbursed = isInterestRecalculationEnabled; } @@ -981,8 +1013,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed()); periods.add(periodData); this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); - } else if (data.isDueForDisbursement(fromDate, dueDate) - && this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) == 1) { + } else if (data.isDueForDisbursement(fromDate, dueDate)) { if (!excludePastUndisbursed || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { principal = principal.add(data.amount()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 4a4a234a1..a8ae5b170 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -695,8 +695,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList<>(); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); final Map changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); @@ -1409,8 +1415,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf WorkingDays workingDays = this.workingDaysRepository.findOne(); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); createLoanScheduleArchive(loan, scheduleGeneratorDTO); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java index 5716c34cf..6ab6a9b3e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestCalculationPeriodMethod.java @@ -42,4 +42,9 @@ public enum InterestCalculationPeriodMethod { } return repaymentMethod; } + + public boolean isDaily() { + return this.value.equals(InterestCalculationPeriodMethod.DAILY.getValue()); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java index ebfea5518..46053017e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java @@ -57,4 +57,13 @@ public enum InterestRecalculationCompoundingMethod { return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue()) || this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue()); } + + public boolean isCompoundingEnabled() { + return !this.getValue().equals(InterestRecalculationCompoundingMethod.NONE.getValue()); + } + + public boolean isInterestCompoundingEnabled() { + return this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST.getValue()) + || this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue()); + } }