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 0d8335e0c..a80fe968f 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 @@ -96,6 +96,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDa import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException; import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; @@ -364,6 +365,10 @@ public class Loan extends AbstractPersistable { @Column(name = "guarantee_amount_derived", scale = 6, precision = 19, nullable = true) private BigDecimal guaranteeAmountDerived; + @Temporal(TemporalType.DATE) + @Column(name = "interest_recalcualated_on") + private Date interestRecalculatedOn; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, @@ -496,8 +501,8 @@ public class Loan extends AbstractPersistable { private Set associateChargesWithThisLoan(final Set loanCharges) { for (final LoanCharge loanCharge : loanCharges) { loanCharge.update(this); - if(loanCharge.getTrancheDisbursementCharge() != null) { - addTrancheLoanCharge(loanCharge.getCharge()) ; + if (loanCharge.getTrancheDisbursementCharge() != null) { + addTrancheLoanCharge(loanCharge.getCharge()); } } return loanCharges; @@ -569,7 +574,7 @@ public class Loan extends AbstractPersistable { } this.charges.add(loanCharge); - + this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); // store Id's of existing loan transactions and existing reversed loan @@ -906,9 +911,12 @@ public class Loan extends AbstractPersistable { Money chargeComponent = amountWaived; if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { Money receivableCharge = Money.zero(getCurrency()); - if(loanInstallmentNumber !=null){ - receivableCharge = accruedCharge.minus(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid(getCurrency())); - }else{ receivableCharge = accruedCharge.minus(loanCharge.getAmountPaid(getCurrency()));} + if (loanInstallmentNumber != null) { + receivableCharge = accruedCharge.minus(loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getAmountPaid( + getCurrency())); + } else { + receivableCharge = accruedCharge.minus(loanCharge.getAmountPaid(getCurrency())); + } if (receivableCharge.isLessThanZero()) { receivableCharge = amountWaived.zero(); } @@ -924,10 +932,11 @@ public class Loan extends AbstractPersistable { feeChargesWaived = Money.zero(loanCurrency()); } - LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); + LocalDate transactionDate = getDisbursementDate(); if (loanCharge.isSpecifiedDueDate()) { transactionDate = loanCharge.getDueLocalDate(); } + scheduleGeneratorDTO.setRecalculateFrom(transactionDate); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1107,6 +1116,17 @@ public class Loan extends AbstractPersistable { } + public void updateLoanSchedule(final Collection installments, AppUser currentUser) { + this.repaymentScheduleInstallments.clear(); + for (final LoanRepaymentScheduleInstallment installment : installments) { + addRepaymentScheduleInstallment(installment); + } + updateLoanScheduleDependentDerivedFields(); + updateLoanSummaryDerivedFields(); + applyAccurals(currentUser); + + } + /** * method updates accrual derived fields on installments and reverse the * unprocessed transactions @@ -1197,8 +1217,8 @@ public class Loan extends AbstractPersistable { updateLoanOutstandingBalaces(); } } - - public void updateLoanSummarAndStatus(){ + + public void updateLoanSummarAndStatus() { updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine); } @@ -1485,9 +1505,9 @@ public class Loan extends AbstractPersistable { } public boolean isMultiDisburmentLoan() { - return this.loanProduct.isMultiDisburseLoan() ; + return this.loanProduct.isMultiDisburseLoan(); } - + /** * Update interest recalculation settings if product configuration changes */ @@ -1660,12 +1680,13 @@ public class Loan extends AbstractPersistable { List loanChargeIds = fetchLoanChargeIds(); int chargeIdLength = loanChargeIds.size(); String chargeIds = null; - //From modify application page, if user removes all charges, we should get empty array. - //So we need to remove all charges applied for this loan + // From modify application page, if user removes all charges, we should + // get empty array. + // So we need to remove all charges applied for this loan boolean removeAllChages = false; if (jsonCommand.parameterExists(LoanApiConstants.chargesParameterName)) { JsonArray chargesArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.chargesParameterName); - if (chargesArray.size() == 0) removeAllChages = true; + if (chargesArray.size() == 0) removeAllChages = true; } if (jsonCommand.parameterExists(LoanApiConstants.disbursementDataParameterName)) { @@ -1754,13 +1775,13 @@ public class Loan extends AbstractPersistable { this.disbursementDetails.add(disbursementDetails); for (LoanTrancheCharge trancheCharge : trancheCharges) { Charge chargeDefinition = trancheCharge.getCharge(); - final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null, - new LocalDate(expectedDisbursementDate), null, null); - loanCharge.update(this); - LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, - disbursementDetails); - loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); - addLoanCharge(loanCharge); + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null, new LocalDate( + expectedDisbursementDate), null, null); + loanCharge.update(this); + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, + disbursementDetails); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + addLoanCharge(loanCharge); } actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal); actualChanges.put("recalculateLoanSchedule", true); @@ -4857,9 +4878,10 @@ public class Loan extends AbstractPersistable { public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { LocalDate lastTransactionDate = getLastUserTransactionDate(); - final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO); + final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO); if (loanSchedule == null) { return; } - updateLoanSchedule(loanSchedule, currentUser); + updateLoanSchedule(loanSchedule.getInstallments(), currentUser); + this.interestRecalculatedOn = DateUtils.getDateOfTenant(); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); Set charges = this.charges(); for (LoanCharge loanCharge : charges) { @@ -4896,7 +4918,7 @@ public class Loan extends AbstractPersistable { } } - private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) { + private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) { if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; } final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); @@ -4912,10 +4934,9 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), - generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), - loanRepaymentScheduleTransactionProcessor); - return loanSchedule; + return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(), + retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, + repaymentScheduleInstallments, generatorDTO.getRecalculateFrom()); } public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency, @@ -5017,8 +5038,8 @@ public class Loan extends AbstractPersistable { getApprovedPrincipal()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), onDate, - loanApplicationTerms, mc, charges(), holidayDetailDTO, retreiveListOfTransactionsPostDisbursementExcludeAccruals(), + installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(), + holidayDetailDTO, retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor); } else { installment = this.getTotalOutstandingOnLoan(); @@ -5053,6 +5074,16 @@ public class Loan extends AbstractPersistable { return accruedTill; } + public LocalDate fetchInterestRecalculateFromDate() { + LocalDate interestRecalculatedOn = null; + if (this.interestRecalculatedOn != null) { + interestRecalculatedOn = getDisbursementDate(); + } else { + interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn); + } + return interestRecalculatedOn; + } + private void updateLoanOutstandingBalaces() { Money outstanding = Money.zero(getCurrency()); List loanTransactions = retreiveListOfTransactionsExcludeAccruals(); @@ -5473,8 +5504,8 @@ public class Loan extends AbstractPersistable { } public void addTrancheLoanCharge(Charge charge) { - if(!trancheCharges.contains(charge)) { - trancheCharges.add(new LoanTrancheCharge(charge, this)); + if (!trancheCharges.contains(charge)) { + trancheCharges.add(new LoanTrancheCharge(charge, this)); } } 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 a0df9b615..66d9b460d 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 @@ -186,6 +186,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = transactionDate; } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 0aeb7e2c3..eed2bab64 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -319,6 +319,7 @@ public final class LoanTransaction extends AbstractPersistable { public void reverse() { this.reversed = true; + this.loanTransactionToRepaymentScheduleMappings.clear(); } public void resetDerivedComponents() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java new file mode 100644 index 000000000..db950e2dd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleDTO.java @@ -0,0 +1,38 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanaccount.loanschedule.data; + +import java.util.List; + +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; + +/** + * Transfer object to return the schedule after generation of schedule + */ +public class LoanScheduleDTO { + + private final List installments; + private final LoanScheduleModel loanScheduleModel; + + private LoanScheduleDTO(final List installments, final LoanScheduleModel loanScheduleModel) { + this.installments = installments; + this.loanScheduleModel = loanScheduleModel; + } + + public static LoanScheduleDTO from(final List installments, final LoanScheduleModel loanScheduleModel){ + return new LoanScheduleDTO(installments, loanScheduleModel); + } + + public List getInstallments() { + return this.installments; + } + + public LoanScheduleModel getLoanScheduleModel() { + return this.loanScheduleModel; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java new file mode 100644 index 000000000..7565d6656 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java @@ -0,0 +1,203 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanaccount.loanschedule.data; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail; + +public class LoanScheduleRecalculationDTO { + + private final int periodNumber; + private final int instalmentNumber; + private final LocalDate periodStartDate; + private final LocalDate actualRepaymentDate; + private final Money totalCumulativePrincipal; + private final Money totalCumulativeInterest; + private final Money totalFeeChargesCharged; + private final Money totalPenaltyChargesCharged; + private final Money totalRepaymentExpected; + private final Money reducePrincipal; + private final Map principalPortionMap; + private final Map latePaymentMap; + private final TreeMap compoundingMap; + private final Map disburseDetailMap; + private final Money outstandingBalance; + private final Money outstandingBalanceAsPerRest; + private final List installments; + private final Collection recalculationDetails; + private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor; + private final LocalDate scheduleTillDate; + private final boolean partialUpdate; + + private LoanScheduleRecalculationDTO(final int periodNumber, final int instalmentNumber, LocalDate periodStartDate, + final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, + final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, + final Money reducePrincipal, final Map principalPortionMap, final Map latePaymentMap, + final TreeMap compoundingMap, final Map disburseDetailMap, final Money outstandingBalance, + final Money outstandingBalanceAsPerRest, final List installments, + final Collection recalculationDetails, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, + final boolean partialUpdate) { + this.periodNumber = periodNumber; + this.instalmentNumber = instalmentNumber; + this.periodStartDate = periodStartDate; + this.actualRepaymentDate = actualRepaymentDate; + this.totalCumulativePrincipal = totalCumulativePrincipal; + this.totalCumulativeInterest = totalCumulativeInterest; + this.totalFeeChargesCharged = totalFeeChargesCharged; + this.totalPenaltyChargesCharged = totalPenaltyChargesCharged; + this.totalRepaymentExpected = totalRepaymentExpected; + this.reducePrincipal = reducePrincipal; + this.principalPortionMap = principalPortionMap; + this.latePaymentMap = latePaymentMap; + this.compoundingMap = compoundingMap; + this.disburseDetailMap = disburseDetailMap; + this.outstandingBalance = outstandingBalance; + this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; + this.installments = installments; + this.recalculationDetails = recalculationDetails; + this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor; + this.scheduleTillDate = scheduleTillDate; + this.partialUpdate = partialUpdate; + } + + public static LoanScheduleRecalculationDTO createLoanScheduleDTOForPartialUpdate(final int periodNumber, final int instalmentNumber, + LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, + final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, + final Money totalRepaymentExpected, final Money reducePrincipal, final Map principalPortionMap, + final Map latePaymentMap, final TreeMap compoundingMap, + final Map disburseDetailMap, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, + final List installments, final Collection recalculationDetails, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { + final boolean partialUpdate = true; + return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, + totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, + totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, + outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); + } + + public static LoanScheduleRecalculationDTO createLoanScheduleDTOForCompleteUpdate( + final Collection recalculationDetails, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { + final int periodNumber = 1; + final int instalmentNumber = 1; + final LocalDate periodStartDate = null; + final LocalDate actualRepaymentDate = null; + final Money totalCumulativePrincipal = null; + final Money totalCumulativeInterest = null; + final Money totalFeeChargesCharged = null; + final Money totalPenaltyChargesCharged = null; + final Money totalRepaymentExpected = null; + final Money reducePrincipal = null; + final Map principalPortionMap = null; + final Map latePaymentMap = null; + final TreeMap compoundingMap = null; + final Map disburseDetailMap = null; + final Money outstandingBalance = null; + final Money outstandingBalanceAsPerRest = null; + final List installments = null; + final boolean partialUpdate = false; + return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, + totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, + totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, + outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); + } + + public int getPeriodNumber() { + return this.periodNumber; + } + + public int getInstalmentNumber() { + return this.instalmentNumber; + } + + public LocalDate getActualRepaymentDate() { + return this.actualRepaymentDate; + } + + public Money getTotalCumulativePrincipal() { + return this.totalCumulativePrincipal; + } + + public Money getTotalCumulativeInterest() { + return this.totalCumulativeInterest; + } + + public Money getTotalFeeChargesCharged() { + return this.totalFeeChargesCharged; + } + + public Money getTotalPenaltyChargesCharged() { + return this.totalPenaltyChargesCharged; + } + + public Money getTotalRepaymentExpected() { + return this.totalRepaymentExpected; + } + + public Money getReducePrincipal() { + return this.reducePrincipal; + } + + public Map getPrincipalPortionMap() { + return this.principalPortionMap; + } + + public Map getLatePaymentMap() { + return this.latePaymentMap; + } + + public TreeMap getCompoundingMap() { + return this.compoundingMap; + } + + public Map getDisburseDetailMap() { + return this.disburseDetailMap; + } + + public Money getOutstandingBalance() { + return this.outstandingBalance; + } + + public Money getOutstandingBalanceAsPerRest() { + return this.outstandingBalanceAsPerRest; + } + + public List getInstallments() { + return this.installments; + } + + public Collection getRecalculationDetails() { + return this.recalculationDetails; + } + + public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() { + return this.loanRepaymentScheduleTransactionProcessor; + } + + public LocalDate getScheduleTillDate() { + return this.scheduleTillDate; + } + + public boolean isPartialUpdate() { + return this.partialUpdate; + } + + public LocalDate getPeriodStartDate() { + return this.periodStartDate; + } + +} 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 52a224a1f..5e0babbc8 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 @@ -7,7 +7,15 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.math.MathContext; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -22,8 +30,14 @@ import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; -import org.mifosplatform.portfolio.loanaccount.domain.*; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +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.data.LoanScheduleDTO; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleRecalculationDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; @@ -42,16 +56,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { - final Collection diffAmt = null; - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; - final LocalDate scheduleTillDate = null; - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, loanRepaymentScheduleTransactionProcessor, - scheduleTillDate); + final LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = null; + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleRecalculationDTO); } private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final Collection transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, + final LoanScheduleRecalculationDTO loanScheduleRecalculationDTO) { final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // generate list of proposed schedule due dates @@ -63,16 +74,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // setup variables for tracking important facts required for loan // schedule generation. - Money principalToBeScheduled; - if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { - principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); - } else { - principalToBeScheduled = loanApplicationTerms.getPrincipal(); - } + Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms); final MonetaryCurrency currency = principalToBeScheduled.getCurrency(); final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); + final LocalDate scheduleTillDate = loanScheduleRecalculationDTO == null ? null : loanScheduleRecalculationDTO.getScheduleTillDate(); + final Collection transactions = loanScheduleRecalculationDTO == null ? null : loanScheduleRecalculationDTO + .getRecalculationDetails(); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleRecalculationDTO == null ? null + : loanScheduleRecalculationDTO.getLoanRepaymentScheduleTransactionProcessor(); + // variables for cumulative totals int loanTermInDays = Integer.valueOf(0); final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; @@ -107,10 +119,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // Actual period Number plus interest only repayments int instalmentNumber = 1; + // actual outstanding balance for interest calculation Money outstandingBalance = principalToBeScheduled; // Set fixed EMI Amount - updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO); + if (loanApplicationTerms.getFixedEmiAmount() == null) { + updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO); + } // disbursement map for tranche details(will added to outstanding // balance as per the start date) @@ -165,6 +180,34 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue()); extendTermForDailyRepayments = true; } + + // this block is to start the schedule generation from specified date + if (loanScheduleRecalculationDTO != null && loanScheduleRecalculationDTO.isPartialUpdate()) { + periodNumber = loanScheduleRecalculationDTO.getPeriodNumber(); + instalmentNumber = loanScheduleRecalculationDTO.getInstalmentNumber(); + periodStartDate = loanScheduleRecalculationDTO.getPeriodStartDate(); + periodStartDateApplicableForInterest = periodStartDate; + actualRepaymentDate = loanScheduleRecalculationDTO.getActualRepaymentDate(); + totalCumulativePrincipal = loanScheduleRecalculationDTO.getTotalCumulativePrincipal(); + totalCumulativeInterest = loanScheduleRecalculationDTO.getTotalCumulativeInterest(); + totalFeeChargesCharged = loanScheduleRecalculationDTO.getTotalFeeChargesCharged().getAmount(); + totalPenaltyChargesCharged = loanScheduleRecalculationDTO.getTotalPenaltyChargesCharged().getAmount(); + totalRepaymentExpected = loanScheduleRecalculationDTO.getTotalRepaymentExpected().getAmount(); + reducePrincipal = loanScheduleRecalculationDTO.getReducePrincipal(); + principalPortionMap.clear(); + principalPortionMap.putAll(loanScheduleRecalculationDTO.getPrincipalPortionMap()); + latePaymentMap.clear(); + latePaymentMap.putAll(loanScheduleRecalculationDTO.getLatePaymentMap()); + compoundingMap.clear(); + compoundingMap.putAll(loanScheduleRecalculationDTO.getCompoundingMap()); + disburseDetailMap.clear(); + disburseDetailMap.putAll(loanScheduleRecalculationDTO.getDisburseDetailMap()); + outstandingBalance = loanScheduleRecalculationDTO.getOutstandingBalance(); + outstandingBalanceAsPerRest = loanScheduleRecalculationDTO.getOutstandingBalanceAsPerRest(); + installments.clear(); + installments.addAll(loanScheduleRecalculationDTO.getInstallments()); + } + while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, @@ -451,9 +494,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - LocalDate tillDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, - holidayDetailDTO); - if (arrears.isGreaterThanZero() && applicableDate.isBefore(tillDate)) { + if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) { updateLatePaidAmountsToPrincipalMap(principalPortionMap, detail.getTransaction(), latePaymentMap, compoundingMap, loanApplicationTerms, currency, holidayDetailDTO, lastRestDate); compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap)); @@ -519,8 +560,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (earlyPaidAmount.isGreaterThanZero()) { reducePrincipal = reducePrincipal.plus(earlyPaidAmount); BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); - reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, totalCumulativePrincipal, - periodNumber + 1, mc, holidayDetailDTO); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, + totalCumulativePrincipal.plus(principalForThisPeriod).plus(earlyPaidAmount), periodNumber + 1, mc, holidayDetailDTO); if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment() && fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) { isEmiAmountChanged = true; @@ -740,6 +781,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); } + /** + * this method calculates the principal amount for generating the repayment + * schedule. + */ + private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) { + Money principalToBeScheduled; + if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { + principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); + } else { + principalToBeScheduled = loanApplicationTerms.getPrincipal(); + } + return principalToBeScheduled; + } + private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, LocalDate actualRepaymentDate, int periodNumber, Money outstandingBalance, final HolidayDetailDTO holidayDetailDTO) { boolean isAmountChanged = false; @@ -943,14 +998,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener case REDUCE_EMI_AMOUNT: // in this case emi amount will be reduced but number of // installments won't change + Money principal = getPrincipalToBeScheduled(loanApplicationTerms); if (loanApplicationTerms.getActualFixedEmiAmount() == null) { loanApplicationTerms.setFixedEmiAmount(null); updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), - periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal), holidayDetailDTO); + periodNumber, principal.minus(totalCumulativePrincipal), holidayDetailDTO); } if (loanApplicationTerms.getAmortizationMethod().isEqualPrincipal()) { - loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, - loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal)); + loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, principal.minus(totalCumulativePrincipal)); } reducePrincipal = reducePrincipal.zero(); break; @@ -1743,23 +1798,353 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } /** - * Method calls schedule regeneration by passing transactions one after - * another(this is done mainly to handle the scenario where interest or fee - * of over due installment should be collected before collecting principal ) + * Method preprocess the installments and transactions and sets the required + * fields to generate the schedule */ @Override - public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List repaymentScheduleInstallments, final LocalDate rescheduleFrom) { + // Loan transactions to process and find the variation on payments Collection recalculationDetails = new ArrayList<>(); for (LoanTransaction loanTransaction : transactions) { recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction .copyTransactionProperties(loanTransaction))); } + // Fixed schedule End Date for generating schedule final LocalDate scheduleTillDate = null; - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, - loanRepaymentScheduleTransactionProcessor, scheduleTillDate); + LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = null; + Collection periods = new ArrayList<>(); + final List retainedInstallments = new ArrayList<>(); + + // this block is to retain the schedule installments prior to the + // provided date and creates late and early payment details for further + // calculations + if (rescheduleFrom != null) { + Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms); + // actual outstanding balance for interest calculation + Money outstandingBalance = principalToBeScheduled; + // total outstanding balance as per rest for interest calculation. + Money outstandingBalanceAsPerRest = outstandingBalance; + + // this is required to update total fee amounts in the + // LoanScheduleModel + final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); + periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.getNumberOfRepayments(), loanApplicationTerms, + chargesDueAtTimeOfDisbursement); + final List newRepaymentScheduleInstallments = new ArrayList<>(); + MonetaryCurrency currency = outstandingBalance.getCurrency(); + final Map disburseDetailMap = new HashMap<>(); + if (loanApplicationTerms.isMultiDisburseLoan()) { + // fetches the first tranche amount and also updates other + // tranche + // details to map + BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), + periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true); + outstandingBalance = outstandingBalance.zero().plus(disburseAmt); + outstandingBalanceAsPerRest = outstandingBalance; + } + + // early payments will be added here and as per the selected + // strategy + // action will be performed on this value + Money reducePrincipal = outstandingBalanceAsPerRest.zero(); + + // 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(principal) 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<>(); + + // compounding(interest/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 TreeMap compoundingMap = new TreeMap<>(); + LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + LocalDate lastRestDate = currentDate; + if (loanApplicationTerms.getRestCalendarInstance() != null) { + lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); + } + LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); + boolean isFirstRepayment = true; + + // cumulative fields + Money totalCumulativePrincipal = principalToBeScheduled.zero(); + Money totalCumulativeInterest = principalToBeScheduled.zero(); + Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement); + Money totalPenaltyChargesCharged = principalToBeScheduled.zero(); + Money totalRepaymentExpected = principalToBeScheduled.zero(); + + // Actual period Number as per the schedule + int periodNumber = 1; + // Actual period Number plus interest only repayments + int instalmentNumber = 1; + LocalDate lastInstallmentDate = actualRepaymentDate; + LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate(); + // Set fixed Amortization Amounts(either EMI or Principal ) + updateAmortization(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO); + + // Block process the installment and creates the period if it falls + // before reschedule from date + // This will create the recalculation details by applying the + // transactions + for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { + for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { + if (disburseDetail.getKey().isAfter(installment.getFromDate()) + && !disburseDetail.getKey().isAfter(installment.getDueDate())) { + // 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()); + } + } + + // this will generate the next schedule due date and allows to + // process the installment only if recalculate from date is + // greater than due date + if (installment.getDueDate().isAfter(lastInstallmentDate)) { + LocalDate previousRepaymentDate = actualRepaymentDate; + actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, + isFirstRepayment, holidayDetailDTO); + isFirstRepayment = false; + lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, + holidayDetailDTO); + if (!lastInstallmentDate.isBefore(rescheduleFrom)) { + actualRepaymentDate = previousRepaymentDate; + break; + } + periodNumber++; + } + + // calculation of basic fields to start the schedule generation + // from the middle + periodStartDate = installment.getDueDate(); + installment.resetDerivedComponents(); + newRepaymentScheduleInstallments.add(installment); + outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); + final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); + periods.add(loanScheduleModelPeriod); + totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); + totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); + totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); + totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency)); + instalmentNumber++; + + // populates the collection with transactions till the due date + // of + // the period for interest recalculation enabled loans + Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, + installment.getDueDate(), recalculationDetails); + + // calculates the expected principal value for this repayment + // schedule + Money principalPortionCalculated = principalToBeScheduled.zero(); + if (!installment.isRecalculatedInterestComponent()) { + principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency), + loanApplicationTerms); + } + + // expected principal considering the previously paid excess + // amount + Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); + if (actualPrincipalPortion.isLessThanZero()) { + actualPrincipalPortion = principalPortionCalculated.zero(); + } + + Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, + installment, applicableTransactions, actualPrincipalPortion); + + // this block is to adjust the period number based on the actual + // schedule due date and installment due date + // recalculatedInterestComponent installment shouldn't be + // considered while calculating fixed EMI amounts + int period = periodNumber; + if (!lastInstallmentDate.isEqual(installment.getDueDate())) { + period--; + } + reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, + loanApplicationTerms, totalCumulativePrincipal, period, mc, holidayDetailDTO); + // Updates principal paid map with efective date for reducing + // the amount from outstanding balance(interest calculation) + LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + // updates map with the installment principal amount excluding + // unprocessed amount since this amount is already accounted. + updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); + // update outstanding balance for interest calculation + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), + outstandingBalanceAsPerRest, false); + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), + outstandingBalanceAsPerRest, true); + + } + totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest).plus(totalFeeChargesCharged) + .plus(totalPenaltyChargesCharged); + + // updates the map with over due amounts + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, + newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap); + + // for partial schedule generation + if (!newRepaymentScheduleInstallments.isEmpty()) { + loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO + .createLoanScheduleDTOForPartialUpdate(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, + totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, + totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, + disburseDetailMap, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, + recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate); + retainedInstallments.addAll(newRepaymentScheduleInstallments); + } + + } + // for complete schedule generation + if (loanScheduleRecalculationDTO == null) { + loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForCompleteUpdate(recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate); + } + + LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + loanScheduleRecalculationDTO); + for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { + if (loanScheduleModelPeriod.isRepaymentPeriod()) { + // adding newly created repayment periods to installments + addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod); + } + } + periods.addAll(loanScheduleModel.getPeriods()); + LoanScheduleModel loanScheduleModelwithPeriodChanges = LoanScheduleModel.withLoanScheduleModelPeriods(periods, loanScheduleModel); + return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); + + } + + /** + * Method identifies the early paid amounts for a installment and update the + * principal map for further calculations + */ + private Money updateEarlyPaidAmountsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List newRepaymentScheduleInstallments, MonetaryCurrency currency, + final Map principalPortionMap, LoanRepaymentScheduleInstallment installment, + Collection applicableTransactions, Money actualPrincipalPortion) { + Money unprocessed = Money.zero(currency); + for (RecalculationDetail detail : applicableTransactions) { + if (!detail.isProcessed()) { + Money principalProcessed = installment.getPrincipalCompleted(currency); + 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 + loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, + newRepaymentScheduleInstallments); + + // Identifies totalEarlyPayment and early paid amount with this + // transaction + Money principalPaidWithTransaction = installment.getPrincipalCompleted(currency).minus(principalProcessed); + Money totalEarlyPayment = installment.getPrincipalCompleted(currency).minus(actualPrincipalPortion); + + if (totalEarlyPayment.isGreaterThanZero()) { + unprocessed = principalPaidWithTransaction; + // will execute this block if partial amount paid as + // early + if (principalPaidWithTransaction.isGreaterThan(totalEarlyPayment)) { + unprocessed = totalEarlyPayment; + } + } + // updates principal portion map with the early + // payment amounts and applicable date as per rest + LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + updateMapWithAmount(principalPortionMap, unprocessed, applicableDate); + + } + } + return unprocessed; + } + + private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, LocalDate actualRepaymentDate, + int periodNumber, Money outstandingBalance, final HolidayDetailDTO holidayDetailDTO) { + if (loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { + updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO); + } else { + loanApplicationTerms.updateFixedPrincipalAmount(mc, periodNumber, outstandingBalance); + } + } + + /** + * Method identifies early paid amount and applies the early payment + * strategy + */ + private Money fetchEarlyPaidAmount(final Money principalPortion, final Money principalPortionCalculated, final Money reducePrincipal, + final LoanApplicationTerms applicationTerms, final Money totalCumulativePrincipal, int periodNumber, final MathContext mc, + final HolidayDetailDTO holidayDetailDTO) { + Money existingEarlyPayment = reducePrincipal.minus(principalPortionCalculated); + Money earlyPaidAmount = principalPortion.plus(existingEarlyPayment); + if (existingEarlyPayment.isLessThanZero()) { + existingEarlyPayment = existingEarlyPayment.zero(); + } + boolean isEarlyPaid = earlyPaidAmount.isGreaterThan(existingEarlyPayment); + + if (earlyPaidAmount.isLessThanZero()) { + earlyPaidAmount = earlyPaidAmount.zero(); + } + + if (isEarlyPaid) { + switch (applicationTerms.getRescheduleStrategyMethod()) { + case REDUCE_EMI_AMOUNT: + // in this case emi amount will be reduced but number of + // installments won't change + Money principal = getPrincipalToBeScheduled(applicationTerms); + if (applicationTerms.getActualFixedEmiAmount() == null) { + applicationTerms.setFixedEmiAmount(null); + updateFixedInstallmentAmount(mc, applicationTerms, applicationTerms.getExpectedDisbursementDate(), periodNumber, + principal.minus(totalCumulativePrincipal), holidayDetailDTO); + } + if (applicationTerms.getAmortizationMethod().isEqualPrincipal()) { + applicationTerms.updateFixedPrincipalAmount(mc, periodNumber, principal.minus(totalCumulativePrincipal)); + } + earlyPaidAmount = earlyPaidAmount.zero(); + break; + case REDUCE_NUMBER_OF_INSTALLMENTS: + // number of installments will reduce but emi amount won't + // get effected + earlyPaidAmount = earlyPaidAmount.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 earlyPaidAmount; + } + + private Money calculateExpectedPrincipalPortion(final Money interestPortion, final LoanApplicationTerms applicationTerms) { + Money principalPortionCalculated = interestPortion.zero(); + if (applicationTerms.getAmortizationMethod().isEqualInstallment()) { + principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedEmiAmount()).minus(interestPortion); + } else { + principalPortionCalculated = principalPortionCalculated.plus(applicationTerms.getFixedPrincipalAmount()); + } + return principalPortionCalculated; } private List fetchInstallmentsFromScheduleModel(final LoanScheduleModel loanScheduleModel) { @@ -1770,16 +2155,29 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return installments; } - private void addLoanRepaymentScheduleInstallment(final List installments, + private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List installments, final LoanScheduleModelPeriod scheduledLoanInstallment) { + LoanRepaymentScheduleInstallment installment = null; 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()); + installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(), + scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), + scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), + scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), + scheduledLoanInstallment.isRecalculatedInterestComponent()); installments.add(installment); } + return installment; + } + + private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment, + final Money outstandingPrincipal) { + final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); + LoanScheduleModelPeriod scheduledLoanInstallment = LoanScheduleModelRepaymentPeriod + .repayment(installment.getInstallmentNumber(), installment.getFromDate(), installment.getDueDate(), + installment.getPrincipal(currency), outstandingPrincipal, installment.getInterestCharged(currency), + installment.getFeeChargesCharged(currency), installment.getPenaltyChargesCharged(currency), + installment.getDue(currency), installment.isRecalculatedInterestComponent()); + return scheduledLoanInstallment; } private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, @@ -1815,9 +2213,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * Method returns the amount payable to close the loan account as of today. */ @Override - public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, - MonetaryCurrency currency, final LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, - Set charges, final HolidayDetailDTO holidayDetailDTO, final List loanTransactions, + public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate, + final LoanApplicationTerms loanApplicationTerms, final MathContext mc, final Set charges, + final HolidayDetailDTO holidayDetailDTO, final List loanTransactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { LocalDate calculateTill = onDate; @@ -1829,9 +2227,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction .copyTransactionProperties(loanTransaction))); } - LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO, recalculationDetails, - loanRepaymentScheduleTransactionProcessor, calculateTill); - installments = fetchInstallmentsFromScheduleModel(loanScheduleModel); + LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForCompleteUpdate( + recalculationDetails, loanRepaymentScheduleTransactionProcessor, calculateTill); + LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO, loanScheduleRecalculationDTO); + List installments = fetchInstallmentsFromScheduleModel(loanScheduleModel); loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, currency, installments, charges); Money feeCharges = Money.zero(currency); 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 7723a6036..21936b23e 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 @@ -1270,4 +1270,9 @@ public final class LoanApplicationTerms { return loanCalendar; } + + public BigDecimal getFixedPrincipalAmount() { + return this.fixedPrincipalAmount; + } + } \ 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 7ebcbb0b5..7db2db481 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 @@ -19,6 +19,7 @@ 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.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; +import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; @@ -27,14 +28,13 @@ public interface LoanScheduleGenerator { LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, final HolidayDetailDTO holidayDetailDTO); - LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, + LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, final HolidayDetailDTO holidayDetailDTO, List transactions, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, List repaymentScheduleInstallments, LocalDate rescheduleFrom); - LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, - MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, - Set charges, HolidayDetailDTO holidayDetailDTO, List loanTransactions, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); + LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, + LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Set charges, + HolidayDetailDTO holidayDetailDTO, List loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index 7f533c9c4..235cc4292 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -51,6 +51,15 @@ public final class LoanScheduleModel { loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); } + public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection periods, + final LoanScheduleModel loanScheduleModel) { + + return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, + loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid, + loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, + loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); + } + private LoanScheduleModel(final Collection periods, final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, @@ -95,7 +104,6 @@ public final class LoanScheduleModel { return this.periods; } - public BigDecimal getTotalPenaltyChargesCharged() { return this.totalPenaltyChargesCharged; } 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 5834932c0..fea999b02 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 @@ -231,8 +231,8 @@ public class LoanScheduleAssembler { final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element); final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); - final Integer graceOnArrearsAgeing = this.fromApiJsonHelper - .extractIntegerWithLocaleNamed(LoanProductConstants.graceOnArrearsAgeingParameterName, element); + final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( + LoanProductConstants.graceOnArrearsAgeingParameterName, element); // other final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); @@ -240,14 +240,14 @@ public class LoanScheduleAssembler { final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element); - final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper - .extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element); + final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + LoanApiConstants.maxOutstandingBalanceParameterName, element); final List disbursementDatas = fetchDisbursementData(element.getAsJsonObject()); final List loanVariationTermsData = new ArrayList<>(); - LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), - expectedDisbursementDate, emiAmount); + LoanTermVariationsData data = new LoanTermVariationsData(null, + LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount); loanVariationTermsData.add(data); /** @@ -267,8 +267,8 @@ public class LoanScheduleAssembler { .getProductInterestRecalculationDetails(); recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType(); if (!recalculationFrequencyType.isSameAsRepayment()) { - LocalDate calendarStartDate = this.fromApiJsonHelper - .extractLocalDateNamed(LoanProductConstants.recalculationRestFrequencyDateParamName, element); + LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationRestFrequencyDateParamName, element); if (calendarStartDate == null) { calendarStartDate = expectedDisbursementDate; } @@ -280,8 +280,8 @@ public class LoanScheduleAssembler { if (compoundingMethod.isCompoundingEnabled()) { compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType(); if (!compoundingFrequencyType.isSameAsRepayment()) { - LocalDate calendarStartDate = this.fromApiJsonHelper - .extractLocalDateNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); + LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); if (calendarStartDate == null) { calendarStartDate = expectedDisbursementDate; } @@ -346,12 +346,12 @@ public class LoanScheduleAssembler { BigDecimal principal = null; if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) { - expectedDisbursementDate = this.fromApiJsonHelper - .extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale); + expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale); } if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName) - && jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() && StringUtils - .isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { + && jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() + && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); } disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null)); @@ -363,8 +363,9 @@ public class LoanScheduleAssembler { } private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) { - if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), - calendar.getStartDateLocalDate(), repaymentsStartingFromDate)) { + if (repaymentsStartingFromDate != null + && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), + repaymentsStartingFromDate)) { final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date"; throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage, repaymentsStartingFromDate); @@ -439,9 +440,10 @@ public class LoanScheduleAssembler { } public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, final JsonElement element, Set disbursementDetails) { + final List holidays, final WorkingDays workingDays, final JsonElement element, + Set disbursementDetails) { - final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails); + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); @@ -455,37 +457,38 @@ public class LoanScheduleAssembler { public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, List transactions, final Set loanCharges, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List repaymentScheduleInstallments, final LocalDate rescheduleFrom) { final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); 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 List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms + .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, - loanRepaymentScheduleTransactionProcessor); + loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel(); } - public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, - MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Set loanCharges, - final Long officeId, List loanTransactions, + public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, + LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final Long officeId, + List loanTransactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); 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 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, mc, loanCharges, - holidayDetailDTO, loanTransactions, loanRepaymentScheduleTransactionProcessor); + return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO, + loanTransactions, loanRepaymentScheduleTransactionProcessor); } @@ -522,7 +525,7 @@ public class LoanScheduleAssembler { refernceDateForCalculatingFirstRepaymentDate, repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar); - } /*** Individual or group account, or JLG not linked to a meeting ***/ + }/*** Individual or group account, or JLG not linked to a meeting ***/ else { LocalDate dateBasedOnRepaymentFrequency; // Derive the first repayment date as greater date among @@ -535,12 +538,12 @@ public class LoanScheduleAssembler { dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusWeeks(repaymentEvery); } else if (repaymentPeriodFrequencyType.isMonthly()) { dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusMonths(repaymentEvery); - } /** yearly loan **/ + }/** yearly loan **/ else { dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusYears(repaymentEvery); } - derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) - ? dateBasedOnRepaymentFrequency : dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment; + derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? dateBasedOnRepaymentFrequency + : dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment; } return derivedFirstRepayment; @@ -554,17 +557,16 @@ public class LoanScheduleAssembler { final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar, refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency); final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment); - return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment - : deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, - repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar); + return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForJLGLoans( + repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType, + minimumDaysBetweenDisbursalAndFirstRepayment, calendar); } private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate, final Integer minimumDaysBetweenDisbursalAndFirstRepayment) { final LocalDate minimumFirstRepaymentDate = disbursalDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment); - if (firstRepaymentDate - .isBefore(minimumFirstRepaymentDate)) { throw new MinDaysBetweenDisbursalAndFirstRepaymentViolationException(disbursalDate, - firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment); } + if (firstRepaymentDate.isBefore(minimumFirstRepaymentDate)) { throw new MinDaysBetweenDisbursalAndFirstRepaymentViolationException( + disbursalDate, firstRepaymentDate, minimumDaysBetweenDisbursalAndFirstRepayment); } } } \ No newline at end of file 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 e7017cbfe..5ab734c27 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 @@ -167,8 +167,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance); LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( - loan.fetchRepaymentScheduleInstallments(), currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), - loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor); + currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), + loanRepaymentScheduleTransactionProcessor); Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); Money interestDue = Money.zero(currency); @@ -189,7 +189,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), - modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor); + modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.fetchRepaymentScheduleInstallments(), + loan.fetchInterestRecalculateFromDate()); 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/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index ec1a22f7f..dd43a3c90 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 @@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.HolidayRepositoryWrapper; @@ -463,6 +464,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = DateUtils.getLocalDateOfTenant(); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index b080d7ad7..6f1ad6a95 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -1071,9 +1071,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa for (LoanRepaymentScheduleInstallment installment : installments) { if (installment.getId() == null) { this.repaymentScheduleInstallmentRepository.save(installment); - } else { - break; - } + } } this.loanRepository.saveAndFlush(loan); } catch (final DataIntegrityViolationException e) { 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 73330877e..4cb0fd145 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 @@ -1695,14 +1695,16 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { sqlBuilder.append(" WHERE ml.loan_status_id = ? "); sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 "); sqlBuilder.append(" and ml.is_npa = 0 "); + sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)"); sqlBuilder.append(" and (("); sqlBuilder.append(" mr.completed_derived is false "); sqlBuilder.append(" and mr.duedate < ? )"); sqlBuilder.append(" or dd.expected_disburse_date < ? ) "); sqlBuilder.append(" group by ml.id"); try { + String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, - new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()), formatter.print(LocalDate.now()) }); + new Object[] { LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate }); } catch (final EmptyResultDataAccessException e) { return null; } 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 88b38c017..1c53278d3 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 @@ -492,8 +492,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf for (LoanRepaymentScheduleInstallment installment : installments) { if (installment.getId() == null) { this.repaymentScheduleInstallmentRepository.save(installment); - } else { - break; } } this.loanRepository.saveAndFlush(loan); @@ -515,9 +513,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf for (LoanRepaymentScheduleInstallment installment : installments) { if (installment.getId() == null) { this.repaymentScheduleInstallmentRepository.save(installment); - } else { - break; - } + } } this.loanRepository.save(loan); } catch (final DataIntegrityViolationException e) { @@ -893,6 +889,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = transactionToAdjust.getTransactionDate().isAfter(transactionDate) ? transactionDate : transactionToAdjust + .getTransactionDate(); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, @@ -1040,6 +1038,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = transactionDate; } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); @@ -1135,6 +1134,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = command.localDateValueOfParameterNamed("transactionDate"); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, @@ -1217,6 +1217,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + recalculateFrom = command.localDateValueOfParameterNamed("transactionDate"); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); @@ -1356,6 +1357,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf boolean isAppliedOnBackDate = false; LoanCharge loanCharge = null; + LocalDate recalculateFrom =loan.fetchInterestRecalculateFromDate(); if (chargeDefinition.isPercentageOfDisbursementAmount()) { LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) { @@ -1367,7 +1369,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE, constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge)); validateAddLoanCharge(loan, chargeDefinition, loanCharge); - isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + addCharge(loan, chargeDefinition, loanCharge); + isAppliedOnBackDate = true; + if (recalculateFrom.isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) { + recalculateFrom = disbursementDetail.expectedDisbursementDateAsLocalDate(); + } } } loan.addTrancheLoanCharge(chargeDefinition); @@ -1378,12 +1384,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf validateAddLoanCharge(loan, chargeDefinition, loanCharge); isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + if (loanCharge.getDueLocalDate() == null || recalculateFrom.isAfter(loanCharge.getDueLocalDate())) { + isAppliedOnBackDate = true; + recalculateFrom = loanCharge.getDueLocalDate(); + } } boolean reprocessRequired = true; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) { - runScheduleRecalculation(loan); + + runScheduleRecalculation(loan, recalculateFrom); reprocessRequired = false; } updateOriginalSchedule(loan); @@ -1445,7 +1456,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } - public void runScheduleRecalculation(Loan loan) { + public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) { AppUser currentUser = getAppUserIfPresent(); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( @@ -1466,7 +1477,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf WorkingDays workingDays = this.workingDaysRepository.findOne(); final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); - LocalDate recalculateFrom = null; HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, @@ -2413,6 +2423,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); boolean runInterestRecalculation = false; + LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant(); for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) { final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString()); @@ -2422,12 +2433,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf overdueInstallment.getPeriodNumber(), command, loan, existingTransactionIds, existingReversedTransactionIds); loan = overdueDTO.getLoan(); runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation(); + if (recalculateFrom.isAfter(overdueDTO.getRecalculateFrom())) { + recalculateFrom = overdueDTO.getRecalculateFrom(); + } } if (loan != null) { boolean reprocessRequired = true; + LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate(); + if(recalculateFrom.isAfter(recalculatedTill)){ + recalculateFrom = recalculatedTill; + } + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) { - runScheduleRecalculation(loan); + runScheduleRecalculation(loan, recalculateFrom); reprocessRequired = false; } updateOriginalSchedule(loan); @@ -2482,7 +2501,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (feeFrequency == null) { scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue())); } else { - while (new LocalDate().isAfter(startDate)) { + while (DateUtils.getLocalDateOfTenant().isAfter(startDate)) { scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue())); LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency), chargeDefinition.feeInterval(), startDate, null, null); @@ -2505,6 +2524,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } installment = loan.fetchRepaymentScheduleInstallment(periodNumber); } + LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant(); if (loan != null) { this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE, constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); @@ -2518,10 +2538,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate; + if (entry.getValue().isBefore(recalculateFrom)) { + recalculateFrom = entry.getValue(); + } } } - return new LoanOverdueDTO(loan, runInterestRecalculation); + return new LoanOverdueDTO(loan, runInterestRecalculation, recalculateFrom); } @Override @@ -2845,7 +2868,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; - LocalDate recalculateFrom = null; + LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); Long overdurPenaltyWaitPeriod = null; final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java index 169683cf1..50915a319 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java @@ -5,16 +5,19 @@ */ package org.mifosplatform.portfolio.loanproduct.data; +import org.joda.time.LocalDate; import org.mifosplatform.portfolio.loanaccount.domain.Loan; public class LoanOverdueDTO { private final Loan loan; private final boolean runInterestRecalculation; + private final LocalDate recalculateFrom; - public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation) { + public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation, final LocalDate recalculateFrom) { this.loan = loan; this.runInterestRecalculation = runInterestRecalculation; + this.recalculateFrom = recalculateFrom; } public boolean isRunInterestRecalculation() { @@ -24,4 +27,9 @@ public class LoanOverdueDTO { public Loan getLoan() { return this.loan; } + + + public LocalDate getRecalculateFrom() { + return this.recalculateFrom; + } } diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql new file mode 100644 index 000000000..21275bf3c --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V276__loan_recalulated_till_date.sql @@ -0,0 +1,2 @@ +ALTER TABLE `m_loan` + ADD COLUMN `interest_recalcualated_on` DATE NULL DEFAULT NULL AFTER `accrued_till`; \ No newline at end of file