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 dc38daa8a..7a302c240 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 @@ -1759,8 +1759,9 @@ public class Loan extends AbstractPersistable { } if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) { + LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); } updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanSummaryDerivedFields(); @@ -2295,6 +2296,11 @@ public class Loan extends AbstractPersistable { LoanStatus statusEnum = null; + LocalDate recalculateFrom = loanTransaction.getTransactionDate(); + if (adjustedTransaction != null && adjustedTransaction.getTransactionDate().isBefore(recalculateFrom)) { + recalculateFrom = adjustedTransaction.getTransactionDate(); + } + if (loanTransaction.isRecoveryRepayment()) { statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, LoanStatus.fromInt(this.loanStatus)); } else { @@ -2382,7 +2388,7 @@ public class Loan extends AbstractPersistable { if (reprocess) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), @@ -2395,7 +2401,7 @@ public class Loan extends AbstractPersistable { * of the transactions. for this need to save the reversed * transactions first and then new transactions. */ - // this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); + this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); } updateLoanSummaryDerivedFields(); @@ -2408,6 +2414,9 @@ public class Loan extends AbstractPersistable { doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); } + if (changedTransactionDetail != null) { + this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values()); + } return changedTransactionDetail; } @@ -2434,6 +2443,18 @@ public class Loan extends AbstractPersistable { return repaymentsOrWaivers; } + private List retreiveListOfTransactionsPostDisbursementExcludeAccruals() { + final List repaymentsOrWaivers = new ArrayList<>(); + for (final LoanTransaction transaction : this.loanTransactions) { + if (!transaction.isDisbursement() && transaction.isNotReversed() && !transaction.isAccrual()) { + repaymentsOrWaivers.add(transaction); + } + } + final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); + Collections.sort(repaymentsOrWaivers, transactionComparator); + return repaymentsOrWaivers; + } + private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { if (isOverPaid()) { @@ -2651,8 +2672,9 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); } ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, @@ -2797,8 +2819,9 @@ public class Loan extends AbstractPersistable { regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), @@ -3897,8 +3920,9 @@ public class Loan extends AbstractPersistable { workingDays); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory @@ -3976,17 +4000,24 @@ public class Loan extends AbstractPersistable { return maturityDate; } - public ChangedTransactionDetail recalculateSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, + public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - + LocalDate recalculateFrom = null; + List loanTransactions = this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); + for (LoanTransaction loanTransaction : loanTransactions) { + if (recalculateFrom == null || loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { + recalculateFrom = loanTransaction.getTransactionDate(); + } + } if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, + calendarInstanceForInterestRecalculation, recalculateFrom); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); @@ -4002,10 +4033,11 @@ public class Loan extends AbstractPersistable { * the transactions. for this need to save the reversed transactions * first and then new transactions. */ - // this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); + this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); updateLoanSummaryDerivedFields(); + this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values()); return changedTransactionDetail; } @@ -4013,7 +4045,7 @@ public class Loan extends AbstractPersistable { private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, - final CalendarInstance calendarInstanceForInterestRecalculation) { + final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) { if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return; } @@ -4056,8 +4088,10 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, - loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, retreiveListOfTransactionsPostDisbursement(), - loanRepaymentScheduleTransactionProcessor); + loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, + retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, + this.repaymentScheduleInstallments, recalculateFrom); + if (loanSchedule == null) { return; } updateLoanSchedule(loanSchedule); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); 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 b4873e74f..74e555b2f 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 @@ -104,11 +104,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen final List processInstallments = new ArrayList<>(1); processInstallments.add(chargePaidDetail.getInstallment()); Money processAmt = chargePaidDetail.getAmount(); - if(processAmt.isGreaterThan(unprocessed)){ + if (processAmt.isGreaterThan(unprocessed)) { processAmt = unprocessed; } - unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments, - transferCharges, processAmt, chargePaidDetail.isFeeCharge()); + unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments, transferCharges, processAmt, + chargePaidDetail.isFeeCharge()); if (!unprocessed.isGreaterThanZero()) { break; } @@ -437,19 +437,6 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen // empty implementation by default. } - @Override - public Map handleRecalculation(final LocalDate disbursementDate, - final List transactionsPostDisbursement, final MonetaryCurrency currency, - final List installments, LoanRepaymentScheduleInstallment currentinstallment, - final Map recalculationDates) { - - for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { - currentInstallment.resetDerivedComponents(); - currentInstallment.updateDerivedFields(currency, disbursementDate); - } - return handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, currentinstallment, recalculationDates); - } - @Override public Map handleRepaymentSchedule(final List transactionsPostDisbursement, final MonetaryCurrency currency, final List installments, @@ -458,7 +445,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { Money amountToProcess = null; - Money unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess); + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + newLoanTransaction.resetDerivedComponents(); + Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) && recalculationDates.get(loanTransaction.getTransactionDate()).isBefore(installment.getDueDate())) { Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency)); 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 587df39e6..df178c718 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 @@ -28,10 +28,8 @@ public interface LoanRepaymentScheduleTransactionProcessor { void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); - Map handleRecalculation(LocalDate disbursementDate, List transactionsPostDisbursement, - MonetaryCurrency currency, List installments, LoanRepaymentScheduleInstallment currentinstallment, Map recalculationDates); - Map handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, - List installments, LoanRepaymentScheduleInstallment currentInstallment, Map recalculationDates); + List installments, LoanRepaymentScheduleInstallment currentInstallment, + Map recalculationDates); } \ No newline at end of file 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 9eaba302f..d3c496e6c 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 @@ -212,8 +212,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } if (diffAmt != null && !diffAmt.isEmpty()) { - Money interestDueToLatePayment = totalCumulativeInterest.zero(); - Money interestReducedDueToEarlyPayment = totalCumulativeInterest.zero(); + BigDecimal interestDueToLatePayment = BigDecimal.ZERO; + BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; for (RecalculationDetail detail : diffAmt) { if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) && !detail.getStartDate().isAfter(scheduledDueDate)) { @@ -231,7 +231,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); } } - interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor( + interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(loanApplicationTerms.interestRateFor( this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), detail.getStartDate(), scheduledDueDate)); } else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) @@ -247,7 +247,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (toDate.isAfter(LocalDate.now())) { toDate = LocalDate.now(); } - interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor( + interestDueToLatePayment = interestDueToLatePayment.add(loanApplicationTerms.interestRateFor( this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), fromDate, toDate)); } } @@ -332,17 +332,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate fromDate = detail.getStartDate(); LocalDate toDate = detail.getToDate(); if (!toDate.isAfter(LocalDate.now())) { - Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, + 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()); - totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount()); - totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount()); - - final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate, - interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, - interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment); - periods.add(installment); - periodNumber++; + final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, + toDate, interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, + interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment); + periods.add(installment); + periodNumber++; + } } } } @@ -520,47 +522,95 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List previousSchedule, LocalDate recalculateFrom) { - int installmentNumber = 0; - final List recalculationDetails = new ArrayList<>(); - LoanScheduleModel loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, - holidays, workingDays); + LoanScheduleModel loanScheduleModel = null; + List installments = previousSchedule; + List removeInstallments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment installment : installments) { + if (!loanApplicationTerms.isPrincipalGraceApplicableForThisPeriod(installment.getInstallmentNumber()) + && installment.getPrincipal(loanApplicationTerms.getCurrency()).isZero()) { + removeInstallments.add(installment); + } + } + installments.removeAll(removeInstallments); + if (previousSchedule == null) { + loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, + workingDays); + installments = retrieveRepaymentSchedule(loanScheduleModel); + } + if (recalculateFrom == null) { + recalculateFrom = loanApplicationTerms.getExpectedDisbursementDate(); + } + while (!recalculateFrom.isAfter(LocalDate.now())) { + int installmentNumber = findLastProcessedInstallmentNumber(installments, recalculateFrom); + List processTransactions = processTransactions(transactions, recalculateFrom); + loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, + holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, + processTransactions, installmentNumber); - while (installmentNumber < loanScheduleModel.getPeriods().size() - 1) { - RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, installmentNumber, - loanScheduleModel, recalculationDetails); - installmentNumber = recalculatedSchedule.getInstallmentNumber(); - loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); + recalculateFrom = getNextRecalculateFromDate(transactions, recalculateFrom); } 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; + } + + private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency, + final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, + final List holidays, final WorkingDays workingDays, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List previousSchedule, LoanScheduleModel loanScheduleModel, + List installments, List processTransactions, final int skipRecalculation) { + int processInstallmentsFrom = 0; + Integer numberOfInstallments = previousSchedule.size() - 1; + final List recalculationDetails = new ArrayList<>(); + while (processInstallmentsFrom < numberOfInstallments) { + RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, + isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor, + processInstallmentsFrom, installments, recalculationDetails, skipRecalculation); + processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber(); + if (recalculatedSchedule.getLoanScheduleModel() != null) { + loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); + installments = retrieveRepaymentSchedule(loanScheduleModel); + numberOfInstallments = installments.size() - 1; + } + } + return loanScheduleModel; + } + private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final List transactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, - LoanScheduleModel model, final List recalculationDetails) { + final List installments, final List recalculationDetails, + final int skipRecalculation) { boolean processRecalculate = false; int processedInstallmentNumber = installmentNumber; - final List installments = new ArrayList<>(); + final List diffAmt = new ArrayList<>(); Money unpaidPricipal = loanApplicationTerms.getPrincipal(); - 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()); - installments.add(installment); - } - } + 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)); @@ -577,7 +627,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener isHolidayEnabled, holidays, workingDays); for (LoanTransaction loanTransaction : transactions) { LocalDate loantransactionDate = loanTransaction.getTransactionDate(); - if (!loantransactionDate.isAfter(transactionsDate) && !loanTransaction.isAccrual()) { + if (!loantransactionDate.isAfter(transactionsDate)) { transactionsForInstallment.add(loanTransaction); recalculationDates.put( loantransactionDate, @@ -585,9 +635,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener workingDays)); } } - Map earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRecalculation( - loanApplicationTerms.getExpectedDisbursementDate(), transactionsForInstallment, currency, processinstallmets, - installment, recalculationDates); + Map earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule( + transactionsForInstallment, currency, processinstallmets, installment, recalculationDates); for (Map.Entry entry : earlyPaymentMap.entrySet()) { LocalDate startDate = entry.getKey(); @@ -645,20 +694,37 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - if (!diffAmt.isEmpty()) { + if ((!diffAmt.isEmpty() && skipRecalculation <= installment.getInstallmentNumber()) + || installment.getInstallmentNumber() == installments.size() - 1) { processRecalculate = true; } break; } + LoanScheduleModel model = null; + recalculationDetails.addAll(diffAmt); if (processRecalculate) { - recalculationDetails.addAll(diffAmt); model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, recalculationDetails); } return new RecalculatedSchedule(model, processedInstallmentNumber); } + 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()); + installments.add(installment); + } + } + return installments; + } + private void applyRest(List loanTransactions, LocalDate from, LocalDate to, LoanRepaymentScheduleInstallment installment, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, @@ -668,7 +734,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Map recalculationDates = new HashMap<>(); for (LoanTransaction transaction : loanTransactions) { LocalDate loantransactionDate = transaction.getTransactionDate(); - if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to) && !transaction.isAccrual()) { + if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) { transactions.add(transaction); recalculationDates.put( loantransactionDate, @@ -714,10 +780,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount, + BigDecimal interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount, calculateInterestFrom, LocalDate.now()); prepaymentAmount = prepaymentAmount.plus(interest).plus(amount); return prepaymentAmount; } + 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 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; + } + } \ No newline at end of file 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 0844caa1f..96933f825 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 @@ -663,8 +663,8 @@ public final class LoanApplicationTerms { return periodicInterestRate; } - public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final Money outstandingBalance, - final LocalDate fromDate, final LocalDate toDate) { + public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, + final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); @@ -678,7 +678,7 @@ public final class LoanApplicationTerms { final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide( divisor, mc); BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc); - return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode()); + return outstandingBalance.getAmount().multiply(interestRate, mc); } private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { @@ -709,7 +709,7 @@ public final class LoanApplicationTerms { return totalNumberOfRepaymentPeriods - getPrincipalGrace(); } - private boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { + public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { return periodNumber > 0 && periodNumber <= getPrincipalGrace(); } @@ -981,7 +981,7 @@ public final class LoanApplicationTerms { if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { switch (this.repaymentPeriodFrequencyType) { case WEEKS: - int days = getPeriodsBetween(fromDate, toDate); + int days = Days.daysBetween(fromDate, toDate).getDays(); isSameAsRepaymentPeriod = (days % 7) == 0; break; case MONTHS: @@ -1016,9 +1016,14 @@ public final class LoanApplicationTerms { PeriodType periodType = PeriodType.yearMonthDay(); Period difference = new Period(fromDate, toDate, periodType); switch (this.repaymentPeriodFrequencyType) { - case WEEKS: + case DAYS: numberOfPeriods = difference.getDays(); break; + case WEEKS: + periodType = PeriodType.weeks(); + difference = new Period(fromDate, toDate, periodType); + numberOfPeriods = difference.getWeeks(); + break; case MONTHS: numberOfPeriods = difference.getMonths(); break; 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 1407b4f1e..9cecdbc24 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 @@ -9,6 +9,7 @@ import java.math.MathContext; import java.util.List; import java.util.Set; +import org.joda.time.LocalDate; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -27,7 +28,8 @@ public interface LoanScheduleGenerator { LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms, Set loanCharges, boolean isHolidayEnabled, List holidays, WorkingDays workingDays, List transactions, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + List previousSchedule, LocalDate recalculateFrom); Money fetchPrepaymentAmount(List installments, MonetaryCurrency currency, LoanApplicationTerms applicationTerms, MathContext mc); 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 24ad4deb5..26c56a823 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 @@ -2205,7 +2205,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); } - ChangedTransactionDetail changedTransactionDetail = loan.recalculateSchedule(loanScheduleFactory, applicationCurrency, + ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, existingTransactionIds, existingReversedTransactionIds);