From 6c6362bf343cc40d2027b8deb257e7ae4082d359 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Thu, 28 Aug 2014 22:38:20 +0530 Subject: [PATCH] MIFOSX-1490 : added interest schedule for interest first repayment strategies --- ...stComponent_for_interest_recalculation.sql | 2 + .../AccountingScenarioIntegrationTest.java | 46 +- .../portfolio/loanaccount/domain/Loan.java | 48 +- .../LoanRepaymentScheduleInstallment.java | 14 +- ...oanRepaymentScheduleProcessingWrapper.java | 42 +- ...RepaymentScheduleTransactionProcessor.java | 40 +- ...RepaymentScheduleTransactionProcessor.java | 14 +- ...RepaymentScheduleTransactionProcessor.java | 5 + .../domain/AbstractLoanScheduleGenerator.java | 521 ++++++++++++++---- .../domain/LoanApplicationTerms.java | 17 + .../domain/LoanScheduleGenerator.java | 5 +- .../LoanScheduleModelDisbursementPeriod.java | 5 + .../domain/LoanScheduleModelPeriod.java | 2 + .../LoanScheduleModelRepaymentPeriod.java | 13 +- .../domain/RecalculationDetail.java | 10 +- .../LoanAccrualWritePlatformServiceImpl.java | 86 ++- .../service/LoanReadPlatformServiceImpl.java | 13 +- .../domain/AmortizationMethod.java | 4 + ...anRepaymentScheduleInstallmentBuilder.java | 3 +- 19 files changed, 662 insertions(+), 228 deletions(-) create mode 100755 mifosng-db/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql diff --git a/mifosng-db/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql b/mifosng-db/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql new file mode 100755 index 000000000..07ff29880 --- /dev/null +++ b/mifosng-db/migrations/core_db/V194__added_recalculatedInterestComponent_for_interest_recalculation.sql @@ -0,0 +1,2 @@ +ALTER TABLE `m_loan_repayment_schedule` + ADD COLUMN `recalculated_interest_component` TINYINT(1) NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java index b8054ef47..4a9d5ef6e 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java @@ -8,6 +8,7 @@ package org.mifosplatform.integrationtests; import static org.junit.Assert.assertEquals; import java.text.DateFormat; +import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -15,6 +16,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.List; +import org.joda.time.Days; import org.joda.time.LocalDate; import org.junit.Assert; import org.junit.Before; @@ -722,9 +724,28 @@ public class AccountingScenarioIntegrationTest { e.printStackTrace(); } + final ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, + loanID); // MAKE 1 - final float FIRST_INTEREST = 200.0f; - final float INTEREST_4_DAYS = 13.33F; + List fromDateList = (List) loanSchedule.get(1).get("fromDate"); + LocalDate fromDateLocal = LocalDate.now(); + fromDateLocal = fromDateLocal.withYear((int) fromDateList.get(0)); + fromDateLocal = fromDateLocal.withMonthOfYear((int) fromDateList.get(1)); + fromDateLocal = fromDateLocal.withDayOfMonth((int) fromDateList.get(2)); + + List dueDateList = (List) loanSchedule.get(1).get("dueDate"); + LocalDate dueDateLocal = LocalDate.now(); + dueDateLocal = dueDateLocal.withYear((int) dueDateList.get(0)); + dueDateLocal = dueDateLocal.withMonthOfYear((int) dueDateList.get(1)); + dueDateLocal = dueDateLocal.withDayOfMonth((int) dueDateList.get(2)); + + int totalDaysInPeriod = Days.daysBetween(fromDateLocal, dueDateLocal).getDays(); + + float totalInterest = (float) loanSchedule.get(1).get("interestOriginalDue"); + DecimalFormat numberFormat = new DecimalFormat("#.00"); + float INTEREST_4_DAYS = totalInterest / totalDaysInPeriod * 4; + INTEREST_4_DAYS = new Float(numberFormat.format(INTEREST_4_DAYS)); + this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(currentDate), INTEREST_4_DAYS, FEE_PORTION, PENALTY_PORTION, loanID); @@ -797,8 +818,27 @@ public class AccountingScenarioIntegrationTest { this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate); + final ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, + loanID); // MAKE 1 - final float INTEREST_3_DAYS = 10.0F; + List fromDateList = (List) loanSchedule.get(1).get("fromDate"); + LocalDate fromDateLocal = LocalDate.now(); + fromDateLocal = fromDateLocal.withYear((int) fromDateList.get(0)); + fromDateLocal = fromDateLocal.withMonthOfYear((int) fromDateList.get(1)); + fromDateLocal = fromDateLocal.withDayOfMonth((int) fromDateList.get(2)); + + List dueDateList = (List) loanSchedule.get(1).get("dueDate"); + LocalDate dueDateLocal = LocalDate.now(); + dueDateLocal = dueDateLocal.withYear((int) dueDateList.get(0)); + dueDateLocal = dueDateLocal.withMonthOfYear((int) dueDateList.get(1)); + dueDateLocal = dueDateLocal.withDayOfMonth((int) dueDateList.get(2)); + + int totalDaysInPeriod = Days.daysBetween(fromDateLocal, dueDateLocal).getDays(); + + float totalInterest = (float) loanSchedule.get(1).get("interestOriginalDue"); + DecimalFormat numberFormat = new DecimalFormat("#.00"); + float INTEREST_3_DAYS = totalInterest / totalDaysInPeriod * 3; + INTEREST_3_DAYS = new Float(numberFormat.format(INTEREST_3_DAYS)); this.loanTransactionHelper.checkAccrualTransactionForRepayment(getDateAsLocalDate(runOndate), INTEREST_3_DAYS, FEE_PORTION, PENALTY_PORTION, loanID); 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 adfed930b..de97846fb 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 @@ -977,7 +977,7 @@ public class Loan extends AbstractPersistable { scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue()); + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); addRepaymentScheduleInstallment(installment); } } @@ -3774,15 +3774,17 @@ public class Loan extends AbstractPersistable { final Set loanChargePerInstallments = new HashSet<>(); if (loanCharge.isInstalmentFee()) { for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isFlat()) { - amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); - } else { - amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment) - .getAmount(); + if (!installment.isRecalculatedInterestComponent()) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isFlat()) { + amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); + } else { + amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), + installment).getAmount(); + } + final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment); + loanChargePerInstallments.add(loanInstallmentCharge); } - final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment); - loanChargePerInstallments.add(loanInstallmentCharge); } } return loanChargePerInstallments; @@ -4140,8 +4142,8 @@ public class Loan extends AbstractPersistable { updateLoanSummaryDerivedFields(); } - public Money fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) { - Money amount = Money.zero(getCurrency()); + public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) { + LoanRepaymentScheduleInstallment installment = null; if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); @@ -4177,21 +4179,27 @@ public class Loan extends AbstractPersistable { loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate()); - amount = loanScheduleGenerator.fetchPrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), loanApplicationTerms, - mc); + installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), + loanApplicationTerms, LocalDate.now()); } else { - amount = this.getTotalOutstandingOnLoan(); + installment = this.getTotalOutstandingOnLoan(); } - return amount; + return installment; } - private Money getTotalOutstandingOnLoan() { - Money cumulativeOurstanding = Money.zero(loanCurrency()); - + private LoanRepaymentScheduleInstallment getTotalOutstandingOnLoan() { + Money feeCharges = Money.zero(loanCurrency()); + Money penaltyCharges = Money.zero(loanCurrency()); + Money totalPrincipal = Money.zero(loanCurrency()); + Money totalInterest = Money.zero(loanCurrency()); for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { - cumulativeOurstanding = cumulativeOurstanding.plus(scheduledRepayment.getTotalOutstanding(getCurrency())); + totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency())); + totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency())); + feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency())); + penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency())); } - return cumulativeOurstanding; + return new LoanRepaymentScheduleInstallment(null, 0, LocalDate.now(), LocalDate.now(), totalPrincipal.getAmount(), + totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false); } public List fetchRepaymentScheduleInstallments() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 5b9a1f44b..73cfde635 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -99,6 +99,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "obligations_met_on_date") private Date obligationsMetOnDate; + @Column(name = "recalculated_interest_component", nullable = false) + private boolean recalculatedInterestComponent; + protected LoanRepaymentScheduleInstallment() { this.installmentNumber = null; this.fromDate = null; @@ -108,7 +111,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges, - final BigDecimal penaltyCharges) { + final BigDecimal penaltyCharges, boolean recalculatedInterestComponent) { this.loan = loan; this.installmentNumber = installmentNumber; this.fromDate = fromDate.toDateTimeAtStartOfDay().toDate(); @@ -118,6 +121,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.feeChargesCharged = defaultToNullIfZero(feeCharges); this.penaltyCharges = defaultToNullIfZero(penaltyCharges); this.obligationsMet = false; + this.recalculatedInterestComponent = recalculatedInterestComponent; } private BigDecimal defaultToNullIfZero(final BigDecimal value) { @@ -540,4 +544,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public Money getTotalPaidLate(final MonetaryCurrency currency) { return Money.of(currency, this.totalPaidLate); } + + public boolean isRecalculatedInterestComponent() { + return this.recalculatedInterestComponent; + } + + public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) { + this.recalculatedInterestComponent = recalculatedInterestComponent; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index 97f542ffb..bae71497b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -30,18 +30,18 @@ public class LoanRepaymentScheduleProcessingWrapper { for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, - currency, period, repaymentPeriods.size(), totalInterest); + currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, - currency); + currency, !period.isRecalculatedInterestComponent()); final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), - loanCharges, currency); + loanCharges, currency, !period.isRecalculatedInterestComponent()); final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), - loanCharges, currency, period, repaymentPeriods.size(), totalInterest); + loanCharges, currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), - loanCharges, currency); + loanCharges, currency, !period.isRecalculatedInterestComponent()); final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, - period.getDueDate(), loanCharges, currency); + period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent()); period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, @@ -53,13 +53,13 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, int numberOfRepayments, - final Money totalInterest) { + final Money totalInterest, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -75,11 +75,11 @@ public class LoanRepaymentScheduleProcessingWrapper { } else { cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments))); } - }else if (loanCharge.isOverdueInstallmentCharge() + } else if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); - }else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) + } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -101,13 +101,13 @@ public class LoanRepaymentScheduleProcessingWrapper { } private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency) { + final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency)); @@ -122,13 +122,13 @@ public class LoanRepaymentScheduleProcessingWrapper { } private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency) { + final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency)); @@ -144,13 +144,13 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, - int numberOfRepayments, Money totalInterest) { + int numberOfRepayments, Money totalInterest, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -166,7 +166,7 @@ public class LoanRepaymentScheduleProcessingWrapper { } else { cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments))); } - }else if (loanCharge.isOverdueInstallmentCharge() + } else if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); @@ -192,13 +192,13 @@ public class LoanRepaymentScheduleProcessingWrapper { } private Money cumulativePenaltyChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency) { + final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency)); @@ -213,13 +213,13 @@ public class LoanRepaymentScheduleProcessingWrapper { } private Money cumulativePenaltyChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, final MonetaryCurrency currency) { + final Set loanCharges, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 74e555b2f..ac473f109 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -6,7 +6,6 @@ package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -26,6 +25,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail; /** * Abstract implementation of {@link LoanRepaymentScheduleTransactionProcessor} @@ -438,29 +438,51 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } @Override - public Map handleRepaymentSchedule(final List transactionsPostDisbursement, + public List handleRepaymentSchedule(final List transactionsPostDisbursement, final MonetaryCurrency currency, final List installments, - final LoanRepaymentScheduleInstallment installment, final Map recalculationDates) { - Map unprocessedMap = new HashMap<>(); + final LoanRepaymentScheduleInstallment installment, final Map recalculationDates, + final LoanTransaction preCloseTransaction) { + List earlypaymentDetail = new ArrayList<>(); Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { Money amountToProcess = null; + LocalDate transactionDate = loanTransaction.getTransactionDate(); + if (loanTransaction != preCloseTransaction) { + transactionDate = recalculationDates.get(transactionDate); + } final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); newLoanTransaction.resetDerivedComponents(); Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); - if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) - && recalculationDates.get(loanTransaction.getTransactionDate()).isBefore(installment.getDueDate())) { + if (transactionDate.isAfter(installment.getFromDate()) && transactionDate.isBefore(installment.getDueDate())) { Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency)); if (earlyPayment.isGreaterThanZero()) { - unprocessedMap.put(loanTransaction.getTransactionDate(), earlyPayment); + earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, earlyPayment, false)); } currentInstallmentOutstanding = installment.getTotalOutstanding(currency); } if (unProcessed.isGreaterThanZero()) { - unprocessedMap.put(loanTransaction.getTransactionDate(), unProcessed); + earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, unProcessed, false)); } } - return unprocessedMap; + return earlypaymentDetail; } + + @Override + public boolean isInterestFirstRepaymentScheduleTransactionProcessor() { + return false; + } + + @Override + public void applyTransaction(List transactionsPostDisbursement, MonetaryCurrency currency, + List installments) { + for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { + Money amountToProcess = null; + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + newLoanTransaction.resetDerivedComponents(); + processTransaction(newLoanTransaction, currency, installments, amountToProcess); + + } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java index df178c718..f91f20757 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java @@ -11,11 +11,11 @@ import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail; public interface LoanRepaymentScheduleTransactionProcessor { @@ -28,8 +28,16 @@ public interface LoanRepaymentScheduleTransactionProcessor { void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); - Map handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, + List handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, List installments, LoanRepaymentScheduleInstallment currentInstallment, - Map recalculationDates); + Map recalculationDates, LoanTransaction preCloseTransaction); + + /** + * Used in interest recalculation to introduce new interest only installment. + */ + boolean isInterestFirstRepaymentScheduleTransactionProcessor(); + + void applyTransaction(List transactionsPostDisbursement, MonetaryCurrency currency, + List installments); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java index 663c4fbb8..b98e6eefb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java @@ -204,4 +204,9 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { // dont do anything for with loan over-payment } + + @Override + public boolean isInterestFirstRepaymentScheduleTransactionProcessor() { + return true; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index d768a95b8..2bb3dc82b 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 @@ -97,6 +97,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate periodStartDateApplicableForInterest = periodStartDate; int periodNumber = 1; + int instalmentNumber = 1; Money totalCumulativePrincipal = principalDisbursed.zero(); Money totalCumulativeInterest = principalDisbursed.zero(); Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero(); @@ -109,12 +110,43 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } Money reducePrincipal = totalCumulativePrincipal.zero(); Money fixedEmiAmount = totalCumulativePrincipal.zero(); + int daysCalcForInstallmentNumber = 0; + LocalDate scheduleStartDateAsPerFrequency = periodStartDate; while (!outstandingBalance.isZero()) { isFirstRepayment = false; - actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isFirstRepayment); - LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isHolidayEnabled, holidays, workingDays); + // to insert a new schedule between actual schedule to collect + // interest(till the transaction date) first on a payment + boolean recalculatedInterestComponent = false; + RecalculationDetail interestonlyPeriodDetail = null; + if (diffAmt != null) { + for (RecalculationDetail recalculationDetail : diffAmt) { + if (recalculationDetail.isInterestompound() && recalculationDetail.getStartDate().isEqual(periodStartDate)) { + interestonlyPeriodDetail = recalculationDetail; + break; + } + } + } + LocalDate scheduledDueDate = null; + LocalDate scheduledDueDateAsPerFrequency = null; + + int daysInPeriodApplicableInFullInstallment = 0; + // will change the schedule dates as per the interest only Period + if (interestonlyPeriodDetail == null) { + actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, + isFirstRepayment); + scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, + isHolidayEnabled, holidays, workingDays); + scheduledDueDateAsPerFrequency = scheduledDueDate; + } else { + recalculatedInterestComponent = true; + scheduledDueDate = interestonlyPeriodDetail.getToDate(); + daysCalcForInstallmentNumber = periodNumber; + LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, + loanApplicationTerms, isFirstRepayment); + scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms, + isHolidayEnabled, holidays, workingDays); + } + final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); if (loanApplicationTerms.isMultiDisburseLoan()) { loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate); @@ -141,10 +173,27 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); } - final double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator - .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, - loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), - loanApplicationTerms.getRepaymentEvery()); + // this block identifies full payment period as per the + // schedule(excluding interest payments) for interest calculation + daysInPeriodApplicableInFullInstallment = daysInPeriodApplicableForInterest; + if (daysCalcForInstallmentNumber == periodNumber) { + periodStartDateApplicableForInterest = scheduleStartDateAsPerFrequency; + if (scheduleStartDateAsPerFrequency.isBefore(idealDisbursementDate)) { + if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { + periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); + } else { + periodStartDateApplicableForInterest = idealDisbursementDate; + } + } + + daysInPeriodApplicableInFullInstallment = Days.daysBetween(periodStartDateApplicableForInterest, + scheduledDueDateAsPerFrequency).getDays(); + } + double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator + .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, + scheduledDueDateAsPerFrequency, loanApplicationTerms.getInterestChargedFromLocalDate(), + loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); + Money balanceForcalculation = outstandingBalance; // reduce principal is the early payment, will processed as per the // reschedule strategy @@ -177,10 +226,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // 5 determine principal,interest of repayment period - final PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( + PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, - daysInPeriodApplicableForInterest, balanceForcalculation, loanApplicationTerms, periodNumber, mc); + daysInPeriodApplicableInFullInstallment, balanceForcalculation, loanApplicationTerms, periodNumber, mc); if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { @@ -190,17 +239,34 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // update cumulative fields for principal & interest Money interestForThisinstallment = principalInterestForThisPeriod.interest(); - totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal()); + if (daysCalcForInstallmentNumber == periodNumber) { + interestForThisinstallment = interestForThisinstallment.zero().plus( + calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment, + daysInPeriodApplicableForInterest)); + } + Money principalForThisPeriod = principalDisbursed.zero(); + Money outstandingForThePeriod = outstandingBalance; + // Exclude principal portion for interest only installments + if (!recalculatedInterestComponent) { + principalForThisPeriod = principalInterestForThisPeriod.principal(); + if (daysCalcForInstallmentNumber == periodNumber && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { + principalForThisPeriod = principalForThisPeriod.plus(principalInterestForThisPeriod.interest()).minus( + interestForThisinstallment); + } + totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod); + + // 6. update outstandingLoanBlance using correct 'principalDue' + outstandingBalance = outstandingBalance.minus(principalForThisPeriod); + } + totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); - // 6. update outstandingLoanBlance using correct 'principalDue' - outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal()); Money actualOutstandingbalance = outstandingBalance; Money feeChargesForInstallment = principalDisbursed.zero(); Money penaltyChargesForInstallment = principalDisbursed.zero(); - Money principalForThisPeriod = principalInterestForThisPeriod.principal(); + if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); reducePrincipal = reducePrincipal.zero(); @@ -213,51 +279,87 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment); } + Money extraPrincipal = reducePrincipal.zero(); // this block is to identify interest based on late/early payment if (diffAmt != null && !diffAmt.isEmpty()) { BigDecimal interestDueToLatePayment = BigDecimal.ZERO; BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; + + boolean isperiodPrincipalReduced = false; for (RecalculationDetail detail : diffAmt) { - // will increase the principal portion and reduces interest - // as per the number of days. and also identifies reduce - // principal for reschedule strategy - if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) - && !detail.getStartDate().isAfter(scheduledDueDate)) { - reducePrincipal = reducePrincipal.plus(detail.getAmount()); - int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); - if (diffDays > 0) { - reducePrincipal = reducePrincipal.minus(principalForThisPeriod); + if (!detail.isInterestompound()) { + // will increase the principal portion and reduces + // interest + // as per the number of days. and also identifies reduce + // principal for reschedule strategy + if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) + && !detail.getStartDate().isAfter(scheduledDueDate)) { + reducePrincipal = reducePrincipal.plus(detail.getAmount()); + extraPrincipal = extraPrincipal.plus(detail.getAmount()); + int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); + if (diffDays > 0 && !isperiodPrincipalReduced) { + reducePrincipal = reducePrincipal.minus(principalForThisPeriod); + isperiodPrincipalReduced = true; + } + + LocalDate startDate = detail.getStartDate(); + if (loanApplicationTerms.getInterestChargedFromLocalDate() != null + && startDate.isBefore(loanApplicationTerms.getInterestChargedFromLocalDate())) { + startDate = loanApplicationTerms.getInterestChargedFromLocalDate(); + } + LocalDate endDate = scheduledDueDate; + if (startDate.isBefore(endDate)) { + Money amountForInterestCalculation = detail.getAmount(); + Money balanceDiff = outstandingForThePeriod.minus(extraPrincipal); + if (balanceDiff.isLessThanZero()) { + amountForInterestCalculation = amountForInterestCalculation.plus(balanceDiff); + } + interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(calculateInterestForSpecificDays( + mc, loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace, + daysInPeriodApplicableInFullInstallment, interestCalculationGraceOnRepaymentPeriodFraction, + amountForInterestCalculation, startDate, endDate)); + } + } - if (reducePrincipal.isLessThanZero()) { - reducePrincipal = reducePrincipal.zero(); - } else { - Money actualOutstanding = outstandingBalance.minus(reducePrincipal); - principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); - if (actualOutstanding.isLessThanZero()) { - principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); + // calculates the interest for late payment and increase + // the interest for the installment + else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) + && periodStartDate.isBefore(LocalDate.now())) { + LocalDate fromDate = periodStartDate; + LocalDate toDate = scheduledDueDate; + if (!detail.getStartDate().isBefore(periodStartDate)) { + fromDate = detail.getStartDate(); + } + if (!detail.getToDate().isAfter(scheduledDueDate)) { + toDate = detail.getToDate(); + } + if (toDate.isAfter(LocalDate.now())) { + toDate = LocalDate.now(); + } + if (fromDate.isAfter(LocalDate.now())) { + fromDate = LocalDate.now(); + } + if (loanApplicationTerms.getInterestChargedFromLocalDate() != null + && fromDate.isBefore(loanApplicationTerms.getInterestChargedFromLocalDate())) { + fromDate = loanApplicationTerms.getInterestChargedFromLocalDate(); + } + if (fromDate.isBefore(toDate)) { + interestDueToLatePayment = interestDueToLatePayment.add(calculateInterestForSpecificDays(mc, + loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace, + daysInPeriodApplicableInFullInstallment, interestCalculationGraceOnRepaymentPeriodFraction, + detail.getAmount(), fromDate, toDate)); } } - interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(loanApplicationTerms.interestRateFor( - this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), detail.getStartDate(), scheduledDueDate)); - } - // calculates the interest for late payment and increase - // the interest for the installment - else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) - && periodStartDate.isBefore(LocalDate.now())) { - LocalDate fromDate = periodStartDate; - LocalDate toDate = scheduledDueDate; - if (!detail.getStartDate().isBefore(periodStartDate)) { - fromDate = detail.getStartDate(); - } - if (!detail.getToDate().isAfter(scheduledDueDate)) { - toDate = detail.getToDate(); - } - if (toDate.isAfter(LocalDate.now())) { - toDate = LocalDate.now(); - } - interestDueToLatePayment = interestDueToLatePayment.add(loanApplicationTerms.interestRateFor( - this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), fromDate, toDate)); + } + + if (reducePrincipal.isLessThanZero()) { + reducePrincipal = reducePrincipal.zero(); + } else { + Money actualOutstanding = outstandingBalance.minus(reducePrincipal); + principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); + if (actualOutstanding.isLessThanZero()) { + principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); } } @@ -281,9 +383,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .plus(penaltyChargesForInstallment); // 9. create repayment period from parts - final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, periodStartDate, + final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, periodStartDate, scheduledDueDate, principalForThisPeriod, actualOutstandingbalance, interestForThisinstallment, - feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue); + feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, recalculatedInterestComponent); periods.add(installment); // handle cumulative fields @@ -296,7 +398,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDate = scheduledDueDate; periodStartDateApplicableForInterest = periodStartDate; - periodNumber++; + if (!recalculatedInterestComponent) { + periodNumber++; + scheduleStartDateAsPerFrequency = periodStartDate; + } + instalmentNumber++; } if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse)) { @@ -311,10 +417,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount()); @@ -324,14 +430,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // this block is to add extra re-payment schedules with interest portion // if the last payment is missed if (diffAmt != null && !diffAmt.isEmpty() && !periodStartDate.isAfter(LocalDate.now())) { + boolean recalculatedInterestComponent = true; Map processDetails = new TreeMap<>(); for (RecalculationDetail detail : diffAmt) { - if (!periodStartDate.isAfter(detail.getStartDate()) && detail.isLatePayment()) { + if (!periodStartDate.isAfter(detail.getStartDate()) && detail.isLatePayment() && !detail.isInterestompound()) { if (processDetails.containsKey(detail.getToDate())) { RecalculationDetail recalculationDetail = processDetails.get(detail.getToDate()); RecalculationDetail updatedDetail = new RecalculationDetail(recalculationDetail.isLatePayment(), recalculationDetail.getStartDate(), recalculationDetail.getToDate(), recalculationDetail.getAmount().plus( - detail.getAmount())); + detail.getAmount()), detail.isInterestompound()); processDetails.put(updatedDetail.getToDate(), updatedDetail); } else { processDetails.put(detail.getToDate(), detail); @@ -350,11 +457,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount()); totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount()); - final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, + final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber, fromDate, toDate, interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, - interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment); + interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, + recalculatedInterestComponent); periods.add(installment); - periodNumber++; + instalmentNumber++; } } } @@ -365,6 +473,23 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalOutstanding); } + private BigDecimal calculateInterestForSpecificDays(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + int periodNumber, Money totalOutstandingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest, + double interestCalculationGraceOnRepaymentPeriodFraction, Money amount, LocalDate startDate, LocalDate endDate) { + PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod(this.paymentPeriodsInOneYearCalculator, + interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, totalOutstandingInterestPaymentDueToGrace.zero(), + daysInPeriodApplicableForInterest, amount); + + int days = Days.daysBetween(startDate, endDate).getDays(); + double interest = calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest.interest(), days); + + return BigDecimal.valueOf(interest); + } + + private double calculateInterestForDays(int daysInPeriodApplicableForInterest, Money interest, int days) { + return ((interest.getAmount().doubleValue()) / daysInPeriodApplicableForInterest) * days; + } + public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest, @@ -432,13 +557,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, - final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments) { + final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, + boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -482,13 +608,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, - final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments) { + final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - if (loanCharge.isInstalmentFee()) { + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { @@ -544,9 +670,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LoanScheduleModel loanScheduleModel = null; List installments = previousSchedule; List removeInstallments = new ArrayList<>(); + MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); + LoanTransaction preCloseTransaction = getPreclosureTransaction(transactions, installments, currency, loanApplicationTerms); for (LoanRepaymentScheduleInstallment installment : installments) { - if (!loanApplicationTerms.isPrincipalGraceApplicableForThisPeriod(installment.getInstallmentNumber()) - && installment.getPrincipal(loanApplicationTerms.getCurrency()).isZero()) { + if (installment.isRecalculatedInterestComponent() && recalculateFrom != null + && !recalculateFrom.isAfter(installment.getDueDate())) { removeInstallments.add(installment); } } @@ -556,22 +684,129 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener workingDays); installments = retrieveRepaymentSchedule(loanScheduleModel); } + if (recalculateFrom == null) { recalculateFrom = loanApplicationTerms.getExpectedDisbursementDate(); } + + final List recalculationDetails = new ArrayList<>(); + final Map retainRecalculationDetails = new HashMap<>(); + LocalDate processTransactionsForInterestCompound = loanApplicationTerms.getExpectedDisbursementDate(); + while (recalculateFrom.isAfter(processTransactionsForInterestCompound) && !transactions.isEmpty() + && loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { + + int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound); + List processTransactions = processTransactions(transactions, processTransactionsForInterestCompound); + if (processTransactions.size() > 0) { + loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, + isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, + loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, false, + installmentNumber, processTransactions, preCloseTransaction); + } + recalculationDetails.retainAll(retainRecalculationDetails.values()); + processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound); + } + while (!recalculateFrom.isAfter(LocalDate.now())) { + int installmentNumber = findLastProcessedInstallmentNumber(installments, recalculateFrom); List processTransactions = processTransactions(transactions, recalculateFrom); + if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor() + && processTransactions.size() > 0) { + loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, + isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, + loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, true, + installmentNumber, processTransactions, preCloseTransaction); + if (loanScheduleModel != null) { + installments = retrieveRepaymentSchedule(loanScheduleModel); + } + } + recalculationDetails.retainAll(retainRecalculationDetails.values()); loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, - processTransactions, installmentNumber); - + processTransactions, installmentNumber, recalculationDetails, preCloseTransaction); + if (loanScheduleModel != null) { + installments = retrieveRepaymentSchedule(loanScheduleModel); + } recalculateFrom = getNextRecalculateFromDate(transactions, recalculateFrom); } return loanScheduleModel; } + /** + * Method identifies interest only repayment periods + */ + private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final ApplicationCurrency applicationCurrency, + final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, + final List holidays, final WorkingDays workingDays, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final List previousSchedule, LoanScheduleModel loanScheduleModel, + List installments, MonetaryCurrency currency, + final List recalculationDetails, final Map retainRecalculationDetails, + boolean recalculate, int installmentNumber, List processTransactions, LoanTransaction preCloseTransaction) { + LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1); + if (loanTransaction == preCloseTransaction) { + processTransactions.add(loanTransaction); + return loanScheduleModel; + } + LocalDate processTransactionsForInterestCompound = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), + loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + List scheduleInstallments = getInstallmentsForInterestCompound(installments, + processTransactionsForInterestCompound, retainRecalculationDetails.keySet()); + + if (!scheduleInstallments.isEmpty()) { + for (LoanRepaymentScheduleInstallment installment : installments) { + installment.resetDerivedComponents(); + installment.updateDerivedFields(currency, loanApplicationTerms.getExpectedDisbursementDate()); + } + loanRepaymentScheduleTransactionProcessor.applyTransaction(processTransactions, currency, installments); + + Money principalUnprocessed = Money.zero(currency); + Money interestUnprocessed = Money.zero(currency); + Money feeUnprocessed = Money.zero(currency); + boolean isBeforeFirstInstallment = false; + if (scheduleInstallments.size() == 1) { + LoanRepaymentScheduleInstallment installment = scheduleInstallments.get(0); + if (installment.getInstallmentNumber() == 1 + && (installment.getDueDate().isAfter(loanTransaction.getTransactionDate()) || (installment + .isRecalculatedInterestComponent() && installment.getDueDate() + .isEqual(loanTransaction.getTransactionDate())))) { + isBeforeFirstInstallment = true; + } + } + if (!isBeforeFirstInstallment) { + for (LoanRepaymentScheduleInstallment installment : scheduleInstallments) { + principalUnprocessed = principalUnprocessed.plus(installment.getPrincipalOutstanding(currency)); + interestUnprocessed = interestUnprocessed.plus(installment.getInterestOutstanding(currency)); + feeUnprocessed = feeUnprocessed.plus(installment.getFeeChargesOutstanding(currency)); + feeUnprocessed = feeUnprocessed.plus(installment.getPenaltyChargesOutstanding(currency)); + } + } + if (interestUnprocessed.isLessThan(loanTransaction.getAmount(currency))) { + LoanRepaymentScheduleInstallment lastProcessedInstallment = scheduleInstallments.get(scheduleInstallments.size() - 1); + LocalDate startDate = lastProcessedInstallment.getDueDate(); + if (isBeforeFirstInstallment) { + startDate = loanApplicationTerms.getExpectedDisbursementDate(); + } + RecalculationDetail recalculationDetail = new RecalculationDetail(false, startDate, processTransactionsForInterestCompound, + null, true); + retainRecalculationDetails.put(processTransactionsForInterestCompound, recalculationDetail); + recalculationDetails.add(recalculationDetail); + if (recalculate) { + recalculationDetails.retainAll(retainRecalculationDetails.values()); + loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, + isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, + loanScheduleModel, installments, processTransactions, installmentNumber, recalculationDetails, + preCloseTransaction); + + } + } + } + processTransactions.add(loanTransaction); + return loanScheduleModel; + } + private LocalDate getNextRecalculateFromDate(List processTransactions, LocalDate preCalculationDate) { LocalDate recalculateFrom = null; for (LoanTransaction loanTransaction : processTransactions) { @@ -590,25 +825,28 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * Method calls regenerate schedule for a particular set of transactions * till all the installments are processed to identify early or late * payments and will be used in regeneration of schedule + * + * @param preCloseTransaction + * TODO */ private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final List previousSchedule, LoanScheduleModel loanScheduleModel, - List installments, List processTransactions, final int skipRecalculation) { + List installments, List processTransactions, final int skipRecalculation, + final List recalculationDetails, LoanTransaction preCloseTransaction) { int processInstallmentsFrom = 0; - Integer numberOfInstallments = previousSchedule.size() - 1; - final List recalculationDetails = new ArrayList<>(); - while (processInstallmentsFrom < numberOfInstallments) { + Integer lastInstallmentNumber = previousSchedule.size(); + while (processInstallmentsFrom < lastInstallmentNumber) { RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor, - processInstallmentsFrom, installments, recalculationDetails, skipRecalculation); + processInstallmentsFrom, installments, recalculationDetails, skipRecalculation, preCloseTransaction); processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber(); if (recalculatedSchedule.getLoanScheduleModel() != null) { loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); installments = retrieveRepaymentSchedule(loanScheduleModel); - numberOfInstallments = installments.size() - 1; + lastInstallmentNumber = installments.size() - 1; } } return loanScheduleModel; @@ -616,13 +854,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener /** * Method identifies late or early payments for schedule recalculation. + * + * @param preCloseTransaction + * TODO */ private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final List transactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, final List installments, final List recalculationDetails, - final int skipRecalculation) { + final int skipRecalculation, LoanTransaction preCloseTransaction) { boolean processRecalculate = false; int processedInstallmentNumber = installmentNumber; @@ -642,9 +883,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener continue; } processedInstallmentNumber = installment.getInstallmentNumber(); - if (installment.getDueDate().isAfter(LocalDate.now())) { - continue; + if (installment.getInstallmentNumber() == installments.size()) { + processRecalculate = true; } + List transactionsForInstallment = new ArrayList<>(); Map recalculationDates = new HashMap<>(); LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, @@ -659,19 +901,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener workingDays)); } } - Map earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule( - transactionsForInstallment, currency, processinstallmets, installment, recalculationDates); + + if (installment.isRecalculatedInterestComponent() && installment.getPrincipal(currency).isGreaterThanZero()) { + diffAmt.add(new RecalculationDetail(false, installment.getDueDate(), null, installment.getPrincipal(currency), false)); + } + List earlypaymentDetail = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule( + transactionsForInstallment, currency, processinstallmets, installment, recalculationDates, preCloseTransaction); // this block is to create early payment entries for schedule // generation - for (Map.Entry entry : earlyPaymentMap.entrySet()) { - LocalDate startDate = entry.getKey(); - if (unpaidPricipal.isGreaterThan(entry.getValue())) { - startDate = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, - workingDays); - } - RecalculationDetail recalculationDetail = new RecalculationDetail(false, startDate, null, entry.getValue()); - if (!entry.getKey().isAfter(installment.getDueDate()) && entry.getKey().isAfter(installment.getFromDate())) { + for (RecalculationDetail recalculationDetail : earlypaymentDetail) { + if (!recalculationDetail.getStartDate().isAfter(installment.getDueDate()) + && recalculationDetail.getStartDate().isAfter(installment.getFromDate())) { diffAmt.add(recalculationDetail); } } @@ -692,7 +933,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener startDate = startDate.minusDays(1); } applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency, - loanApplicationTerms, isHolidayEnabled, holidays, workingDays, installments); + loanApplicationTerms, isHolidayEnabled, holidays, workingDays, installments, preCloseTransaction); totalOutstanding = installment.getTotalOutstanding(currency); if (totalOutstanding.isGreaterThanZero()) { Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency); @@ -714,7 +955,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener break; } RecalculationDetail recalculationDetail = new RecalculationDetail(true, recalculateFrom, recalcualteTill, - latepaymentoutstanding); + latepaymentoutstanding, false); diffAmt.add(recalculationDetail); } startDate = recalculateFrom.plusDays(1); @@ -722,8 +963,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - if ((!diffAmt.isEmpty() && skipRecalculation <= installment.getInstallmentNumber()) - || installment.getInstallmentNumber() == installments.size() - 1) { + if (!diffAmt.isEmpty() && skipRecalculation <= installment.getInstallmentNumber()) { processRecalculate = true; } break; @@ -746,7 +986,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue()); + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); installments.add(installment); } } @@ -756,12 +996,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener /** * Method to identify which transaction did the payment for current * installment + * + * @param preCloseTransaction + * TODO */ private void applyRest(List loanTransactions, LocalDate from, LocalDate to, LoanRepaymentScheduleInstallment installment, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays, final List installments) { + final WorkingDays workingDays, final List installments, LoanTransaction preCloseTransaction) { List transactions = new ArrayList<>(); Map recalculationDates = new HashMap<>(); for (LoanTransaction transaction : loanTransactions) { @@ -776,7 +1019,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(transactions, currency, installments, installment, - recalculationDates); + recalculationDates, preCloseTransaction); } @SuppressWarnings("unused") @@ -796,29 +1039,54 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * Method returns the amount payable to close the loan account as of today. */ @Override - public Money fetchPrepaymentAmount(final List installments, MonetaryCurrency currency, - final LoanApplicationTerms applicationTerms, final MathContext mc) { - Money prepaymentAmount = Money.zero(currency); - Money amount = Money.zero(currency); - LocalDate calculateInterestFrom = LocalDate.now(); + public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final List installments, + MonetaryCurrency currency, final LoanApplicationTerms applicationTerms, final LocalDate onDate) { + Money outstandingPrincipal = Money.zero(currency); + Money feeCharges = Money.zero(currency); + Money penaltyCharges = Money.zero(currency); + Money totalPrincipal = Money.zero(currency); + Money totalInterest = Money.zero(currency); + LocalDate calculateInterestFrom = onDate; + LocalDate dueDate = onDate; + Money inerestForCurrentInstallment = null; for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { if (currentInstallment.isNotFullyPaidOff()) { - if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) { - amount = amount.plus(currentInstallment.getTotalOutstanding(currency)); + if (!currentInstallment.getDueDate().isAfter(onDate)) { + totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); + totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency)); + feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency)); + penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); } else { - prepaymentAmount = prepaymentAmount.plus(currentInstallment.getPrincipalOutstanding(currency)); + outstandingPrincipal = outstandingPrincipal.plus(currentInstallment.getPrincipal(currency)); + totalPrincipal = totalPrincipal.minus(currentInstallment.getPrincipalCompleted(currency)); + totalInterest = totalInterest.minus(currentInstallment.getInterestPaid(currency)).minus( + currentInstallment.getInterestWaived(currency)); if (currentInstallment.getFromDate().isBefore(calculateInterestFrom)) { calculateInterestFrom = currentInstallment.getFromDate(); + dueDate = currentInstallment.getDueDate(); + inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency); } } } } + if (applicationTerms.getInterestChargedFromLocalDate() != null + && calculateInterestFrom.isBefore(applicationTerms.getInterestChargedFromLocalDate())) { + calculateInterestFrom = applicationTerms.getInterestChargedFromLocalDate(); + } + Money interest = Money.zero(currency); + ; + if (calculateInterestFrom.isBefore(onDate)) { + int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, dueDate).getDays(); + int days = Days.daysBetween(calculateInterestFrom, onDate).getDays(); + interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, inerestForCurrentInstallment, days)); + } - BigDecimal interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount, - calculateInterestFrom, LocalDate.now()); - prepaymentAmount = prepaymentAmount.plus(interest).plus(amount); - return prepaymentAmount; + totalInterest = totalInterest.plus(interest); + totalPrincipal = totalPrincipal.plus(outstandingPrincipal); + + return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(), + feeCharges.getAmount(), penaltyCharges.getAmount(), false); } private Integer findLastProcessedInstallmentNumber(final List installments, LocalDate date) { @@ -832,6 +1100,27 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return installmentNumber; } + private List getInstallmentsForInterestCompound( + final List installments, final LocalDate date, Collection processedDates) { + List repaymentScheduleInstallments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment installment : installments) { + if (installment.getDueDate().isEqual(date) + && (!installment.isRecalculatedInterestComponent() || (processedDates.contains(date) && installment + .isRecalculatedInterestComponent()))) { + repaymentScheduleInstallments.clear(); + break; + } else if (installment.getDueDate().isBefore(date)) { + repaymentScheduleInstallments.add(installment); + } else { + if (installment.getInstallmentNumber() == 1) { + repaymentScheduleInstallments.add(installment); + } + break; + } + } + return repaymentScheduleInstallments; + } + private List processTransactions(final List transactions, final LocalDate tillDate) { List toProcess = new ArrayList<>(); for (LoanTransaction loanTransaction : transactions) { @@ -842,4 +1131,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return toProcess; } + private LoanTransaction getPreclosureTransaction(List loanTransactions, + List installments, MonetaryCurrency currency, final LoanApplicationTerms applicationTerms) { + LoanTransaction precloseTransaction = null; + for (LoanTransaction loanTransaction : loanTransactions) { + if (precloseTransaction == null || precloseTransaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate())) { + precloseTransaction = loanTransaction; + } + } + if (precloseTransaction != null) { + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency, + applicationTerms, precloseTransaction.getTransactionDate()); + if (precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) { + precloseTransaction = null; + } + } + + return precloseTransaction; + + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 96933f825..cb2cb8d06 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 @@ -236,6 +236,23 @@ public final class LoanApplicationTerms { interestRecalculationCompoundingMethod, restCalendarInstance); } + public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, + final LoanApplicationTerms applicationTerms) { + return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, + applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, repaymentEvery, + repaymentPeriodFrequencyType, applicationTerms.amortizationMethod, applicationTerms.interestMethod, + applicationTerms.interestRatePerPeriod, applicationTerms.interestRatePeriodFrequencyType, + applicationTerms.annualNominalInterestRate, applicationTerms.interestCalculationPeriodMethod, applicationTerms.principal, + applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate, + applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace, + applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate, + applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan, applicationTerms.fixedEmiAmount, + applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance, applicationTerms.emiAmountVariations, + applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType, + applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod, + applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance); + } + private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final AmortizationMethod amortizationMethod, 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 9cecdbc24..9243db69e 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 @@ -13,7 +13,6 @@ import org.joda.time.LocalDate; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -31,6 +30,6 @@ public interface LoanScheduleGenerator { LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, List previousSchedule, LocalDate recalculateFrom); - Money fetchPrepaymentAmount(List installments, MonetaryCurrency currency, - LoanApplicationTerms applicationTerms, MathContext mc); + LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, + LoanApplicationTerms applicationTerms, LocalDate onDate); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index de2545579..5e944f380 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -94,4 +94,9 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) { return; } + + @Override + public boolean isRecalculatedInterestComponent() { + return false; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java index 38c64b2a2..89ecefae8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java @@ -31,4 +31,6 @@ public interface LoanScheduleModelPeriod { BigDecimal penaltyChargesDue(); void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge); + + boolean isRecalculatedInterestComponent(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java index 096516a6e..7b856bc94 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java @@ -26,18 +26,19 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private Money feeChargesDue; private Money penaltyChargesDue; private Money totalDue; + private final boolean recalculatedInterestComponent; public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, - final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue) { + final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) { return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance, - interestDue, feeChargesDue, penaltyChargesDue, totalDue); + interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent); } public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue, - final Money penaltyChargesDue, final Money totalDue) { + final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) { this.periodNumber = periodNumber; this.fromDate = fromDate; this.dueDate = dueDate; @@ -47,6 +48,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel this.feeChargesDue = feeChargesDue; this.penaltyChargesDue = penaltyChargesDue; this.totalDue = totalDue; + this.recalculatedInterestComponent = recalculatedInterestComponent; } @Override @@ -122,4 +124,9 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge); this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge); } + + @Override + public boolean isRecalculatedInterestComponent() { + return this.recalculatedInterestComponent; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java index 916757a43..f2a02ba24 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java @@ -11,15 +11,18 @@ import org.mifosplatform.organisation.monetary.domain.Money; public class RecalculationDetail { private boolean isLatePayment; + private boolean isInterestompound; private LocalDate startDate; private LocalDate toDate; private Money amount; - public RecalculationDetail(final boolean isLatePayment, final LocalDate startDate, final LocalDate toDate, final Money amount) { + public RecalculationDetail(final boolean isLatePayment, final LocalDate startDate, final LocalDate toDate, final Money amount, + final boolean isInterestompound) { this.isLatePayment = isLatePayment; this.startDate = startDate; this.toDate = toDate; this.amount = amount; + this.isInterestompound = isInterestompound; } public boolean isLatePayment() { @@ -42,8 +45,11 @@ public class RecalculationDetail { return !this.startDate.isAfter(endDate) && !startDate.isAfter(this.toDate); } - public void updateAmount(Money amount) { this.amount = amount; } + + public boolean isInterestompound() { + return this.isInterestompound; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 29eeb25cb..1b45df998 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -24,8 +24,6 @@ import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; import org.mifosplatform.infrastructure.jobs.service.JobName; -import org.mifosplatform.portfolio.common.domain.DaysInMonthType; -import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; @@ -108,7 +106,9 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat if (accruredTill == null || lastLoanId == null || !lastLoanId.equals(accrualData.getLoanId())) { accruredTill = accrualData.getAccruedTill(); } - addAccrualTillSpecificDate(tilldate, accrualData, accruredTill); + if (accruredTill == null || accruredTill.isBefore(tilldate)) { + addAccrualTillSpecificDate(tilldate, accrualData, accruredTill); + } } else { addAccrualAccounting(accrualData); accruredTill = accrualData.getDueDateAsLocaldate(); @@ -131,10 +131,10 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData, final LocalDate accruredTill) throws Exception { LocalDate interestStartDate = accrualData.getFromDateAsLocaldate(); - boolean isInterestStartDateEqulsInstallmentStartdate = true; + // boolean isInterestStartDateEqulsInstallmentStartdate = true; if (accrualData.getInterestCalculatedFrom() != null && accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) { - isInterestStartDateEqulsInstallmentStartdate = false; + // isInterestStartDateEqulsInstallmentStartdate = false; if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) { interestStartDate = accrualData.getInterestCalculatedFrom(); } else { @@ -157,22 +157,18 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays(); // if (daysToBeAccrued < 1) { return; } int daysInSchedule = Days.daysBetween(interestStartDate, tilldate).getDays(); - switch (accrualData.getRepaymentFrequency()) { - case MONTHS: - int numberOfDaysInMonth = calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData); - if (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays < numberOfDaysInMonth)) { - totalNumberOfDays = numberOfDaysInMonth; - } - break; - case YEARS: - int numberOfDaysinYear = calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); - if (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays < numberOfDaysinYear)) { - totalNumberOfDays = numberOfDaysinYear; - } - break; - default: - break; - } + /* + * switch (accrualData.getRepaymentFrequency()) { case MONTHS: int + * numberOfDaysInMonth = + * calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData); + * if (!(!isInterestStartDateEqulsInstallmentStartdate && + * totalNumberOfDays < numberOfDaysInMonth)) { totalNumberOfDays = + * numberOfDaysInMonth; } break; case YEARS: int numberOfDaysinYear = + * calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); if + * (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays + * < numberOfDaysinYear)) { totalNumberOfDays = numberOfDaysinYear; } + * break; default: break; } + */ double interestPerDay = accrualData.getInterestIncome().doubleValue() / totalNumberOfDays; BigDecimal amount = BigDecimal.ZERO; @@ -232,37 +228,23 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat } } - private int calculateTotalNumberOfDaysForMonth(final int totalNumberOfDays, final LoanScheduleAccrualData accrualData) { - int numberOfDays = totalNumberOfDays; - DaysInMonthType type = DaysInMonthType.fromInt(accrualData.getNumberOfDaysInMonth()); - switch (type) { - case DAYS_30: - numberOfDays = 30 * accrualData.getRepayEvery(); - break; - default: - break; - } - return numberOfDays; - } - - private int calculateTotalNumberOfDaysForYear(final int totalNumberOfDays, final LoanScheduleAccrualData accrualData) { - int numberOfDays = totalNumberOfDays; - DaysInYearType type = DaysInYearType.fromInt(accrualData.getNumberOfDaysInMonth()); - switch (type) { - case DAYS_360: - numberOfDays = 360 * accrualData.getRepayEvery(); - break; - case DAYS_364: - numberOfDays = 364 * accrualData.getRepayEvery(); - break; - case DAYS_365: - numberOfDays = 365 * accrualData.getRepayEvery(); - break; - default: - break; - } - return numberOfDays; - } + /* + * private int calculateTotalNumberOfDaysForMonth(final int + * totalNumberOfDays, final LoanScheduleAccrualData accrualData) { int + * numberOfDays = totalNumberOfDays; DaysInMonthType type = + * DaysInMonthType.fromInt(accrualData.getNumberOfDaysInMonth()); switch + * (type) { case DAYS_30: numberOfDays = 30 * accrualData.getRepayEvery(); + * break; default: break; } return numberOfDays; } + * + * private int calculateTotalNumberOfDaysForYear(final int + * totalNumberOfDays, final LoanScheduleAccrualData accrualData) { int + * numberOfDays = totalNumberOfDays; DaysInYearType type = + * DaysInYearType.fromInt(accrualData.getNumberOfDaysInMonth()); switch + * (type) { case DAYS_360: numberOfDays = 360 * accrualData.getRepayEvery(); + * break; case DAYS_364: numberOfDays = 364 * accrualData.getRepayEvery(); + * break; case DAYS_365: numberOfDays = 365 * accrualData.getRepayEvery(); + * break; default: break; } return numberOfDays; } + */ @Transactional public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) throws Exception { 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 22edf00cf..6256bb6ed 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 @@ -383,12 +383,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate earliestUnpaidInstallmentDate = LocalDate.now(); - final Money precloseAmount = loan.fetchPrepaymentDetail(this.loanScheduleFactory); + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final Collection paymentOptions = this.codeValueReadPlatformService .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - precloseAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null, null); + loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment + .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency) + .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(), + loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, paymentOptions, null, null, null); } @Override @@ -1450,8 +1453,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Long officeId = rs.getLong("officeId"); final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill"); final LocalDate interestCalculatedFrom = JdbcSupport.getLocalDate(rs, "interestCalculatedFrom"); - final Integer numberOfDaysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");; - final Integer numberOfDaysInYear = JdbcSupport.getInteger(rs, "daysInYear");; + final Integer numberOfDaysInMonth = JdbcSupport.getInteger(rs, "daysInMonth"); + ; + final Integer numberOfDaysInYear = JdbcSupport.getInteger(rs, "daysInYear"); + ; final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum"); final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery"); final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java index 7d87bddc8..56cb01cd0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java @@ -44,4 +44,8 @@ public enum AmortizationMethod { } return repaymentMethod; } + + public boolean isEqualInstallment() { + return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS); + } } \ No newline at end of file diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java index b50a5301a..c0abd2fab 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java @@ -24,6 +24,7 @@ public class LoanRepaymentScheduleInstallmentBuilder { private final Money feeCharges = new MoneyBuilder().build(); private final Money penaltyCharges = new MoneyBuilder().build(); private boolean completed = false; + private boolean recalculatedInterestComponent = false; public LoanRepaymentScheduleInstallmentBuilder(final MonetaryCurrency currencyDetail) { this.currencyDetail = currencyDetail; @@ -34,7 +35,7 @@ public class LoanRepaymentScheduleInstallmentBuilder { public LoanRepaymentScheduleInstallment build() { final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this.loan, this.installmentNumber, this.fromDate, this.dueDate, this.principal.getAmount(), this.interest.getAmount(), this.feeCharges.getAmount(), - this.penaltyCharges.getAmount()); + this.penaltyCharges.getAmount(), this.recalculatedInterestComponent); if (this.completed) { installment.payPrincipalComponent(this.latestTransactionDate, this.principal); installment.payInterestComponent(this.latestTransactionDate, this.interest);