Merge pull request #1059 from pramodn02/develop

MIFOSX-1526 : added future instalments calculation
This commit is contained in:
Vishwas Babu A J
2014-09-09 07:30:34 -07:00
12 changed files with 331 additions and 54 deletions
@@ -0,0 +1,14 @@
CREATE TABLE `m_loan_transaction_temp` (
`id` BIGINT(20) NOT NULL,
`loan_id` BIGINT(20) NOT NULL,
`amount` DECIMAL(19,6) NOT NULL DEFAULT '0',
`transaction_date` DATE NOT NULL
);
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;
DROP TABLE `m_loan_transaction_temp`;
@@ -9,7 +9,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -319,8 +318,8 @@ public class LoansApiResource {
if (!associationParameters.isEmpty()) { if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) { if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "charges", "guarantors", "collateral", associationParameters.addAll(Arrays.asList("repaymentSchedule", "futureSchedule", "transactions", "charges", "guarantors",
"notes", "linkedAccount", "multiDisburseDetails")); "collateral", "notes", "linkedAccount", "multiDisburseDetails"));
} }
if (associationParameters.contains("guarantors")) { if (associationParameters.contains("guarantors")) {
@@ -356,6 +355,11 @@ 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);
if (associationParameters.contains("futureSchedule")) {
mandatoryResponseParameters.add("futureSchedule");
this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId);
}
} }
if (associationParameters.contains("charges")) { if (associationParameters.contains("charges")) {
@@ -2095,6 +2095,7 @@ public class Loan extends AbstractPersistable<Long> {
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero); chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(this); chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment); this.loanTransactions.add(chargesPayment);
updateLoanOutstandingBalaces();
} }
} }
@@ -2139,6 +2140,7 @@ public class Loan extends AbstractPersistable<Long> {
chargesPayment.updateComponents(zero, zero, charge.getAmount(getCurrency()), zero); chargesPayment.updateComponents(zero, zero, charge.getAmount(getCurrency()), zero);
chargesPayment.updateLoan(this); chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment); this.loanTransactions.add(chargesPayment);
updateLoanOutstandingBalaces();
charge.markAsFullyPaid(); charge.markAsFullyPaid();
return chargesPayment; return chargesPayment;
} }
@@ -2493,7 +2495,7 @@ public class Loan extends AbstractPersistable<Long> {
return repaymentsOrWaivers; return repaymentsOrWaivers;
} }
private List<LoanTransaction> retreiveListOfTransactionsPostDisbursementExcludeAccruals() { public List<LoanTransaction> retreiveListOfTransactionsPostDisbursementExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>(); final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) { for (final LoanTransaction transaction : this.loanTransactions) {
if (!transaction.isDisbursement() && transaction.isNotReversed() && !transaction.isAccrual()) { if (!transaction.isDisbursement() && transaction.isNotReversed() && !transaction.isAccrual()) {
@@ -4111,14 +4113,54 @@ public class Loan extends AbstractPersistable<Long> {
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays, final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) { final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return; } final LoanScheduleModel loanSchedule = getRecalculatedSchedule(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation,
recalculateFrom);
if (loanSchedule == null) { return; }
updateLoanSchedule(loanSchedule);
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge);
loanCharge.updateWaivedAmount(getCurrency());
} else {
loanCharge.setActive(false);
}
}
}
updateLoanSummaryDerivedFields();
}
private LoanScheduleModel getRecalculatedSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return null; }
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, calendarInstanceForInterestRecalculation);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency,
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays,
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
this.repaymentScheduleInstallments, recalculateFrom);
return loanSchedule;
}
public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
final Integer loanTermFrequency = this.termFrequency; final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final List<DisbursementData> disbursementData = new ArrayList<>(); final List<DisbursementData> disbursementData = new ArrayList<>();
@@ -4149,30 +4191,7 @@ public class Loan extends AbstractPersistable<Long> {
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails, loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails,
calendarInstanceForInterestRecalculation, recalculationFrequencyType); calendarInstanceForInterestRecalculation, recalculationFrequencyType);
return loanApplicationTerms;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency,
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays,
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
this.repaymentScheduleInstallments, recalculateFrom);
if (loanSchedule == null) { return; }
updateLoanSchedule(loanSchedule);
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge);
loanCharge.updateWaivedAmount(getCurrency());
} else {
loanCharge.setActive(false);
}
}
}
updateLoanSummaryDerivedFields();
} }
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) { public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) {
@@ -4182,6 +4201,9 @@ public class Loan extends AbstractPersistable<Long> {
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final Integer loanTermFrequency = this.termFrequency; final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final List<DisbursementData> disbursementData = new ArrayList<>(); final List<DisbursementData> disbursementData = new ArrayList<>();
@@ -4209,8 +4231,9 @@ public class Loan extends AbstractPersistable<Long> {
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(), loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(),
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate()); this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate());
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(),
loanApplicationTerms, LocalDate.now()); LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc);
} else { } else {
installment = this.getTotalOutstandingOnLoan(); installment = this.getTotalOutstandingOnLoan();
} }
@@ -4258,4 +4281,12 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
public LoanTransactionProcessingStrategy transactionProcessingStrategy() {
return this.transactionProcessingStrategy;
}
public boolean isNpa() {
return this.isNpa;
}
} }
@@ -49,6 +49,8 @@ public class LoanScheduleData {
*/ */
private final Collection<LoanSchedulePeriodData> periods; private final Collection<LoanSchedulePeriodData> periods;
private Collection<LoanSchedulePeriodData> futurePeriods;
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays, public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods, final Integer loanTermInDays,
final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalPrincipalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid,
final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged,
@@ -76,4 +78,8 @@ public class LoanScheduleData {
public Collection<LoanSchedulePeriodData> getPeriods() { public Collection<LoanSchedulePeriodData> getPeriods() {
return this.periods; return this.periods;
} }
public void updateFuturePeriods(Collection<LoanSchedulePeriodData> futurePeriods) {
this.futurePeriods = futurePeriods;
}
} }
@@ -414,4 +414,8 @@ public class LoanSchedulePeriodData {
public BigDecimal totalOverdue() { public BigDecimal totalOverdue() {
return defaultToZeroIfNull(this.totalOverdue); return defaultToZeroIfNull(this.totalOverdue);
} }
public BigDecimal principalLoanBalanceOutstanding() {
return this.principalLoanBalanceOutstanding;
}
} }
@@ -241,7 +241,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
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, calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment.getAmount(),
daysInPeriodApplicableForInterest)); daysInPeriodApplicableForInterest));
} }
Money principalForThisPeriod = principalDisbursed.zero(); Money principalForThisPeriod = principalDisbursed.zero();
@@ -481,13 +481,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
daysInPeriodApplicableForInterest, amount); daysInPeriodApplicableForInterest, amount);
int days = Days.daysBetween(startDate, endDate).getDays(); int days = Days.daysBetween(startDate, endDate).getDays();
double interest = calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest.interest(), days); double interest = calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest.interest().getAmount(), days);
return BigDecimal.valueOf(interest); return BigDecimal.valueOf(interest);
} }
private double calculateInterestForDays(int daysInPeriodApplicableForInterest, Money interest, int days) { private double calculateInterestForDays(int daysInPeriodApplicableForInterest, BigDecimal interest, int days) {
return ((interest.getAmount().doubleValue()) / daysInPeriodApplicableForInterest) * days; if (interest.doubleValue() == 0) { return 0; }
return ((interest.doubleValue()) / daysInPeriodApplicableForInterest) * days;
} }
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
@@ -671,7 +672,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanRepaymentScheduleInstallment> installments = previousSchedule; List<LoanRepaymentScheduleInstallment> installments = previousSchedule;
List<LoanRepaymentScheduleInstallment> removeInstallments = new ArrayList<>(); List<LoanRepaymentScheduleInstallment> removeInstallments = new ArrayList<>();
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
LoanTransaction preCloseTransaction = getPreclosureTransaction(transactions, installments, currency, loanApplicationTerms); LoanTransaction preCloseTransaction = getPreclosureTransaction(transactions, installments, currency, loanApplicationTerms, mc);
for (LoanRepaymentScheduleInstallment installment : installments) { for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.isRecalculatedInterestComponent() && recalculateFrom != null if (installment.isRecalculatedInterestComponent() && recalculateFrom != null
&& !recalculateFrom.isAfter(installment.getDueDate())) { && !recalculateFrom.isAfter(installment.getDueDate())) {
@@ -846,7 +847,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (recalculatedSchedule.getLoanScheduleModel() != null) { if (recalculatedSchedule.getLoanScheduleModel() != null) {
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
installments = retrieveRepaymentSchedule(loanScheduleModel); installments = retrieveRepaymentSchedule(loanScheduleModel);
lastInstallmentNumber = installments.size() - 1; lastInstallmentNumber = installments.size();
} }
} }
return loanScheduleModel; return loanScheduleModel;
@@ -963,7 +964,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
} }
if (!diffAmt.isEmpty() && skipRecalculation <= installment.getInstallmentNumber()) { if ((!diffAmt.isEmpty() && skipRecalculation < installment.getInstallmentNumber())
|| skipRecalculation == installment.getInstallmentNumber()) {
processRecalculate = true; processRecalculate = true;
} }
break; break;
@@ -1039,15 +1041,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
*/ */
@Override @Override
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final List<LoanRepaymentScheduleInstallment> installments, public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final List<LoanRepaymentScheduleInstallment> installments,
MonetaryCurrency currency, final LoanApplicationTerms applicationTerms, final LocalDate onDate) { MonetaryCurrency currency, final LocalDate onDate, LocalDate interestChargedFromLocalDate,
Money outstandingPrincipal = Money.zero(currency); LoanApplicationTerms loanApplicationTerms, MathContext mc) {
Money feeCharges = Money.zero(currency); Money feeCharges = Money.zero(currency);
Money penaltyCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency);
Money totalPrincipal = Money.zero(currency); Money totalPrincipal = Money.zero(currency);
Money totalInterest = Money.zero(currency); Money totalInterest = Money.zero(currency);
LocalDate calculateInterestFrom = onDate; LocalDate calculateInterestFrom = onDate;
LocalDate dueDate = onDate; LocalDate dueDate = onDate;
Money inerestForCurrentInstallment = null; LocalDate repaymentDueDate = null;
boolean isInterestRepayment = false;
Money inerestForCurrentInstallment = Money.zero(currency);
Money principalOutstanding = Money.zero(currency);
Integer periodNumber = 0;
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)) {
@@ -1056,7 +1062,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency)); feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency));
penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
} else { } else {
outstandingPrincipal = outstandingPrincipal.plus(currentInstallment.getPrincipal(currency)); principalOutstanding = principalOutstanding.plus(currentInstallment.getPrincipal(currency));
totalPrincipal = totalPrincipal.minus(currentInstallment.getPrincipalCompleted(currency)); totalPrincipal = totalPrincipal.minus(currentInstallment.getPrincipalCompleted(currency));
totalInterest = totalInterest.minus(currentInstallment.getInterestPaid(currency)).minus( totalInterest = totalInterest.minus(currentInstallment.getInterestPaid(currency)).minus(
currentInstallment.getInterestWaived(currency)); currentInstallment.getInterestWaived(currency));
@@ -1064,25 +1070,74 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
calculateInterestFrom = currentInstallment.getFromDate(); calculateInterestFrom = currentInstallment.getFromDate();
dueDate = currentInstallment.getDueDate(); dueDate = currentInstallment.getDueDate();
inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency); inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency);
if (currentInstallment.isRecalculatedInterestComponent()) {
isInterestRepayment = true;
} else {
isInterestRepayment = false;
}
repaymentDueDate = null;
periodNumber = currentInstallment.getInstallmentNumber();
}
if (isInterestRepayment && repaymentDueDate == null && !currentInstallment.isRecalculatedInterestComponent()) {
repaymentDueDate = currentInstallment.getDueDate();
} }
} }
} }
} }
if (applicationTerms.getInterestChargedFromLocalDate() != null if (interestChargedFromLocalDate != null && calculateInterestFrom.isBefore(interestChargedFromLocalDate)) {
&& calculateInterestFrom.isBefore(applicationTerms.getInterestChargedFromLocalDate())) { calculateInterestFrom = interestChargedFromLocalDate;
calculateInterestFrom = applicationTerms.getInterestChargedFromLocalDate();
} }
Money interest = Money.zero(currency); Money interest = Money.zero(currency);
;
if (calculateInterestFrom.isBefore(onDate)) { if (calculateInterestFrom.isBefore(onDate)) {
int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, dueDate).getDays(); boolean useDailyInterest = false;
if (repaymentDueDate == null) {
if (isInterestRepayment) {
useDailyInterest = true;
}
repaymentDueDate = dueDate;
}
int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, repaymentDueDate).getDays();
int days = Days.daysBetween(calculateInterestFrom, onDate).getDays(); int days = Days.daysBetween(calculateInterestFrom, onDate).getDays();
interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, inerestForCurrentInstallment, days)); BigDecimal interestForInstallment = BigDecimal.ZERO;
if (useDailyInterest) {
interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
principalOutstanding, calculateInterestFrom, repaymentDueDate);
} else {
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, true);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator
.idealDisbursementDateBasedOnFirstRepaymentDate(loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
LocalDate periodStartDateApplicableForInterest = calculateInterestFrom;
if (calculateInterestFrom.isBefore(idealDisbursementDate)) {
if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) {
periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate();
} else {
periodStartDateApplicableForInterest = idealDisbursementDate;
}
daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, repaymentDueDate).getDays();
}
Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, repaymentDueDate,
loanApplicationTerms.getInterestChargedFromLocalDate(),
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc,
totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, principalOutstanding);
interestForInstallment = principalInterest.interest().getAmount();
}
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment));
interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days));
} }
totalInterest = totalInterest.plus(interest); totalInterest = totalInterest.plus(interest);
totalPrincipal = totalPrincipal.plus(outstandingPrincipal); totalPrincipal = totalPrincipal.plus(principalOutstanding);
return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(), return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
feeCharges.getAmount(), penaltyCharges.getAmount(), false); feeCharges.getAmount(), penaltyCharges.getAmount(), false);
@@ -1131,7 +1186,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
private LoanTransaction getPreclosureTransaction(List<LoanTransaction> loanTransactions, private LoanTransaction getPreclosureTransaction(List<LoanTransaction> loanTransactions,
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, final LoanApplicationTerms applicationTerms) { List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, final LoanApplicationTerms applicationTerms,
MathContext mc) {
LoanTransaction precloseTransaction = null; LoanTransaction precloseTransaction = null;
for (LoanTransaction loanTransaction : loanTransactions) { for (LoanTransaction loanTransaction : loanTransactions) {
if (precloseTransaction == null || precloseTransaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate())) { if (precloseTransaction == null || precloseTransaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate())) {
@@ -1140,7 +1196,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
if (precloseTransaction != null) { if (precloseTransaction != null) {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency, LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency,
applicationTerms, precloseTransaction.getTransactionDate()); precloseTransaction.getTransactionDate(), applicationTerms.getInterestChargedFromLocalDate(), applicationTerms, mc);
if (precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) { if (precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) {
precloseTransaction = null; precloseTransaction = null;
} }
@@ -31,5 +31,5 @@ public interface LoanScheduleGenerator {
List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom); List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
LoanApplicationTerms applicationTerms, LocalDate onDate); LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc);
} }
@@ -29,6 +29,7 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException; import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException;
@@ -44,13 +45,18 @@ import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException; import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler; import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
@@ -358,6 +364,49 @@ public class LoanScheduleAssembler {
workingDays); workingDays);
} }
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
final List<LoanRepaymentScheduleInstallment> previousSchedule,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom) {
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository
.findOneWithNotFoundDetection(loanApplicationTerms.getCurrency());
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
return loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, previousSchedule,
recalculateFrom);
}
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms,final Long officeId) {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate);
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
}
return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate,
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms, mc);
}
private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) { private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) {
if (!WorkingDaysUtil.isWorkingDay(workingDays, disbursementDate)) { if (!WorkingDaysUtil.isWorkingDay(workingDays, disbursementDate)) {
final String errorMessage = "The expected disbursement date cannot be on a non working day"; final String errorMessage = "The expected disbursement date cannot be on a non working day";
@@ -6,9 +6,12 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service; package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import org.mifosplatform.infrastructure.core.api.JsonQuery; import org.mifosplatform.infrastructure.core.api.JsonQuery;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
public interface LoanScheduleCalculationPlatformService { public interface LoanScheduleCalculationPlatformService {
LoanScheduleModel calculateLoanSchedule(JsonQuery query, Boolean validateParams); LoanScheduleModel calculateLoanSchedule(JsonQuery query, Boolean validateParams);
void updateFutureSchedule(LoanScheduleData loanScheduleData, Long loanId);
} }
@@ -6,17 +6,36 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.service; package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonQuery; import org.mifosplatform.infrastructure.core.api.JsonQuery;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
@@ -35,12 +54,20 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer; private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer; private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer;
private final LoanAssembler loanAssembler;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final LoanAccountDomainService accountDomainService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
@Autowired @Autowired
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer, public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
final LoanScheduleAssembler loanScheduleAssembler, final FromJsonHelper fromJsonHelper, final LoanScheduleAssembler loanScheduleAssembler, final FromJsonHelper fromJsonHelper,
final LoanProductRepository loanProductRepository, final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, final LoanProductRepository loanProductRepository, final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
final LoanReadPlatformService loanReadPlatformService, final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer) { final LoanReadPlatformService loanReadPlatformService, final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer,
final LoanAssembler loanAssembler, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final LoanAccountDomainService accountDomainService, final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanScheduleAssembler = loanScheduleAssembler; this.loanScheduleAssembler = loanScheduleAssembler;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
@@ -48,6 +75,11 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer; this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
this.loanReadPlatformService = loanReadPlatformService; this.loanReadPlatformService = loanReadPlatformService;
this.loanApiJsonDeserializer = loanApiJsonDeserializer; this.loanApiJsonDeserializer = loanApiJsonDeserializer;
this.loanAssembler = loanAssembler;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.accountDomainService = accountDomainService;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
} }
@Override @Override
@@ -83,4 +115,77 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson()); return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
} }
@Override
public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || !loan.status().isActive()
|| !loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { return; }
MonetaryCurrency currency = loan.getCurrency();
Money totalPrincipal = Money.zero(currency);
final List<LoanSchedulePeriodData> futureInstallments = new ArrayList<>();
for (final LoanRepaymentScheduleInstallment currentInstallment : loan.fetchRepaymentScheduleInstallments()) {
if (currentInstallment.isNotFullyPaidOff()) {
if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) {
totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
}
}
}
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
LocalDate calculatedRepaymentsStartingFromDate = accountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(
loan.fetchRepaymentScheduleInstallments(), currency, LocalDate.now(), loanApplicationTerms, loan.getOfficeId());
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
Money interestDue = Money.zero(currency);
if (loanRepaymentScheduleInstallment.isInterestDue(currency)) {
interestDue = loanRepaymentScheduleInstallment.getInterestOutstanding(currency);
totalAmount = totalAmount.plus(interestDue);
}
boolean isNewPaymentRequired = loanRepaymentScheduleInstallment.isInterestDue(currency) || totalPrincipal.isGreaterThanZero();
final List<LoanTransaction> modifiedTransactions = new ArrayList<>();
List<LoanTransaction> transactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals();
for (LoanTransaction loanTransaction : transactions) {
modifiedTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
}
if (isNewPaymentRequired) {
LoanTransaction ondayPaymentTransaction = LoanTransaction.repayment(null, totalAmount, null, LocalDate.now(), null);
modifiedTransactions.add(ondayPaymentTransaction);
}
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor,
LocalDate.now());
LoanScheduleData scheduleDate = model.toData();
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
for (LoanSchedulePeriodData periodData : periodDatas) {
if ((periodData.periodDueDate().isEqual(LocalDate.now()) || periodData.periodDueDate().isAfter(LocalDate.now()))
&& isNewPaymentRequired) {
LoanSchedulePeriodData loanSchedulePeriodData = LoanSchedulePeriodData.repaymentOnlyPeriod(periodData.periodNumber(),
periodData.periodFromDate(), periodData.periodDueDate(), totalPrincipal.getAmount(), periodData
.principalLoanBalanceOutstanding(), interestDue.getAmount(), loanRepaymentScheduleInstallment
.getFeeChargesCharged(currency).getAmount(),
loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount(), totalAmount.getAmount());
futureInstallments.add(loanSchedulePeriodData);
isNewPaymentRequired = false;
} else if (periodData.periodDueDate().isAfter(LocalDate.now())) {
futureInstallments.add(periodData);
}
}
loanScheduleData.updateFuturePeriods(futureInstallments);
}
} }
@@ -614,8 +614,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.loanRepository.save(existingLoanApplication); this.loanRepository.save(existingLoanApplication);
if (productRelatedDetail.isInterestRecalculationEnabled()) { if (productRelatedDetail.isInterestRecalculationEnabled()) {
LocalDate recalculationFrequencyDate = existingLoanApplication.loanProduct().getProductInterestRecalculationDetails() LocalDate recalculationFrequencyDate = existingLoanApplication.loanInterestRecalculationDetails()
.getRestFrequencyLocalDate(); .getRestFrequencyLocalDate();
if (recalculationFrequencyDate == null) {
recalculationFrequencyDate = existingLoanApplication.loanProduct().getProductInterestRecalculationDetails()
.getRestFrequencyLocalDate();
}
if (this.fromJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson())) { if (this.fromJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson())) {
recalculationFrequencyDate = this.fromJsonHelper.extractLocalDateNamed( recalculationFrequencyDate = this.fromJsonHelper.extractLocalDateNamed(
LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson()); LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson());
@@ -358,7 +358,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private void updateRecurringCalendarDatesForInterestRecalculation(final Loan loan) { private void updateRecurringCalendarDatesForInterestRecalculation(final Loan loan) {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& loan.loanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment() && !loan.isDisbursed()) {
final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(), .findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue());