MIFOSX-1490 : added interest schedule for interest first repayment strategies
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
ADD COLUMN `recalculated_interest_component` TINYINT(1) NOT NULL DEFAULT '0';
|
||||
+43
-3
@@ -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<HashMap> 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<HashMap> 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);
|
||||
|
||||
|
||||
+28
-20
@@ -977,7 +977,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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<Long> {
|
||||
final Set<LoanInstallmentCharge> 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<Long> {
|
||||
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<Long> {
|
||||
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<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() {
|
||||
|
||||
+13
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+21
-21
@@ -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<LoanCharge> 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<LoanCharge> loanCharges, final MonetaryCurrency currency) {
|
||||
final Set<LoanCharge> 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<LoanCharge> loanCharges, final MonetaryCurrency currency) {
|
||||
final Set<LoanCharge> 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<LoanCharge> 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<LoanCharge> loanCharges, final MonetaryCurrency currency) {
|
||||
final Set<LoanCharge> 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<LoanCharge> loanCharges, final MonetaryCurrency currency) {
|
||||
final Set<LoanCharge> 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));
|
||||
|
||||
+31
-9
@@ -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<LocalDate, Money> handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement,
|
||||
public List<RecalculationDetail> handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement,
|
||||
final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanRepaymentScheduleInstallment installment, final Map<LocalDate, LocalDate> recalculationDates) {
|
||||
Map<LocalDate, Money> unprocessedMap = new HashMap<>();
|
||||
final LoanRepaymentScheduleInstallment installment, final Map<LocalDate, LocalDate> recalculationDates,
|
||||
final LoanTransaction preCloseTransaction) {
|
||||
List<RecalculationDetail> 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<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
|
||||
Money amountToProcess = null;
|
||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||
newLoanTransaction.resetDerivedComponents();
|
||||
processTransaction(newLoanTransaction, currency, installments, amountToProcess);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
-3
@@ -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<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
|
||||
|
||||
Map<LocalDate, Money> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<RecalculationDetail> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment,
|
||||
Map<LocalDate, LocalDate> recalculationDates);
|
||||
Map<LocalDate, LocalDate> recalculationDates, LoanTransaction preCloseTransaction);
|
||||
|
||||
/**
|
||||
* Used in interest recalculation to introduce new interest only installment.
|
||||
*/
|
||||
boolean isInterestFirstRepaymentScheduleTransactionProcessor();
|
||||
|
||||
void applyTransaction(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments);
|
||||
|
||||
}
|
||||
+5
@@ -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;
|
||||
}
|
||||
}
|
||||
+415
-106
@@ -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<LocalDate, RecalculationDetail> 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<LoanCharge> 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<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments = previousSchedule;
|
||||
List<LoanRepaymentScheduleInstallment> 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<RecalculationDetail> recalculationDetails = new ArrayList<>();
|
||||
final Map<LocalDate, RecalculationDetail> retainRecalculationDetails = new HashMap<>();
|
||||
LocalDate processTransactionsForInterestCompound = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
while (recalculateFrom.isAfter(processTransactionsForInterestCompound) && !transactions.isEmpty()
|
||||
&& loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) {
|
||||
|
||||
int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound);
|
||||
List<LoanTransaction> 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<LoanTransaction> 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<LoanCharge> loanCharges, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
|
||||
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
|
||||
final List<RecalculationDetail> recalculationDetails, final Map<LocalDate, RecalculationDetail> retainRecalculationDetails,
|
||||
boolean recalculate, int installmentNumber, List<LoanTransaction> 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<LoanRepaymentScheduleInstallment> 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<LoanTransaction> 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<LoanCharge> loanCharges, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
|
||||
List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> processTransactions, final int skipRecalculation) {
|
||||
List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> processTransactions, final int skipRecalculation,
|
||||
final List<RecalculationDetail> recalculationDetails, LoanTransaction preCloseTransaction) {
|
||||
int processInstallmentsFrom = 0;
|
||||
Integer numberOfInstallments = previousSchedule.size() - 1;
|
||||
final List<RecalculationDetail> 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<LoanCharge> loanCharges, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> 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<LoanTransaction> transactionsForInstallment = new ArrayList<>();
|
||||
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
|
||||
LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
|
||||
@@ -659,19 +901,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
workingDays));
|
||||
}
|
||||
}
|
||||
Map<LocalDate, Money> 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<RecalculationDetail> earlypaymentDetail = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(
|
||||
transactionsForInstallment, currency, processinstallmets, installment, recalculationDates, preCloseTransaction);
|
||||
|
||||
// this block is to create early payment entries for schedule
|
||||
// generation
|
||||
for (Map.Entry<LocalDate, Money> 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<LoanTransaction> loanTransactions, LocalDate from, LocalDate to,
|
||||
LoanRepaymentScheduleInstallment installment,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency,
|
||||
final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays, final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
final WorkingDays workingDays, final List<LoanRepaymentScheduleInstallment> installments, LoanTransaction preCloseTransaction) {
|
||||
List<LoanTransaction> transactions = new ArrayList<>();
|
||||
Map<LocalDate, LocalDate> 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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments, LocalDate date) {
|
||||
@@ -832,6 +1100,27 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
return installmentNumber;
|
||||
}
|
||||
|
||||
private List<LoanRepaymentScheduleInstallment> getInstallmentsForInterestCompound(
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate date, Collection<LocalDate> processedDates) {
|
||||
List<LoanRepaymentScheduleInstallment> 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<LoanTransaction> processTransactions(final List<LoanTransaction> transactions, final LocalDate tillDate) {
|
||||
List<LoanTransaction> toProcess = new ArrayList<>();
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
@@ -842,4 +1131,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
return toProcess;
|
||||
}
|
||||
|
||||
private LoanTransaction getPreclosureTransaction(List<LoanTransaction> loanTransactions,
|
||||
List<LoanRepaymentScheduleInstallment> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -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,
|
||||
|
||||
+2
-3
@@ -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<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom);
|
||||
|
||||
Money fetchPrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
|
||||
LoanApplicationTerms applicationTerms, MathContext mc);
|
||||
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
|
||||
LoanApplicationTerms applicationTerms, LocalDate onDate);
|
||||
}
|
||||
+5
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
@@ -31,4 +31,6 @@ public interface LoanScheduleModelPeriod {
|
||||
BigDecimal penaltyChargesDue();
|
||||
|
||||
void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge);
|
||||
|
||||
boolean isRecalculatedInterestComponent();
|
||||
}
|
||||
|
||||
+10
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-52
@@ -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 {
|
||||
|
||||
+9
-4
@@ -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<CodeValueData> 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);
|
||||
|
||||
+4
@@ -44,4 +44,8 @@ public enum AmortizationMethod {
|
||||
}
|
||||
return repaymentMethod;
|
||||
}
|
||||
|
||||
public boolean isEqualInstallment() {
|
||||
return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user