Merge pull request #1074 from pramodn02/develop

Develop
This commit is contained in:
Vishwas Babu A J
2014-09-17 22:28:45 -07:00
13 changed files with 251 additions and 150 deletions
+76 -43
View File
@@ -6839,18 +6839,10 @@ An enumeration that indicates the type of transaction processing strategy to be
</td> </td>
</tr> </tr>
<tr class=alt> <tr class=alt>
<td>recalculationFrequencyType</td> <td>recalculationRestFrequencyDate</td>
</tr> </tr>
<tr> <tr>
<td class=fielddesc>Frequency at which Outstanding Principal is recalculated. This field is mandatory when interest interest recalculation is enabled for product. <td class=fielddesc>Specifies rest frequency start date for interest recalculation. This date must be before or equal to disbursement date
<br>Available calculation types:
<ol>
<li>1 = Same as repayment period</li>
<li>2 = Daily</li>
<li>3 = Weekly</li>
<li>4 = Fortnightly</li>
<li>5 = Monthly</li>
</ol>
</td> </td>
</tr> </tr>
</table> </table>
@@ -7024,7 +7016,12 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&
"id": 2, "id": 2,
"code": "loanRescheduleStrategyMethod.reduce.number.of.installments", "code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
"value": "Reduce number of installments" "value": "Reduce number of installments"
} },
"recalculationRestFrequencyType": {
"id":1,
"code":"interestRecalculationFrequencyType.same.as.repayment.period",
"value":"Same as repayment period"
}
} }
"charges": [], "charges": [],
"productOptions": [ "productOptions": [
@@ -7215,32 +7212,7 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&
"fieldOfficerId":0, "fieldOfficerId":0,
"currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"} "currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"}
} }
], ]
"interestRecalculationFrequencyTypeOptions": [{
"id": 1,
"code": "interestRecalculationFrequencyType.same.as.repayment.period",
"value": "Same as repayment period"
},
{
"id": 2,
"code": "interestRecalculationFrequencyType.daily",
"value": "Daily"
},
{
"id": 3,
"code": "interestRecalculationFrequencyType.weekly",
"value": "Weekly"
},
{
"id": 4,
"code": "interestRecalculationFrequencyType.fortnightly",
"value": "Fortnightly"
},
{
"id": 5,
"code": "interestRecalculationFrequencyType.monthly",
"value": "Monthly"
}],
} }
</code> </code>
</div> </div>
@@ -7271,6 +7243,12 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&
<dd> <dd>
<b>'repaymentSchedule':</b> Loan schedule data. <b>'repaymentSchedule':</b> Loan schedule data.
</dd> </dd>
<dd>
<b>'originalSchedule':</b> Loan schedule data without interest recalculations.
</dd>
<dd>
<b>'futureSchedule':</b> Loan schedule data from today date(will be displayed only for interest first repayment strategy processors)
</dd>
<dd> <dd>
<b>'transactions':</b> Loan transactions data. <b>'transactions':</b> Loan transactions data.
</dd> </dd>
@@ -7980,10 +7958,10 @@ Request Body:
<br/> <br/>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled for product</div></td> <td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled for product and Rest frequency not same as repayment period</div></td>
</tr> </tr>
<tr class=alt> <tr class=alt>
<td>recalculationFrequencyType</td> <td>recalculationRestFrequencyDate</td>
</tr> </tr>
</table> </table>
<br/> <br/>
@@ -8027,7 +8005,6 @@ Request Body:
"submittedOnDate": "10 Jun 2013", "submittedOnDate": "10 Jun 2013",
"linkAccountId" : "1", "linkAccountId" : "1",
"fixedEmiAmount":1100, "fixedEmiAmount":1100,
"recalculationFrequencyType":"1",
"maxOutstandingLoanBalance":"35000", "maxOutstandingLoanBalance":"35000",
"disbursementData":[{"expectedDisbursementDate":"01 November 2013", "disbursementData":[{"expectedDisbursementDate":"01 November 2013",
"principal":22000,"approvedPrincipal":22000}] "principal":22000,"approvedPrincipal":22000}]
@@ -10874,6 +10851,25 @@ An enumeration that indicates the type of transaction processing strategy to be
<td class=fielddesc>Specifies what action should perform on loan repayment schedule for advance payments. <br> <td class=fielddesc>Specifies what action should perform on loan repayment schedule for advance payments. <br>
<span>Example Values:</span>1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount</td> <span>Example Values:</span>1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount</td>
</tr> </tr>
<tr class=alt>
<td>recalculationRestFrequencyType</td>
</tr>
<tr>
<td class=fielddesc>Specifies effective date from which the late or advanced payment amounts will be considered in recalculation.<br>
<span>Example Values:</span>1=Same as repayment period, 2=Daily, 3=Weekly, 4=Monthly</td>
</tr>
<tr class=alt>
<td>recalculationRestFrequencyInterval</td>
</tr>
<tr>
<td class=fielddesc>Specifies rest frequency interval for interest recalculation.<br>
</tr>
<tr class=alt>
<td>recalculationRestFrequencyDate</td>
</tr>
<tr>
<td class=fielddesc>Specifies rest frequency start date for interest recalculation.<br>
</tr>
</table> </table>
</div> </div>
</div> </div>
@@ -10983,7 +10979,12 @@ GET https://DomainName/api/v1/loanproducts
"id": 2, "id": 2,
"code": "loanRescheduleStrategyMethod.reduce.number.of.installments", "code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
"value": "Reduce number of installments" "value": "Reduce number of installments"
} },
"recalculationRestFrequencyType": {
"id":1,
"code":"interestRecalculationFrequencyType.same.as.repayment.period",
"value":"Same as repayment period"
}
}, },
"accountingRule": { "accountingRule": {
"id": 2, "id": 2,
@@ -11831,7 +11832,28 @@ GET https://DomainName/api/v1/loanproducts/template
"id": 1, "id": 1,
"code": "loanRescheduleStrategyMethod.reschedule.next.repayments", "code": "loanRescheduleStrategyMethod.reschedule.next.repayments",
"value": "Reschedule next repayments" "value": "Reschedule next repayments"
}] }],
"interestRecalculationFrequencyTypeOptions": [
{
"id":1,
"code":"interestRecalculationFrequencyType.same.as.repayment.period",
"value":"Same as repayment period"
},
{
"id":2,
"code":"interestRecalculationFrequencyType.daily",
"value":"Daily"
},
{
"id":3,
"code":"interestRecalculationFrequencyType.weekly",
"value":"Weekly"
},
{
"id":4,
"code":"interestRecalculationFrequencyType.monthly",
"value":"Monthly"
}]
} }
</code> </code>
</div> </div>
@@ -12120,7 +12142,15 @@ GET https://DomainName/api/v1/loanproducts/{productId}
<td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled(true)</div></td> <td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled(true)</div></td>
</tr> </tr>
<tr class=alt> <tr class=alt>
<td>interestRecalculationCompoundingMethod,rescheduleStrategyMethod</td> <td>interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType</td>
</tr>
</table>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Additional Optional Fields if interest recalculation is enabled(true) and recalculationRestFrequencyType is not same as repayment period</div></td>
</tr>
<tr class=alt>
<td>recalculationRestFrequencyInterval, recalculationRestFrequencyDate</td>
</tr> </tr>
</table> </table>
</div> </div>
@@ -12219,6 +12249,7 @@ Request Body:
"isInterestRecalculationEnabled":"true", "isInterestRecalculationEnabled":"true",
"interestRecalculationCompoundingMethod":"0", "interestRecalculationCompoundingMethod":"0",
"rescheduleStrategyMethod":"3", "rescheduleStrategyMethod":"3",
"recalculationRestFrequencyType":"1",
"accountingRule":"2", "accountingRule":"2",
"fundSourceAccountId":"4", "fundSourceAccountId":"4",
"loanPortfolioAccountId":"8", "loanPortfolioAccountId":"8",
@@ -12341,6 +12372,7 @@ Request Body:
"isInterestRecalculationEnabled":"true", "isInterestRecalculationEnabled":"true",
"interestRecalculationCompoundingMethod":"0", "interestRecalculationCompoundingMethod":"0",
"rescheduleStrategyMethod":"3", "rescheduleStrategyMethod":"3",
"recalculationRestFrequencyType":"1"
} }
</code> </code>
<code class="method-response"> <code class="method-response">
@@ -12351,6 +12383,7 @@ Request Body:
"isInterestRecalculationEnabled":"true", "isInterestRecalculationEnabled":"true",
"interestRecalculationCompoundingMethod":"0", "interestRecalculationCompoundingMethod":"0",
"rescheduleStrategyMethod":"3", "rescheduleStrategyMethod":"3",
"recalculationRestFrequencyType":"1"
} }
} }
</code> </code>
@@ -365,8 +365,7 @@ public class LoansApiResource {
this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId); this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId);
} }
if (associationParameters.contains("originalSchedule") && loanBasicDetails.isInterestRecalculationEnabled() if (associationParameters.contains("originalSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) {
&& loanBasicDetails.isInterestRecalculationEnabled()) {
mandatoryResponseParameters.add("originalSchedule"); mandatoryResponseParameters.add("originalSchedule");
LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService.retrieveRepaymentArchiveSchedule( LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService.retrieveRepaymentArchiveSchedule(
loanId, repaymentScheduleRelatedData, disbursementData); loanId, repaymentScheduleRelatedData, disbursementData);
@@ -1820,24 +1820,11 @@ public class Loan extends AbstractPersistable<Long> {
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
boolean isEmiAmountChanged = false;
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
actualDisbursementDate.toDate(), emiAmount, this);
this.loanTermVariations.add(loanVariationTerms);
isEmiAmountChanged = true;
}
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) { && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule
|| isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
} }
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1863,6 +1850,24 @@ public class Loan extends AbstractPersistable<Long> {
} }
public void regenerateScheduleOnDisbursement(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean recalculateSchedule,
final LocalDate actualDisbursementDate, BigDecimal emiAmount) {
boolean isEmiAmountChanged = false;
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
actualDisbursementDate.toDate(), emiAmount, this);
this.loanTermVariations.add(loanVariationTerms);
isEmiAmountChanged = true;
}
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
}
public boolean canDisburse(final LocalDate actualDisbursementDate) { public boolean canDisburse(final LocalDate actualDisbursementDate) {
Date lastDusburseDate = this.actualDisbursementDate; Date lastDusburseDate = this.actualDisbursementDate;
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
@@ -1991,7 +1996,7 @@ public class Loan extends AbstractPersistable<Long> {
return isDisbursementMissed; return isDisbursementMissed;
} }
private BigDecimal getDisbursedAmount() { public BigDecimal getDisbursedAmount() {
BigDecimal principal = BigDecimal.ZERO; BigDecimal principal = BigDecimal.ZERO;
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (disbursementDetail.actualDisbursementDate() != null) { if (disbursementDetail.actualDisbursementDate() != null) {
@@ -4282,7 +4287,7 @@ public class Loan extends AbstractPersistable<Long> {
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate()); this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate());
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(),
LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc); LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc, charges());
} else { } else {
installment = this.getTotalOutstandingOnLoan(); installment = this.getTotalOutstandingOnLoan();
} }
@@ -436,7 +436,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency); Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
final Money feesDue = getPenaltyChargesOutstanding(currency); final Money feesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount(); this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue); waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue);
@@ -301,4 +301,9 @@ public final class LoanSummary {
public BigDecimal getTotalPenaltyChargesCharged() { public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged; return this.totalPenaltyChargesCharged;
} }
public BigDecimal getTotalPrincipalWrittenOff() {
return this.totalPrincipalWrittenOff;
}
} }
@@ -123,7 +123,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
transactionstoBeProcessed.add(loanTransaction); transactionstoBeProcessed.add(loanTransaction);
} }
} }
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) { for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
@@ -451,11 +451,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
transactionDate = recalculationDates.get(transactionDate); transactionDate = recalculationDates.get(transactionDate);
} }
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
newLoanTransaction.resetDerivedComponents(); if (newLoanTransaction.isRepayment() || newLoanTransaction.isInterestWaiver() || newLoanTransaction.isRecoveryRepayment()) {
newLoanTransaction.resetDerivedComponents();
}
Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); Money unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess);
if (transactionDate.isAfter(installment.getFromDate()) && transactionDate.isBefore(installment.getDueDate())) { if (transactionDate.isAfter(installment.getFromDate()) && transactionDate.isBefore(installment.getDueDate())) {
Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency)); Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency));
if (earlyPayment.isGreaterThanZero()) { if (earlyPayment.isGreaterThanZero() && (newLoanTransaction.isRepayment() || newLoanTransaction.isInterestWaiver() || newLoanTransaction.isRecoveryRepayment())) {
earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, earlyPayment, false)); earlypaymentDetail.add(new RecalculationDetail(false, transactionDate, null, earlyPayment, false));
} }
currentInstallmentOutstanding = installment.getTotalOutstanding(currency); currentInstallmentOutstanding = installment.getTotalOutstanding(currency);
@@ -53,12 +53,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) { final List<Holiday> holidays, final WorkingDays workingDays) {
final List<RecalculationDetail> diffAmt = null; final List<RecalculationDetail> diffAmt = null;
return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt); final LocalDate prepayDate = null;
return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt,
prepayDate);
} }
private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, List<RecalculationDetail> diffAmt) { final List<Holiday> holidays, final WorkingDays workingDays, List<RecalculationDetail> diffAmt, LocalDate prepayDate) {
// 1. generate list of proposed schedule due dates // 1. generate list of proposed schedule due dates
final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
@@ -191,8 +193,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (loanApplicationTerms.isMultiDisburseLoan()) { if (loanApplicationTerms.isMultiDisburseLoan()) {
loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate); loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);
final Collection<DisbursementData> disbursementDatas = new ArrayList<>(); final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, disbursementDatas, LocalDate tillDate = scheduledDueDate;
diffAmt != null); if (prepayDate != null && prepayDate.isBefore(tillDate)) {
tillDate = prepayDate;
}
disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, tillDate, disbursementDatas, diffAmt != null);
principalDisbursed = principalDisbursed.plus(disburseAmt); principalDisbursed = principalDisbursed.plus(disburseAmt);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt)); loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt));
outstandingBalance = outstandingBalance.plus(disburseAmt); outstandingBalance = outstandingBalance.plus(disburseAmt);
@@ -207,11 +212,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
disbursementData.disbursementDate(), disbursedAmt, chargesDueAtTimeOfDisbursement); disbursementData.disbursementDate(), disbursedAmt, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod); periods.add(disbursementPeriod);
if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)) {
if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)
&& (prepayDate == null || prepayDate.isAfter(disbursementData.disbursementDate()) || disbursementData
.isDisbursed())) {
interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, periodNumber, interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, periodNumber,
totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableInFullInstallment, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableInFullInstallment,
interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, disbursementData.disbursementDate(), interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, disbursementData.disbursementDate(),
scheduledDueDate)); tillDate));
} }
} }
} }
@@ -224,14 +232,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
case REDUCE_EMI_AMOUNT: case REDUCE_EMI_AMOUNT:
loanApplicationTerms.setFixedEmiAmount(null); loanApplicationTerms.setFixedEmiAmount(null);
outstandingBalance = outstandingBalance.minus(reducePrincipal); outstandingBalance = outstandingBalance.minus(reducePrincipal);
balanceForcalculation = outstandingBalance; balanceForcalculation = balanceForcalculation.minus(reducePrincipal);
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
reducePrincipal = reducePrincipal.zero(); reducePrincipal = reducePrincipal.zero();
break; break;
case REDUCE_NUMBER_OF_INSTALLMENTS: case REDUCE_NUMBER_OF_INSTALLMENTS:
loanApplicationTerms.setFixedEmiAmount(fixedEmiAmount.getAmount()); loanApplicationTerms.setFixedEmiAmount(fixedEmiAmount.getAmount());
outstandingBalance = outstandingBalance.minus(reducePrincipal); outstandingBalance = outstandingBalance.minus(reducePrincipal);
balanceForcalculation = outstandingBalance; balanceForcalculation = balanceForcalculation.minus(reducePrincipal);
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
reducePrincipal = reducePrincipal.zero(); reducePrincipal = reducePrincipal.zero();
break; break;
@@ -1022,11 +1030,15 @@ 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, mc); LoanTransaction preCloseTransaction = getPreclosureTransaction(transactions, installments, currency, loanApplicationTerms, mc,
loanCharges);
Integer updatedInstallmentNumber = 1;
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())) {
removeInstallments.add(installment); removeInstallments.add(installment);
} else {
installment.updateInstallmentNumber(updatedInstallmentNumber++);
} }
} }
installments.removeAll(removeInstallments); installments.removeAll(removeInstallments);
@@ -1097,12 +1109,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final List<RecalculationDetail> recalculationDetails, final Map<LocalDate, RecalculationDetail> retainRecalculationDetails, final List<RecalculationDetail> recalculationDetails, final Map<LocalDate, RecalculationDetail> retainRecalculationDetails,
boolean recalculate, int installmentNumber, List<LoanTransaction> processTransactions, LoanTransaction preCloseTransaction) { boolean recalculate, int installmentNumber, List<LoanTransaction> processTransactions, LoanTransaction preCloseTransaction) {
LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1); LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1);
if (loanTransaction == preCloseTransaction) { if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) {
processTransactions.add(loanTransaction); processTransactions.add(loanTransaction);
return loanScheduleModel; return loanScheduleModel;
} }
LocalDate processTransactionsForInterestCompound = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), LocalDate processTransactionsForInterestCompound = loanTransaction.getTransactionDate();
loanApplicationTerms, isHolidayEnabled, holidays, workingDays); if (loanTransaction != preCloseTransaction) {
processTransactionsForInterestCompound = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1),
loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
}
List<LoanRepaymentScheduleInstallment> scheduleInstallments = getInstallmentsForInterestCompound(installments, List<LoanRepaymentScheduleInstallment> scheduleInstallments = getInstallmentsForInterestCompound(installments,
processTransactionsForInterestCompound, retainRecalculationDetails.keySet()); processTransactionsForInterestCompound, retainRecalculationDetails.keySet());
@@ -1214,7 +1229,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions, final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> recalculationDetails, final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> recalculationDetails,
final int skipRecalculation, LoanTransaction preCloseTransaction) { final int skipRecalculation, final LoanTransaction preCloseTransaction) {
boolean processRecalculate = false; boolean processRecalculate = false;
int processedInstallmentNumber = installmentNumber; int processedInstallmentNumber = installmentNumber;
@@ -1324,8 +1339,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LoanScheduleModel model = null; LoanScheduleModel model = null;
recalculationDetails.addAll(diffAmt); recalculationDetails.addAll(diffAmt);
if (processRecalculate) { if (processRecalculate) {
LocalDate prepayDate = null;
if (preCloseTransaction != null) {
prepayDate = preCloseTransaction.getTransactionDate();
}
model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays,
recalculationDetails); recalculationDetails, prepayDate);
} }
return new RecalculatedSchedule(model, processedInstallmentNumber); return new RecalculatedSchedule(model, processedInstallmentNumber);
} }
@@ -1392,7 +1411,7 @@ 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 LocalDate onDate, LocalDate interestChargedFromLocalDate, MonetaryCurrency currency, final LocalDate onDate, LocalDate interestChargedFromLocalDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc) { LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges) {
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);
@@ -1400,12 +1419,21 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate calculateInterestFrom = onDate; LocalDate calculateInterestFrom = onDate;
LocalDate dueDate = onDate; LocalDate dueDate = onDate;
LocalDate repaymentDueDate = null; LocalDate repaymentDueDate = null;
LocalDate periodStartDate = onDate;
boolean isInterestRepayment = false; boolean isInterestRepayment = false;
Money inerestForCurrentInstallment = Money.zero(currency); Money inerestForCurrentInstallment = Money.zero(currency);
Money principalOutstanding = Money.zero(currency); Money principalOutstanding = Money.zero(currency);
Integer periodNumber = 0; Integer periodNumber = 0;
LocalDate lastDueDate = loanApplicationTerms.getExpectedDisbursementDate(); LocalDate lastDueDate = loanApplicationTerms.getExpectedDisbursementDate();
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) {
calculateInterestFrom = currentInstallment.getFromDate();
if (currentInstallment.isRecalculatedInterestComponent()) {
isInterestRepayment = true;
} else {
isInterestRepayment = false;
}
}
if (currentInstallment.isNotFullyPaidOff()) { if (currentInstallment.isNotFullyPaidOff()) {
if (!currentInstallment.getDueDate().isAfter(onDate)) { if (!currentInstallment.getDueDate().isAfter(onDate)) {
totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
@@ -1417,8 +1445,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
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));
if (currentInstallment.getFromDate().isBefore(calculateInterestFrom)) { if (currentInstallment.getFromDate().isBefore(periodStartDate)) {
calculateInterestFrom = currentInstallment.getFromDate(); if (!isInterestRepayment) {
calculateInterestFrom = currentInstallment.getFromDate();
}
periodStartDate = currentInstallment.getFromDate();
dueDate = currentInstallment.getDueDate(); dueDate = currentInstallment.getDueDate();
inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency); inerestForCurrentInstallment = currentInstallment.getInterestCharged(currency);
if (currentInstallment.isRecalculatedInterestComponent()) { if (currentInstallment.isRecalculatedInterestComponent()) {
@@ -1443,8 +1474,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
calculateInterestFrom = interestChargedFromLocalDate; calculateInterestFrom = interestChargedFromLocalDate;
} }
Money interest = Money.zero(currency); Money interest = Money.zero(currency);
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
if (calculateInterestFrom.isBefore(onDate)) { loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, true);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
if (calculateInterestFrom.isBefore(onDate) && !periodStartDate.isBefore(idealDisbursementDate)) {
boolean useDailyInterest = false; boolean useDailyInterest = false;
if (repaymentDueDate == null) { if (repaymentDueDate == null) {
if (isInterestRepayment) { if (isInterestRepayment) {
@@ -1453,7 +1487,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
repaymentDueDate = dueDate; repaymentDueDate = dueDate;
} }
int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, repaymentDueDate).getDays(); int daysInPeriodApplicableForInterest = Days.daysBetween(calculateInterestFrom, repaymentDueDate).getDays();
int days = Days.daysBetween(calculateInterestFrom, onDate).getDays(); int days = Days.daysBetween(periodStartDate, onDate).getDays();
int actualPeriodDays = Days.daysBetween(periodStartDate, repaymentDueDate).getDays();
BigDecimal interestForInstallment = BigDecimal.ZERO; BigDecimal interestForInstallment = BigDecimal.ZERO;
if (useDailyInterest) { if (useDailyInterest) {
interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
@@ -1462,11 +1497,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days)); interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days));
} else { } else {
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, true);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator
.idealDisbursementDateBasedOnFirstRepaymentDate(loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
LocalDate periodStartDateApplicableForInterest = calculateInterestFrom; LocalDate periodStartDateApplicableForInterest = calculateInterestFrom;
if (calculateInterestFrom.isBefore(idealDisbursementDate)) { if (calculateInterestFrom.isBefore(idealDisbursementDate)) {
if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) {
@@ -1481,58 +1512,48 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, repaymentDueDate, .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, repaymentDueDate,
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getInterestChargedFromLocalDate(),
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery());
Money calculateInterestOnPrincipal = principalOutstanding;
final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
if (loanApplicationTerms.isMultiDisburseLoan()) { if (loanApplicationTerms.isMultiDisburseLoan()) {
BigDecimal reducePrincipal = disbursementAfterPeriod(loanApplicationTerms, repaymentDueDate, lastDueDate); BigDecimal reducePrincipal = disbursementAfterPeriod(loanApplicationTerms, repaymentDueDate, lastDueDate);
principalOutstanding = principalOutstanding.minus(reducePrincipal); principalOutstanding = principalOutstanding.minus(reducePrincipal);
calculateInterestOnPrincipal = principalOutstanding;
BigDecimal currentPeriodDisbursal = disbursementForPeriod(loanApplicationTerms, periodStartDate, repaymentDueDate,
disbursementDatas, true);
calculateInterestOnPrincipal = calculateInterestOnPrincipal.minus(currentPeriodDisbursal);
} }
PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod( PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc,
totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, principalOutstanding); totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, calculateInterestOnPrincipal);
interestForInstallment = principalInterest.interest().getAmount(); interestForInstallment = BigDecimal.valueOf(calculateInterestForDays(daysInPeriodApplicableForInterest, principalInterest
Money alteredPrincipal = principalOutstanding; .interest().getAmount(), actualPeriodDays));
Money reducePricipal = principalOutstanding.zero(); BigDecimal interestToBeAdded = BigDecimal.ZERO;
BigDecimal interestToBeReduced = BigDecimal.ZERO; BigDecimal interestToBeReduced = BigDecimal.ZERO;
BigDecimal interestForExtraPrincipal = BigDecimal.ZERO;
if (loanApplicationTerms.isMultiDisburseLoan()) { if (loanApplicationTerms.isMultiDisburseLoan()) {
final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
BigDecimal addedPrincipal = disbursementForPeriod(loanApplicationTerms, calculateInterestFrom, repaymentDueDate,
disbursementDatas, true);
alteredPrincipal = alteredPrincipal.minus(addedPrincipal);
for (DisbursementData disbursementData : disbursementDatas) { for (DisbursementData disbursementData : disbursementDatas) {
Money disbursedAmt = Money.of(currency, disbursementData.amount()); Money disbursedAmt = Money.of(currency, disbursementData.amount());
if (disbursementData.disbursementDate().isAfter(calculateInterestFrom)) { LocalDate calculateTill = disbursementData.disbursementDate();
LocalDate calculateTill = disbursementData.disbursementDate(); if (!onDate.isAfter(calculateTill)) {
principalOutstanding = principalOutstanding.minus(disbursedAmt);
} else {
interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms,
periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest,
interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateTill, onDate));
}
if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)) {
interestToBeReduced = interestToBeReduced.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, interestToBeReduced = interestToBeReduced.add(calculateInterestForSpecificDays(mc, loanApplicationTerms,
periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest,
interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateInterestFrom, calculateTill)); interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateTill, repaymentDueDate));
if (onDate.isAfter(calculateTill)) {
interestForExtraPrincipal = interestForExtraPrincipal.add(calculateInterestForSpecificDays(mc,
loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace,
daysInPeriodApplicableForInterest, interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt,
calculateTill, onDate));
} else {
reducePricipal = reducePricipal.plus(disbursedAmt);
}
} }
}
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment).plus(interestToBeReduced));
BigDecimal interestForCloseDate = BigDecimal.ZERO;
if (alteredPrincipal.isNotEqualTo(principalOutstanding)) {
interestForCloseDate = interestForExtraPrincipal;
interestForCloseDate = interestForCloseDate.add(calculateInterestForSpecificDays(mc, loanApplicationTerms,
periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest,
interestCalculationGraceOnRepaymentPeriodFraction, alteredPrincipal, calculateInterestFrom, onDate));
} else {
interestForCloseDate = interestForCloseDate.add(BigDecimal.valueOf(calculateInterestForDays(
daysInPeriodApplicableForInterest, interestForInstallment, days)));
}
interest = interest.plus(interestForCloseDate);
principalOutstanding = principalOutstanding.minus(reducePricipal);
}
}
}
interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment).minus(interestToBeReduced));
BigDecimal interestForCloseDate = BigDecimal.valueOf(calculateInterestForDays(actualPeriodDays, interestForInstallment,
days));
interest = interest.plus(interestForCloseDate).plus(interestToBeAdded);
} }
} }
@@ -1540,6 +1561,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalInterest = totalInterest.plus(interest); totalInterest = totalInterest.plus(interest);
totalPrincipal = totalPrincipal.plus(principalOutstanding); totalPrincipal = totalPrincipal.plus(principalOutstanding);
for (LoanCharge loanCharge : charges) {
if (loanCharge.isActive() && loanCharge.isNotFullyPaid() && !loanCharge.isWaived()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStartDate, onDate)) {
if (loanCharge.isFeeCharge()) {
feeCharges = feeCharges.plus(loanCharge.amountOutstanding());
} else {
penaltyCharges = penaltyCharges.plus(loanCharge.amountOutstanding());
}
}
}
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);
} }
@@ -1588,7 +1620,7 @@ 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) { MathContext mc, final Set<LoanCharge> loanCharges) {
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())) {
@@ -1597,7 +1629,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
if (precloseTransaction != null) { if (precloseTransaction != null) {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency, LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = calculatePrepaymentAmount(installments, currency,
precloseTransaction.getTransactionDate(), applicationTerms.getInterestChargedFromLocalDate(), applicationTerms, mc); precloseTransaction.getTransactionDate(), applicationTerms.getInterestChargedFromLocalDate(), applicationTerms, mc,
loanCharges);
if (precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) { if (precloseTransaction.getAmount(currency).isLessThan(loanRepaymentScheduleInstallment.getTotalOutstanding(currency))) {
precloseTransaction = null; precloseTransaction = null;
} }
@@ -33,7 +33,7 @@ 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,
LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc); LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges);
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
@@ -385,7 +385,8 @@ public class LoanScheduleAssembler {
} }
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms,final Long officeId) { MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Long officeId,
final Set<LoanCharge> loanCharges) {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
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);
@@ -404,7 +405,7 @@ public class LoanScheduleAssembler {
} }
return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate, return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate,
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms, mc); loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms, mc, loanCharges);
} }
private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) { private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) {
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.loanschedule.service; package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -127,6 +128,12 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || !loan.status().isActive() if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || !loan.status().isActive()
|| !loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { return; } || !loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { return; }
if (loan.loanProduct().isMultiDisburseLoan()) {
BigDecimal disbursedAmount = loan.getDisbursedAmount();
BigDecimal principalRepaid = loan.getLoanSummary().getTotalPrincipalRepaid();
BigDecimal principalWrittenOff = loan.getLoanSummary().getTotalPrincipalWrittenOff();
if (disbursedAmount.subtract(principalWrittenOff).subtract(principalRepaid).compareTo(BigDecimal.ZERO) != 1) { return; }
}
MonetaryCurrency currency = loan.getCurrency(); MonetaryCurrency currency = loan.getCurrency();
Money totalPrincipal = Money.zero(currency); Money totalPrincipal = Money.zero(currency);
final List<LoanSchedulePeriodData> futureInstallments = new ArrayList<>(); final List<LoanSchedulePeriodData> futureInstallments = new ArrayList<>();
@@ -147,7 +154,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance); calculatedRepaymentsStartingFromDate, restCalendarInstance);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(
loan.fetchRepaymentScheduleInstallments(), currency, LocalDate.now(), loanApplicationTerms, loan.getOfficeId()); loan.fetchRepaymentScheduleInstallments(), currency, LocalDate.now(), loanApplicationTerms, loan.getOfficeId(),
loan.charges());
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
Money interestDue = Money.zero(currency); Money interestDue = Money.zero(currency);
@@ -156,7 +156,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
} }
int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays(); int daysToBeAccrued = Days.daysBetween(startDate, tilldate).getDays();
// if (daysToBeAccrued < 1) { return; } // if (daysToBeAccrued < 1) { return; }
int daysInSchedule = Days.daysBetween(interestStartDate, tilldate).getDays(); // int daysInSchedule = Days.daysBetween(interestStartDate, tilldate).getDays();
/* /*
* switch (accrualData.getRepaymentFrequency()) { case MONTHS: int * switch (accrualData.getRepaymentFrequency()) { case MONTHS: int
* numberOfDaysInMonth = * numberOfDaysInMonth =
@@ -175,7 +175,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
BigDecimal interestportion = null; BigDecimal interestportion = null;
BigDecimal feeportion = accrualData.getDueDateFeeIncome(); BigDecimal feeportion = accrualData.getDueDateFeeIncome();
BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome(); BigDecimal penaltyportion = accrualData.getDueDatePenaltyIncome();
if (daysInSchedule >= totalNumberOfDays) { if (daysToBeAccrued >= totalNumberOfDays) {
interestportion = accrualData.getInterestIncome(); interestportion = accrualData.getInterestIncome();
} else { } else {
double iterest = interestPerDay * daysToBeAccrued; double iterest = interestPerDay * daysToBeAccrued;
@@ -83,6 +83,7 @@ import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentComman
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -280,7 +281,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
if (canDisburse) { if (canDisburse) {
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal()); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
if (isAccountTransfer) { if (isAccountTransfer) {
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail); disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
@@ -301,9 +302,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
} }
regenerateScheduleOnDisbursement(command, loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays, recalculateSchedule);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
loan, null);
}
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes, changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); allowTransactionsOnNonWorkingDay, recalculateSchedule, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -322,10 +329,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
} }
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
@@ -476,7 +480,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (canDisburse) { if (canDisburse) {
Money amountBeforeAdjust = loan.getPrincpal(); Money amountBeforeAdjust = loan.getPrincpal();
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal()); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
if (isAccountTransfer) { if (isAccountTransfer) {
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail); disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
@@ -496,10 +500,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
} }
regenerateScheduleOnDisbursement(command, loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays,
workingDays, recalculateSchedule);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
loan, null);
}
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes, changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); allowTransactionsOnNonWorkingDay, recalculateSchedule, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
@@ -518,10 +527,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
} }
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
final Set<LoanCharge> loanCharges = loan.charges(); final Set<LoanCharge> loanCharges = loan.charges();
@@ -2315,6 +2320,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean recalculateSchedule) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
loan.regenerateScheduleOnDisbursement(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, recalculateSchedule, actualDisbursementDate, emiAmount);
}
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>(); final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) { for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
@@ -78,12 +78,13 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency); RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency);
Date recurrenceOnDate = null; Date recurrenceOnDate = null;
if (recurrenceOnLocalDate != null) { if (recurrenceOnLocalDate != null) {
if (frequencyType.isSameAsRepayment()) { if (!frequencyType.isSameAsRepayment()) {
recurrenceInterval = 0;
} else {
recurrenceOnDate = recurrenceOnLocalDate.toDate(); recurrenceOnDate = recurrenceOnLocalDate.toDate();
} }
} }
if (frequencyType.isSameAsRepayment()) {
recurrenceInterval = 0;
}
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod, return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
recurrenceFrequency, recurrenceInterval, recurrenceOnDate); recurrenceFrequency, recurrenceInterval, recurrenceOnDate);