Merge pull request #1059 from pramodn02/develop
MIFOSX-1526 : added future instalments calculation
This commit is contained in:
+14
@@ -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`;
|
||||||
|
|
||||||
+7
-3
@@ -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")) {
|
||||||
|
|||||||
+58
-27
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+6
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+4
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+76
-20
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
}
|
}
|
||||||
+49
@@ -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";
|
||||||
|
|||||||
+3
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-1
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+5
-1
@@ -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());
|
||||||
|
|||||||
+2
-1
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user