MIFOSX-1517 : corrected recalculation for tranche loans

This commit is contained in:
Pramod Nuthakki
2014-09-12 19:27:06 +05:30
parent ba1373acae
commit 45cd2e7602
9 changed files with 229 additions and 94 deletions
@@ -8,7 +8,7 @@ CREATE TABLE `m_loan_transaction_temp` (
INSERT INTO m_loan_transaction_temp(`id`,`loan_id`,`transaction_date`,`amount`) select lt.id, lt.loan_id,lt.transaction_date,if(lt.transaction_type_enum = 1 , IFNULL(lt.amount,0),IFNULL(-lt.principal_portion_derived,0)) from m_loan_transaction lt where lt.is_reversed=0; INSERT INTO m_loan_transaction_temp(`id`,`loan_id`,`transaction_date`,`amount`) select lt.id, lt.loan_id,lt.transaction_date,if(lt.transaction_type_enum = 1 , IFNULL(lt.amount,0),IFNULL(-lt.principal_portion_derived,0)) from m_loan_transaction lt where lt.is_reversed=0;
UPDATE m_loan_transaction lt SET lt.outstanding_loan_balance_derived = (select sum(ltt.amount) from m_loan_transaction_temp ltt where ltt.id <= lt.id and ltt.transaction_date <= lt.transaction_date and ltt.loan_id = lt.loan_id) where lt.transaction_type_enum != 10 and lt.is_reversed = 0; UPDATE m_loan_transaction lt SET lt.outstanding_loan_balance_derived = (select sum(ltt.amount) from m_loan_transaction_temp ltt where ((ltt.transaction_date = lt.transaction_date and ltt.id <= lt.id) or ltt.transaction_date < lt.transaction_date) and ltt.loan_id = lt.loan_id) where lt.transaction_type_enum != 10 and lt.is_reversed = 0;
DROP TABLE `m_loan_transaction_temp`; DROP TABLE `m_loan_transaction_temp`;
@@ -354,9 +354,9 @@ public class LoansApiResource {
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData(); final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData();
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData, repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData,
disbursementData); disbursementData, loanBasicDetails.isInterestRecalculationEnabled());
if (associationParameters.contains("futureSchedule")) { if (associationParameters.contains("futureSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) {
mandatoryResponseParameters.add("futureSchedule"); mandatoryResponseParameters.add("futureSchedule");
this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId); this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId);
} }
@@ -1817,7 +1817,7 @@ public class Loan extends AbstractPersistable<Long> {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) { && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
@@ -1960,6 +1960,18 @@ public class Loan extends AbstractPersistable<Long> {
return details; return details;
} }
private boolean isDisbursementMissed() {
boolean isDisbursementMissed = false;
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (disbursementDetail.actualDisbursementDate() == null
&& LocalDate.now().isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) {
isDisbursementMissed = true;
break;
}
}
return isDisbursementMissed;
}
private BigDecimal getDisbursedAmount() { private BigDecimal getDisbursedAmount() {
BigDecimal principal = BigDecimal.ZERO; BigDecimal principal = BigDecimal.ZERO;
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
@@ -24,6 +24,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_loan_disbursement_detail") @Table(name = "m_loan_disbursement_detail")
public class LoanDisbursementDetails extends AbstractPersistable<Long> { public class LoanDisbursementDetails extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "loan_id", nullable = false) @JoinColumn(name = "loan_id", nullable = false)
private Loan loan; private Loan loan;
@@ -77,6 +78,14 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
return this.expectedDisbursementDate; return this.expectedDisbursementDate;
} }
public LocalDate expectedDisbursementDateAsLocalDate() {
LocalDate expectedDisburseDate = null;
if (this.expectedDisbursementDate != null) {
expectedDisburseDate = new LocalDate(this.expectedDisbursementDate);
}
return expectedDisburseDate;
}
public Date actualDisbursementDate() { public Date actualDisbursementDate() {
return this.actualDisbursementDate; return this.actualDisbursementDate;
} }
@@ -102,14 +111,10 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
} }
public DisbursementData toData() { public DisbursementData toData() {
LocalDate expectedDisburseDate = null; LocalDate expectedDisburseDate = expectedDisbursementDateAsLocalDate();
LocalDate actualDisburseDate = null; LocalDate actualDisburseDate = null;
if (this.expectedDisbursementDate != null) { if (this.actualDisbursementDate != null) {
expectedDisburseDate = new LocalDate(this.expectedDisbursementDate); actualDisburseDate = new LocalDate(this.actualDisbursementDate);
actualDisburseDate = expectedDisburseDate;
}
if (getDisbursementDate() != null) {
actualDisburseDate = new LocalDate(getDisbursementDate());
} }
return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, this.approvedPrincipal); return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, this.approvedPrincipal);
} }
@@ -158,20 +158,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
if (loanApplicationTerms.isMultiDisburseLoan()) {
loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);
BigDecimal disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, periods,
BigDecimal.ZERO);
principalDisbursed = principalDisbursed.plus(disburseAmt);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt));
outstandingBalance = outstandingBalance.plus(disburseAmt);
if (loanApplicationTerms.getMaxOutstandingBalance() != null
&& outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) {
String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance();
throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance()
.getAmount(), disburseAmt);
}
}
int daysInPeriodApplicableForInterest = daysInPeriod; int daysInPeriodApplicableForInterest = daysInPeriod;
if (periodStartDate.isBefore(idealDisbursementDate)) { if (periodStartDate.isBefore(idealDisbursementDate)) {
@@ -203,8 +189,37 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest,
scheduledDueDateAsPerFrequency, loanApplicationTerms.getInterestChargedFromLocalDate(), scheduledDueDateAsPerFrequency, loanApplicationTerms.getInterestChargedFromLocalDate(),
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
BigDecimal interestToBeAdded = BigDecimal.ZERO;
BigDecimal disburseAmt = BigDecimal.ZERO;
if (loanApplicationTerms.isMultiDisburseLoan()) {
loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);
final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, disbursementDatas,
diffAmt != null);
principalDisbursed = principalDisbursed.plus(disburseAmt);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt));
outstandingBalance = outstandingBalance.plus(disburseAmt);
if (loanApplicationTerms.getMaxOutstandingBalance() != null
&& outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) {
String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance();
throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance()
.getAmount(), disburseAmt);
}
for (DisbursementData disbursementData : disbursementDatas) {
Money disbursedAmt = Money.of(currency, disbursementData.amount());
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
disbursementData.disbursementDate(), disbursedAmt, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);
if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)) {
interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, periodNumber,
totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableInFullInstallment,
interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, disbursementData.disbursementDate(),
scheduledDueDate));
}
}
}
Money balanceForcalculation = outstandingBalance; Money balanceForcalculation = outstandingBalance.minus(disburseAmt);
// reduce principal is the early payment, will processed as per the // reduce principal is the early payment, will processed as per the
// reschedule strategy // reschedule strategy
if (reducePrincipal.isGreaterThanZero()) { if (reducePrincipal.isGreaterThanZero()) {
@@ -249,53 +264,36 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
// update cumulative fields for principal & interest // update cumulative fields for principal & interest
Money interestForThisinstallment = principalInterestForThisPeriod.interest(); Money interestForThisinstallment = principalInterestForThisPeriod.interest();
if (daysCalcForInstallmentNumber == periodNumber) { if (daysCalcForInstallmentNumber == periodNumber) {
interestForThisinstallment = interestForThisinstallment.zero().plus( interestForThisinstallment = interestForThisinstallment.zero().plus(
calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment.getAmount(), calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment.getAmount(),
daysInPeriodApplicableForInterest)); 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(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
Money actualOutstandingbalance = outstandingBalance; if (interestForThisinstallment.isGreaterThanZero()) {
interestForThisinstallment = interestForThisinstallment.plus(interestToBeAdded);
} else if (principalInterestForThisPeriod.interestPaymentDueToGrace().isGreaterThanZero()) {
totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.minus(interestToBeAdded);
}
Money principalForThisPeriod = principalDisbursed.zero();
Money outstandingForThePeriod = outstandingBalance;
Money reducePrincipalForCurrentInstallment = reducePrincipal;
totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment);
Money feeChargesForInstallment = principalDisbursed.zero(); Money feeChargesForInstallment = principalDisbursed.zero();
Money penaltyChargesForInstallment = principalDisbursed.zero(); Money penaltyChargesForInstallment = principalDisbursed.zero();
if (principalForThisPeriod.isGreaterThan(reducePrincipal)) {
principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal);
reducePrincipal = reducePrincipal.zero();
} else {
reducePrincipal = reducePrincipal.minus(principalForThisPeriod);
principalForThisPeriod = principalForThisPeriod.zero();
}
if (periodNumber == 1) {
fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment);
}
Money extraPrincipal = reducePrincipal.zero(); Money extraPrincipal = reducePrincipal.zero();
Money canBeReducedFromReducePrincipal = reducePrincipal.zero();
// this block is to identify interest based on late/early payment // this block is to identify interest based on late/early payment
if (diffAmt != null && !diffAmt.isEmpty()) { if (diffAmt != null && !diffAmt.isEmpty()) {
BigDecimal interestDueToLatePayment = BigDecimal.ZERO; BigDecimal interestDueToLatePayment = BigDecimal.ZERO;
BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO;
boolean isperiodPrincipalReduced = false;
for (RecalculationDetail detail : diffAmt) { for (RecalculationDetail detail : diffAmt) {
if (!detail.isInterestompound()) { if (!detail.isInterestompound()) {
// will increase the principal portion and reduces // will increase the principal portion and reduces
@@ -307,9 +305,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
reducePrincipal = reducePrincipal.plus(detail.getAmount()); reducePrincipal = reducePrincipal.plus(detail.getAmount());
extraPrincipal = extraPrincipal.plus(detail.getAmount()); extraPrincipal = extraPrincipal.plus(detail.getAmount());
int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays();
if (diffDays > 0 && !isperiodPrincipalReduced) { if (diffDays > 0) {
reducePrincipal = reducePrincipal.minus(principalForThisPeriod); canBeReducedFromReducePrincipal = canBeReducedFromReducePrincipal.plus(detail.getAmount());
isperiodPrincipalReduced = true;
} }
LocalDate startDate = detail.getStartDate(); LocalDate startDate = detail.getStartDate();
@@ -363,16 +360,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
} }
if (reducePrincipal.isLessThanZero()) {
reducePrincipal = reducePrincipal.zero();
} else {
Money actualOutstanding = outstandingBalance.minus(reducePrincipal);
principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod);
if (actualOutstanding.isLessThanZero()) {
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
}
}
if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) { if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) {
totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment) totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment)
.minus(interestReducedDueToEarlyPayment); .minus(interestReducedDueToEarlyPayment);
@@ -384,7 +371,60 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
} }
// 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);
principalForThisPeriod = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisPeriod,
totalCumulativePrincipal.plus(principalForThisPeriod), periodNumber);
}
totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod);
// 6. update outstandingLoanBlance using correct 'principalDue'
outstandingBalance = outstandingBalance.minus(principalForThisPeriod);
if (outstandingBalance.isLessThanZero()) {
principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance);
outstandingBalance = outstandingBalance.zero();
}
}
Money actualOutstandingbalance = outstandingBalance;
if (diffAmt != null && !diffAmt.isEmpty()) {
reducePrincipal = reducePrincipal.minus(canBeReducedFromReducePrincipal);
Money pricipalAfterCurrentPrincipal = canBeReducedFromReducePrincipal.minus(principalForThisPeriod);
if (pricipalAfterCurrentPrincipal.isGreaterThanZero()) {
reducePrincipal = reducePrincipal.plus(pricipalAfterCurrentPrincipal);
}
if (reducePrincipal.isLessThanZero()) {
reducePrincipal = reducePrincipal.zero();
} else {
Money actualOutstanding = outstandingBalance.minus(reducePrincipal);
principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod);
if (actualOutstanding.isLessThanZero()) {
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
}
}
}
if (principalForThisPeriod.isGreaterThan(reducePrincipalForCurrentInstallment)) {
principalForThisPeriod = principalForThisPeriod.minus(reducePrincipalForCurrentInstallment);
reducePrincipal = reducePrincipal.minus(reducePrincipalForCurrentInstallment);
} else {
reducePrincipal = reducePrincipal.minus(principalForThisPeriod);
principalForThisPeriod = principalForThisPeriod.zero();
}
if (periodNumber == 1) {
fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment);
}
actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal); actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal);
if (actualOutstandingbalance.isLessThanZero()) {
actualOutstandingbalance = actualOutstandingbalance.zero();
}
// 8. sum up real totalInstallmentDue from components // 8. sum up real totalInstallmentDue from components
final Money totalInstallmentDue = principalForThisPeriod// final Money totalInstallmentDue = principalForThisPeriod//
@@ -415,7 +455,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
instalmentNumber++; instalmentNumber++;
} }
if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse)) { if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse) && diffAmt == null) {
final String errorMsg = "One of the Disbursement date is not falling on Loan Schedule"; final String errorMsg = "One of the Disbursement date is not falling on Loan Schedule";
throw new MultiDisbursementDisbursementDateException(errorMsg); throw new MultiDisbursementDisbursementDateException(errorMsg);
} }
@@ -870,17 +910,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
private BigDecimal disbursementForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate startDate, LocalDate endDate, private BigDecimal disbursementForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate startDate, LocalDate endDate,
final Collection<LoanScheduleModelPeriod> periods, final BigDecimal chargesDueAtTimeOfDisbursement) { final Collection<DisbursementData> disbursementDatas, boolean excludePastUndisbursed) {
BigDecimal principal = BigDecimal.ZERO; BigDecimal principal = BigDecimal.ZERO;
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
if (disbursementData.isDueForDisbursement(startDate, endDate)) { if (!excludePastUndisbursed
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( || (excludePastUndisbursed && (disbursementData.isDisbursed() || !disbursementData.disbursementDate().isBefore(
disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount()), chargesDueAtTimeOfDisbursement); LocalDate.now())))) {
periods.add(disbursementPeriod); if (disbursementData.isDueForDisbursement(startDate, endDate)) {
disbursementDatas.add(disbursementData);
principal = principal.add(disbursementData.amount());
}
}
}
return principal;
}
private BigDecimal disbursementAfterPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate endDate,
LocalDate lastinstallmentDueDate) {
BigDecimal principal = BigDecimal.ZERO;
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
if (disbursementData.disbursementDate().isAfter(endDate) && lastinstallmentDueDate.isAfter(disbursementData.disbursementDate())) {
principal = principal.add(disbursementData.amount()); principal = principal.add(disbursementData.amount());
} }
} }
return principal; return principal;
} }
@@ -1413,6 +1467,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Money inerestForCurrentInstallment = Money.zero(currency); Money inerestForCurrentInstallment = Money.zero(currency);
Money principalOutstanding = Money.zero(currency); Money principalOutstanding = Money.zero(currency);
Integer periodNumber = 0; Integer periodNumber = 0;
LocalDate lastDueDate = loanApplicationTerms.getExpectedDisbursementDate();
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (currentInstallment.isNotFullyPaidOff()) { if (currentInstallment.isNotFullyPaidOff()) {
if (!currentInstallment.getDueDate().isAfter(onDate)) { if (!currentInstallment.getDueDate().isAfter(onDate)) {
@@ -1443,6 +1498,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
} }
if (!currentInstallment.isRecalculatedInterestComponent() && currentInstallment.getDueDate().isAfter(lastDueDate)) {
lastDueDate = currentInstallment.getDueDate();
}
} }
if (interestChargedFromLocalDate != null && calculateInterestFrom.isBefore(interestChargedFromLocalDate)) { if (interestChargedFromLocalDate != null && calculateInterestFrom.isBefore(interestChargedFromLocalDate)) {
calculateInterestFrom = interestChargedFromLocalDate; calculateInterestFrom = interestChargedFromLocalDate;
@@ -1463,6 +1521,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (useDailyInterest) { if (useDailyInterest) {
interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
principalOutstanding, calculateInterestFrom, repaymentDueDate); principalOutstanding, calculateInterestFrom, repaymentDueDate);
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment));
interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days));
} else { } else {
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
@@ -1485,13 +1545,58 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getInterestChargedFromLocalDate(),
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
if (loanApplicationTerms.isMultiDisburseLoan()) {
BigDecimal reducePrincipal = disbursementAfterPeriod(loanApplicationTerms, repaymentDueDate, lastDueDate);
principalOutstanding = principalOutstanding.minus(reducePrincipal);
}
PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod( PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc,
totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, principalOutstanding); totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, principalOutstanding);
interestForInstallment = principalInterest.interest().getAmount(); interestForInstallment = principalInterest.interest().getAmount();
Money alteredPrincipal = principalOutstanding;
Money reducePricipal = principalOutstanding.zero();
BigDecimal interestToBeReduced = BigDecimal.ZERO;
BigDecimal interestForExtraPrincipal = BigDecimal.ZERO;
if (loanApplicationTerms.isMultiDisburseLoan()) {
final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
BigDecimal addedPrincipal = disbursementForPeriod(loanApplicationTerms, calculateInterestFrom, repaymentDueDate,
disbursementDatas, true);
alteredPrincipal = alteredPrincipal.minus(addedPrincipal);
for (DisbursementData disbursementData : disbursementDatas) {
Money disbursedAmt = Money.of(currency, disbursementData.amount());
if (disbursementData.disbursementDate().isAfter(calculateInterestFrom)) {
LocalDate calculateTill = disbursementData.disbursementDate();
interestToBeReduced = interestToBeReduced.add(calculateInterestForSpecificDays(mc, loanApplicationTerms,
periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest,
interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateInterestFrom, calculateTill));
if (onDate.isAfter(calculateTill)) {
interestForExtraPrincipal = interestForExtraPrincipal.add(calculateInterestForSpecificDays(mc,
loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace,
daysInPeriodApplicableForInterest, interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt,
calculateTill, onDate));
} else {
reducePricipal = reducePricipal.plus(disbursedAmt);
}
}
}
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment).plus(interestToBeReduced));
BigDecimal interestForCloseDate = BigDecimal.ZERO;
if (alteredPrincipal.isNotEqualTo(principalOutstanding)) {
interestForCloseDate = interestForExtraPrincipal;
interestForCloseDate = interestForCloseDate.add(calculateInterestForSpecificDays(mc, loanApplicationTerms,
periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest,
interestCalculationGraceOnRepaymentPeriodFraction, alteredPrincipal, calculateInterestFrom, onDate));
} else {
interestForCloseDate = interestForCloseDate.add(BigDecimal.valueOf(calculateInterestForDays(
daysInPeriodApplicableForInterest, interestForInstallment, days)));
}
interest = interest.plus(interestForCloseDate);
principalOutstanding = principalOutstanding.minus(reducePricipal);
}
} }
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment));
interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days));
} }
@@ -397,7 +397,7 @@ public class LoanScheduleAssembler {
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate); LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1));
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled, nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled,
holidays, workingDays); holidays, workingDays);
@@ -26,7 +26,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveOne(Long loanId); LoanAccountData retrieveOne(Long loanId);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData, LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData,
Collection<DisbursementData> disbursementData); Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId); Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
@@ -177,13 +177,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override @Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) { final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
boolean isInterestRecalculationEnabled) {
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor( final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData); repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment"; final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId }); return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId });
@@ -862,15 +863,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final Collection<DisbursementData> disbursementData; private final Collection<DisbursementData> disbursementData;
private LocalDate lastDueDate; private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance; private BigDecimal outstandingLoanPrincipalBalance;
private boolean excludePastUndisbursed;
public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData) { Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency(); this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate(); this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.outstandingLoanPrincipalBalance = this.disbursement.amount();
this.disbursementData = disbursementData; this.disbursementData = disbursementData;
this.excludePastUndisbursed = isInterestRecalculationEnabled;
} }
public String schema() { public String schema() {
@@ -899,6 +902,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
periods.add(disbursementPeriod); periods.add(disbursementPeriod);
totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount(); totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
} else { } else {
if (!this.disbursement.isDisbursed()) {
excludePastUndisbursed = false;
}
this.outstandingLoanPrincipalBalance = BigDecimal.ZERO; this.outstandingLoanPrincipalBalance = BigDecimal.ZERO;
} }
@@ -930,7 +936,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate"); final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate"); final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
final boolean complete = rs.getBoolean("complete"); final boolean complete = rs.getBoolean("complete");
if (disbursementData != null) { if (disbursementData != null) {
BigDecimal principal = BigDecimal.ZERO; BigDecimal principal = BigDecimal.ZERO;
for (DisbursementData data : disbursementData) { for (DisbursementData data : disbursementData) {
@@ -939,14 +944,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed()); data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
periods.add(periodData); periods.add(periodData);
} else if (data.isDueForDisbursement(fromDate, dueDate)) { this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
principal = principal.add(data.amount()); } else if (data.isDueForDisbursement(fromDate, dueDate)
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( && this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) == 1) {
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); if (!excludePastUndisbursed
periods.add(periodData); || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) {
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
}
} }
} }
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(principal);
totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal); totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
} }
@@ -1588,15 +1598,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
StringBuilder sqlBuilder = new StringBuilder(); StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT ml.id FROM m_loan ml "); sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null ");
sqlBuilder.append(" WHERE ml.loan_status_id = ? "); sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 "); sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 ");
sqlBuilder.append(" and ml.is_npa = 0 "); sqlBuilder.append(" and ml.is_npa = 0 ");
sqlBuilder.append(" and mr.completed_derived is false "); sqlBuilder.append(" and ((");
sqlBuilder.append(" and mr.duedate < ? "); sqlBuilder.append(" mr.completed_derived is false ");
sqlBuilder.append(" and mr.duedate < ? )");
sqlBuilder.append(" or dd.expected_disburse_date < ? ) ");
sqlBuilder.append(" group by ml.id"); sqlBuilder.append(" group by ml.id");
try { try {
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class,
new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()) }); new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()),formatter.print(LocalDate.now()) });
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
return null; return null;
} }
@@ -46,6 +46,6 @@ public enum AmortizationMethod {
} }
public boolean isEqualInstallment() { public boolean isEqualInstallment() {
return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS); return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS.getValue());
} }
} }