FINERACT-202 : enabling the option for multi reshedule

This commit is contained in:
venkatconflux
2016-08-01 17:07:34 +05:30
parent ada9ced9a5
commit fe6171237e
31 changed files with 1496 additions and 1310 deletions
+58 -30
View File
@@ -11877,11 +11877,11 @@ Content-Type: application/json Request Body:
{ {
"loanId": 1, "loanId": 1,
"graceOnPrincipal": 2, "graceOnPrincipal": 2,
"rescheduleFromDate": "04 December 2014", "rescheduleFromDate": "25 December 2013",
"adjustedDueDate": "20 December 2014", "adjustedDueDate": "31 December 2013",
"dateFormat": "dd MMMM yyyy", "dateFormat": "dd MMMM yyyy",
"locale": "en", "locale": "en",
"submittedOnDate": "04 September 2014", "submittedOnDate": "04 September 2013",
"rescheduleReasonComment" : "Client has gone AWOL", "rescheduleReasonComment" : "Client has gone AWOL",
"rescheduleReasonId": 1 "rescheduleReasonId": 1
} }
@@ -11907,52 +11907,80 @@ GET https://DomainName/api/v1/rescheduleloans/{requestId}
</code> </code>
<code class="method-response"> <code class="method-response">
{ {
"id": 2, "id": 1,
"loanId": 17, "loanId": 1,
"clientId": 4, "clientId": 1,
"clientName": "Fernando Michael Torres", "clientName": "test test",
"loanAccountNumber": "000000017", "loanAccountNumber": "000000001",
"statusEnum": { "statusEnum": {
"id": 500, "id": 100,
"code": "loanStatusType.rejected", "code": "loanStatusType.submitted.and.pending.approval",
"value": "Rejected", "value": "Submitted and pending approval",
"pendingApproval": false, "pendingApproval": true,
"approved": false, "approved": false,
"rejected": true "rejected": false
}, },
"rescheduleFromInstallment": 5, "rescheduleFromInstallment": 5,
"graceOnPrincipal": 3,
"rescheduleFromDate": [ "rescheduleFromDate": [
2013, 2013,
12, 12,
6 25
], ],
"recalculateInterest": false, "recalculateInterest": false,
"rescheduleReasonCodeValue": { "rescheduleReasonCodeValue": {
"id": 239, "id": 1,
"name": "client cannot pay on time" "name": "Passport",
"isActive": false
}, },
"timeline": { "timeline": {
"submittedOnDate": [ "submittedOnDate": [
2014, 2013,
8, 9,
4 4
], ],
"submittedByUsername": "mifos", "submittedByUsername": "mifos",
"submittedByFirstname": "App", "submittedByFirstname": "App",
"submittedByLastname": "Administrator", "submittedByLastname": "Administrator"
"rejectedOnDate": [
2014,
8,
5
],
"rejectedByUsername": "mifos",
"rejectedByFirstname": "App",
"rejectedByLastname": "Administrator"
}, },
"rescheduleReasonComment": "" "rescheduleReasonComment": "Client has gone AWOL",
"loanTermVariationsData": [
{
"id": 13,
"termType": {
"id": 4,
"code": "loanTermType.dueDate",
"value": "dueDate"
},
"termVariationApplicableFrom": [
2013,
12,
25
],
"dateValue": [
2013,
12,
31
],
"isSpecificToInstallment": false
},
{
"id": 14,
"termType": {
"id": 8,
"code": "loanTermType.graceOnPrincipal",
"value": "graceOnPrincipal"
},
"termVariationApplicableFrom": [
2013,
12,
25
],
"decimalValue": 2,
"isSpecificToInstallment": false
}
]
} }
</code> </code>
</div> </div>
</div> </div>
<a id="loan_reschedule_preview_retrieve" name="loan_reschedule_preview_retrieve" class="old-syle-anchor">&nbsp;</a> <a id="loan_reschedule_preview_retrieve" name="loan_reschedule_preview_retrieve" class="old-syle-anchor">&nbsp;</a>
@@ -211,7 +211,7 @@ public class LoanRescheduleRequestTest {
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "16", numberOfRepayments.toString()); assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "12", numberOfRepayments.toString());
assertEquals("TOTAL EXPECTED REPAYMENT MUST BE EQUAL TO 118000.0", "118000.0", totalExpectedRepayment.toString()); assertEquals("TOTAL EXPECTED REPAYMENT MUST BE EQUAL TO 118000.0", "118000.0", totalExpectedRepayment.toString());
System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")"); System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
@@ -29,7 +29,7 @@ public class LoanTermVariationsData implements Comparable<LoanTermVariationsData
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Long id; private final Long id;
private final EnumOptionData termType; private final EnumOptionData termType;
private final LocalDate termVariationApplicableFrom; private LocalDate termVariationApplicableFrom;
private final BigDecimal decimalValue; private final BigDecimal decimalValue;
private final LocalDate dateValue; private final LocalDate dateValue;
private final boolean isSpecificToInstallment; private final boolean isSpecificToInstallment;
@@ -114,4 +114,8 @@ public class LoanTermVariationsData implements Comparable<LoanTermVariationsData
return comparsion; return comparsion;
} }
public void setApplicableFromDate(final LocalDate applicableFromDate) {
this.termVariationApplicableFrom = applicableFromDate;
}
} }
@@ -22,37 +22,27 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
public class LoanTermVariationsDataWrapper { public class LoanTermVariationsDataWrapper {
private final List<LoanTermVariationsData> exceptionData; private final List<LoanTermVariationsData> exceptionData;
private final ListIterator<LoanTermVariationsData> iterator; private ListIterator<LoanTermVariationsData> iterator;
private final List<LoanTermVariationsData> interestRateChanges; private final List<LoanTermVariationsData> interestRateChanges;
private final List<LoanTermVariationsData> interestRateFromInstallment;
private final List<LoanTermVariationsData> dueDateVariation; private final List<LoanTermVariationsData> dueDateVariation;
private final ListIterator<LoanTermVariationsData> dueDateIterator; private ListIterator<LoanTermVariationsData> dueDateIterator;
public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) { public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) {
if (exceptionData == null) { if (exceptionData == null) {
this.exceptionData = new ArrayList<>(1); this.exceptionData = new ArrayList<>(1);
} else { } else {
this.exceptionData = exceptionData; this.exceptionData = exceptionData;
Collections.sort(this.exceptionData);
} }
this.interestRateChanges = new ArrayList<>(); this.interestRateChanges = new ArrayList<>();
this.dueDateVariation = new ArrayList<>(); this.dueDateVariation = new ArrayList<>();
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) { this.interestRateFromInstallment = new ArrayList<>();
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) { deriveLoanTermVariations();
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
}
}
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
iterator = this.exceptionData.listIterator();
dueDateIterator = this.dueDateVariation.listIterator();
} }
public boolean hasVariation(final LocalDate date) { public boolean hasVariation(final LocalDate date) {
@@ -85,6 +75,10 @@ public class LoanTermVariationsDataWrapper {
return this.dueDateIterator.next(); return this.dueDateIterator.next();
} }
public LoanTermVariationsData previousDueDateVariation() {
return this.dueDateIterator.previous();
}
public List<LoanTermVariationsData> getInterestRateChanges() { public List<LoanTermVariationsData> getInterestRateChanges() {
return this.interestRateChanges; return this.interestRateChanges;
} }
@@ -97,6 +91,23 @@ public class LoanTermVariationsDataWrapper {
return this.exceptionData; return this.exceptionData;
} }
public void setExceptionData(final List<LoanTermVariationsData> exceptionData) {
clearTerms();
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
public void clearTerms() {
this.exceptionData.clear();
this.interestRateChanges.clear();
this.dueDateVariation.clear();
this.interestRateFromInstallment.clear();
}
public List<LoanTermVariationsData> getInterestRateFromInstallment() {
return this.interestRateFromInstallment;
}
public int adjustNumberOfRepayments() { public int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0; int repaymetsForAdjust = 0;
for (LoanTermVariationsData loanTermVariations : this.exceptionData) { for (LoanTermVariationsData loanTermVariations : this.exceptionData) {
@@ -125,4 +136,39 @@ public class LoanTermVariationsDataWrapper {
return hasNext(date, iterator); return hasNext(date, iterator);
} }
public void updateLoanTermVariationsData(final List<LoanTermVariationsData> exceptionData){
if(this.exceptionData != null && exceptionData != null && exceptionData.size() > 0){
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
}
private void deriveLoanTermVariations() {
Collections.sort(this.exceptionData);
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isInterestRateFromInstallment()) {
this.interestRateFromInstallment.add(loanTermVariationsData);
}
}
Collections.sort(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateFromInstallment);
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
public void resetVariations(){
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
loanTermVariationsData.setProcessed(false);
}
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
} }
@@ -2734,6 +2734,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus);
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus); final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate()); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (!statusEnum.hasStateOf(currentStatus)) { if (!statusEnum.hasStateOf(currentStatus)) {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
@@ -2750,6 +2752,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
updateLoanToPreDisbursalState(); updateLoanToPreDisbursalState();
if (isScheduleRegenerateRequired || isDisbursedAmountChanged || isEmiAmountChanged if (isScheduleRegenerateRequired || isDisbursedAmountChanged || isEmiAmountChanged
|| this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { || this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -2760,14 +2763,14 @@ public class Loan extends AbstractPersistable<Long> {
if (isDisbursedAmountChanged) { if (isDisbursedAmountChanged) {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
}else if(isPeriodicAccrualAccountingEnabledOnLoanProduct()){
for (final LoanRepaymentScheduleInstallment period : getRepaymentScheduleInstallments()) {
period.resetAccrualComponents();
}
} }
actualChanges.put("actualDisbursementDate", ""); actualChanges.put("actualDisbursementDate", "");
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
this.accruedTill = null;
reverseExistingTransactions();
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
@@ -2776,15 +2779,23 @@ public class Loan extends AbstractPersistable<Long> {
} }
private final void reverseExistingTransactions() { private final void reverseExistingTransactions() {
Collection<LoanTransaction> retainTransactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) { for (final LoanTransaction transaction : this.loanTransactions) {
transaction.reverse(); transaction.reverse();
if(transaction.getId() != null){
retainTransactions.add(transaction);
}
} }
this.loanTransactions.retainAll(retainTransactions);
isTransactionsListDirty = true ;
} }
private void updateLoanToPreDisbursalState() { private void updateLoanToPreDisbursalState() {
this.actualDisbursementDate = null; this.actualDisbursementDate = null;
this.accruedTill = null;
reverseExistingTransactions();
for (final LoanCharge charge : charges()) { for (final LoanCharge charge : charges()) {
if (charge.isOverdueInstallmentCharge()) { if (charge.isOverdueInstallmentCharge()) {
charge.setActive(false); charge.setActive(false);
@@ -2796,13 +2807,11 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
currentInstallment.resetDerivedComponents(); currentInstallment.resetDerivedComponents();
} }
final List<LoanTermVariations> removeTerms = new ArrayList<>(this.loanTermVariations.size());
for (LoanTermVariations variations : this.loanTermVariations) { for (LoanTermVariations variations : this.loanTermVariations) {
if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) { if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) {
removeTerms.add(variations); variations.markAsInactive();
} }
} }
this.loanTermVariations.removeAll(removeTerms);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges());
@@ -5024,7 +5033,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
private ChangedTransactionDetail processTransactions() { public ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
@@ -5292,9 +5301,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(), return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(),
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
getRepaymentScheduleInstallments(), generatorDTO.getRecalculateFrom());
} }
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) { public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
@@ -5310,9 +5318,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod); final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(), installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
scheduleGeneratorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
loanRepaymentScheduleTransactionProcessor, getRepaymentScheduleInstallments());
} else { } else {
installment = this.getTotalOutstandingOnLoan(); installment = this.getTotalOutstandingOnLoan();
} }
@@ -5377,10 +5384,12 @@ public class Loan extends AbstractPersistable<Long> {
return loanApplicationTerms; return loanApplicationTerms;
} }
private BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate, public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
List<LoanTermVariationsData> loanTermVariations) { List<LoanTermVariationsData> loanTermVariations) {
for (LoanTermVariations variationTerms : this.loanTermVariations) { for (LoanTermVariations variationTerms : this.loanTermVariations) {
loanTermVariations.add(variationTerms.toData()); if(variationTerms.isActive()) {
loanTermVariations.add(variationTerms.toData());
}
} }
annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
return annualNominalInterestRate; return annualNominalInterestRate;
@@ -6332,4 +6341,16 @@ public class Loan extends AbstractPersistable<Long> {
return isForeClosure; return isForeClosure;
} }
public Set<LoanTermVariations> getActiveLoanTermVariations() {
Set<LoanTermVariations> retData = new HashSet<>();
if(this.loanTermVariations != null && this.loanTermVariations.size() > 0){
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
if(loanTermVariations.isActive()){
retData.add(loanTermVariations);
}
}
}
return retData.size()>0?retData:null;
}
} }
@@ -65,4 +65,6 @@ public interface LoanAccountDomainService {
* @param loan {@link Loan} object * @param loan {@link Loan} object
*/ */
void disableStandingInstructionsLinkedToClosedLoan(Loan loan); void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
} }
@@ -447,15 +447,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
*/ */
@Override @Override
public void recalculateAccruals(Loan loan) { public void recalculateAccruals(Loan loan) {
boolean isInterestCalcualtionHappened = loan.repaymentScheduleDetail().isInterestRecalculationEnabled();
recalculateAccruals(loan, isInterestCalcualtionHappened);
}
@Override
public void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened) {
LocalDate accruedTill = loan.getAccruedTill(); LocalDate accruedTill = loan.getAccruedTill();
if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !isInterestCalcualtionHappened
|| accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
boolean isOrganisationDateEnabled = this.configurationDomainService.isOrganisationstartDateEnabled(); boolean isOrganisationDateEnabled = this.configurationDomainService.isOrganisationstartDateEnabled();
Date organisationStartDate = new Date(); Date organisationStartDate = new Date();
if(isOrganisationDateEnabled){ if(isOrganisationDateEnabled){
organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate(); organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
} }
if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
|| accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>(); Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Long loanId = loan.getId(); Long loanId = loan.getId();
@@ -697,17 +703,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate fromDate, final Loan loan) {
LoanRepaymentScheduleInstallment installment = null;
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
if (fromDate.equals(loanRepaymentScheduleInstallment.getFromDate())) {
installment = loanRepaymentScheduleInstallment;
break;
}
}
return installment;
}
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1); Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity); map.put(entityEvent, entity);
@@ -345,6 +345,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.obligationsMetOnDate = null; this.obligationsMetOnDate = null;
} }
public void resetAccrualComponents() {
this.interestAccrued = null;
this.feeAccrued = null;
this.penaltyAccrued = null;
}
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name="m_loan_reschedule_request_term_variations_mapping")
public class LoanRescheduleRequestToTermVariationMapping extends AbstractPersistable<Long> {
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "loan_term_variations_id", nullable = false)
private LoanTermVariations loanTermVariations;
protected LoanRescheduleRequestToTermVariationMapping(){
}
private LoanRescheduleRequestToTermVariationMapping(final LoanTermVariations loanTermVariations) {
this.loanTermVariations = loanTermVariations;
}
public static LoanRescheduleRequestToTermVariationMapping createNew(final LoanTermVariations loanTermVariation) {
return new LoanRescheduleRequestToTermVariationMapping(loanTermVariation);
}
public LoanTermVariations getLoanTermVariations() {
return this.loanTermVariations;
}
}
@@ -26,7 +26,11 @@ public enum LoanTermVariationType {
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), // PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
DUE_DATE(4, "loanTermType.dueDate"), // DUE_DATE(4, "loanTermType.dueDate"), //
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), // INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"); DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"),
GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"),
GRACE_ON_PRINCIPAL(8, "loanTermType.graceOnPrincipal"),
EXTEND_REPAYMENT_PERIOD(9, "loanTermType.extendRepaymentPeriod"),
INTEREST_RATE_FROM_INSTALLMENT(10, "loanTermType.interestRateFromInstallment");
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -66,6 +70,18 @@ public enum LoanTermVariationType {
case 6: case 6:
enumeration = LoanTermVariationType.DELETE_INSTALLMENT; enumeration = LoanTermVariationType.DELETE_INSTALLMENT;
break; break;
case 7:
enumeration = LoanTermVariationType.GRACE_ON_INTEREST;
break;
case 8:
enumeration = LoanTermVariationType.GRACE_ON_PRINCIPAL;
break;
case 9:
enumeration = LoanTermVariationType.EXTEND_REPAYMENT_PERIOD;
break;
case 10:
enumeration = LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT;
break;
} }
return enumeration; return enumeration;
} }
@@ -93,4 +109,20 @@ public enum LoanTermVariationType {
public boolean isDeleteInstallment() { public boolean isDeleteInstallment() {
return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue()); return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue());
} }
public boolean isGraceOnInterest() {
return this.value.equals(LoanTermVariationType.GRACE_ON_INTEREST.getValue());
}
public boolean isGraceOnPrincipal() {
return this.value.equals(LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue());
}
public boolean isExtendRepaymentPeriod() {
return this.value.equals(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue());
}
public boolean isInterestRateFromInstallment() {
return this.value.equals(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue());
}
} }
@@ -23,12 +23,13 @@ import java.util.Date;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@@ -63,6 +64,13 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
@Column(name = "applied_on_loan_status", nullable = false) @Column(name = "applied_on_loan_status", nullable = false)
private Integer onLoanStatus; private Integer onLoanStatus;
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private LoanTermVariations parent;
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue, public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
final boolean isSpecificToInstallment, final Loan loan) { final boolean isSpecificToInstallment, final Loan loan) {
this.loan = loan; this.loan = loan;
@@ -72,6 +80,8 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
this.dateValue = dateValue; this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment; this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loan.status().getValue(); this.onLoanStatus = loan.status().getValue();
this.isActive = true;
this.parent = null;
} }
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue, public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
@@ -83,6 +93,21 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
this.dateValue = dateValue; this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment; this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus; this.onLoanStatus = loanStatus;
this.isActive = true;
this.parent = null;
}
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
final boolean isSpecificToInstallment, final Loan loan, final Integer loanStatus, final Boolean isActive, final LoanTermVariations parent) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loanStatus;
this.isActive = isActive;
this.parent = parent;
} }
protected LoanTermVariations() { protected LoanTermVariations() {
@@ -136,4 +161,20 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
return this.onLoanStatus; return this.onLoanStatus;
} }
public Boolean isActive() {
return this.isActive;
}
public LoanTermVariations parent() {
return this.parent;
}
public void updateIsActive(final Boolean isActive){
this.isActive = isActive;
}
public void markAsInactive() {
this.isActive = false;
}
} }
@@ -777,4 +777,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public boolean isAccrualTransaction() { public boolean isAccrualTransaction() {
return isAccrual(); return isAccrual();
} }
public boolean isPaymentTransaction() {
return this.isNotReversed()
&& !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this
.isIncomePosting());
}
} }
@@ -73,6 +73,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
Money balanceForInterestCalculation = outstandingBalance; Money balanceForInterestCalculation = outstandingBalance;
Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size()); Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size());
for (LoanTermVariationsData loanTermVariation : termVariations) { for (LoanTermVariationsData loanTermVariation : termVariations) {
if (loanTermVariation.getTermVariationType().isInterestRateVariation() if (loanTermVariation.getTermVariationType().isInterestRateVariation()
&& loanTermVariation.isApplicable(periodStartDate, periodEndDate)) { && loanTermVariation.isApplicable(periodStartDate, periodEndDate)) {
@@ -86,6 +87,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
} }
} }
} }
if (principalVariation != null) { if (principalVariation != null) {
for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) { for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) {
@@ -99,6 +101,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
interestForThisInstallment = interestForThisInstallment.plus(result.interest()); interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
interestStartDate = principal.getKey(); interestStartDate = principal.getKey();
} }
Money compoundFee = totalCumulativePrincipal.zero(); Money compoundFee = totalCumulativePrincipal.zero();
if (compoundingMap.containsKey(principal.getKey())) { if (compoundingMap.containsKey(principal.getKey())) {
@@ -118,13 +121,14 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey()));
} }
} }
} }
} }
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
balanceForInterestCalculation, interestStartDate, periodEndDate); balanceForInterestCalculation, interestStartDate, periodEndDate);
interestForThisInstallment = interestForThisInstallment.plus(result.interest()); interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
@@ -32,11 +32,12 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
@Override @Override
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation, @SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
@SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, @SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
@SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) { @SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance, Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
periodNumber, mc, null); periodNumber, mc, null);
@@ -54,6 +55,11 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment, principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
totalCumulativePrincipalToDate, periodNumber); totalCumulativePrincipalToDate, periodNumber);
// totalCumulativeInterest from partial schedule generation for multi rescheduling
/*if (loanApplicationTerms.getPartialTotalCumulativeInterest() != null && loanApplicationTerms.getTotalInterestDue() != null) {
totalInterestDueForLoan = loanApplicationTerms.getTotalInterestDue();
totalInterestDueForLoan = totalInterestDueForLoan.plus(loanApplicationTerms.getPartialTotalCumulativeInterest());
}*/
interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment, interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment,
totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber); totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber);
@@ -21,7 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -193,6 +195,25 @@ public final class LoanApplicationTerms {
private final HolidayDetailDTO holidayDetailDTO; private final HolidayDetailDTO holidayDetailDTO;
private final Set<Integer> periodNumbersApplicableForPrincipalGrace = new HashSet<>();
private final Set<Integer> periodNumbersApplicableForInterestGrace = new HashSet<>();
// used for FLAT loans when interest rate changed
private Integer excludePeriodsForCalculation = 0;
private Money totalPrincipalAccountedForInterestCalcualtion;
//used for FLAT loans generation on modifying terms
private Money totalPrincipalAccounted;
private Money totalInterestAccounted;
private int periodsCompleted = 0;
private int extraPeriods = 0;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -482,6 +503,12 @@ public final class LoanApplicationTerms {
this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled;
this.holidayDetailDTO = holidayDetailDTO; this.holidayDetailDTO = holidayDetailDTO;
this.allowCompoundingOnEod = allowCompoundingOnEod; this.allowCompoundingOnEod = allowCompoundingOnEod;
Integer periodNumber = 1;
updatePeriodNumberApplicableForPrincipalOrInterestGrace(periodNumber);
updateRecurringMoratoriumOnPrincipalPeriods(periodNumber);
this.totalPrincipalAccountedForInterestCalcualtion = principal.zero();
this.totalInterestAccounted = principal.zero();
this.totalPrincipalAccounted = principal.zero();
} }
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -706,7 +733,8 @@ public final class LoanApplicationTerms {
switch (this.interestMethod) { switch (this.interestMethod) {
case FLAT: case FLAT:
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); totalInterestDue = this.principal.minus(totalPrincipalAccountedForInterestCalcualtion).multiplyRetainScale(interestRateForLoanTerm,
mc.getRoundingMode());
break; break;
case DECLINING_BALANCE: case DECLINING_BALANCE:
@@ -715,10 +743,6 @@ public final class LoanApplicationTerms {
break; break;
} }
if (this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue;
}
return totalInterestDue; return totalInterestDue;
} }
@@ -844,13 +868,19 @@ public final class LoanApplicationTerms {
private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.excludePeriodsForCalculation), mc.getRoundingMode());
if (this.excludePeriodsForCalculation < this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
interestPerInstallment = interestLeft.dividedBy(Long.valueOf(this.actualNumberOfRepayments)
- defaultToZeroIfNull(this.periodsCompleted), mc.getRoundingMode());
}
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode()); return interestPerInstallment;
} }
private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) { private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) {
final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment();
Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus( Money principalPerPeriod = this.principal.minus(totalPrincipalAccounted).dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(
this.adjustPrincipalForFlatLoans); this.adjustPrincipalForFlatLoans);
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principalPerPeriod = principalPerPeriod.zero(); principalPerPeriod = principalPerPeriod.zero();
@@ -907,10 +937,29 @@ public final class LoanApplicationTerms {
final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace()); final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree); final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments);
Integer interestPaymentDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
this.excludePeriodsForCalculation);
interestForInstallment = realTotalInterestForLoan interestForInstallment = realTotalInterestForLoan
.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode()); .dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
if (this.excludePeriodsForCalculation < this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
this.periodsCompleted);
interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode());
}
if(!this.periodNumbersApplicableForInterestGrace.isEmpty()){
int periodsElapsed = calculateLastInterestGracePeriod(periodNumber);
if (periodsElapsed > this.excludePeriodsForCalculation && periodsElapsed > this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments,
periodsElapsed);
interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode());
}
}
} }
return interestForInstallment; return interestForInstallment;
@@ -1032,24 +1081,46 @@ public final class LoanApplicationTerms {
private int calculateNumberOfRepaymentsWithPrincipalPayment() { private int calculateNumberOfRepaymentsWithPrincipalPayment() {
int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), 0); this.periodsCompleted);
// numPeriods = numPeriods - this.periodsCompleted;
return numPeriods; return numPeriods;
} }
private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) { private Integer calculateNumberOfRemainingInterestPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) {
return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace()); int principalFeePeriods = 0;
for (Integer intNumber : this.periodNumbersApplicableForInterestGrace) {
if (intNumber > periodsElapsed) {
principalFeePeriods++;
}
}
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods;
return periodsRemaining;
}
private Integer calculateLastInterestGracePeriod(int periodNumber){
int lastGracePeriod = 0;
for (Integer grace : this.periodNumbersApplicableForInterestGrace) {
if(grace < periodNumber && lastGracePeriod < grace){
lastGracePeriod = grace;
}
}
return lastGracePeriod;
} }
public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) { boolean isPrincipalGraceApplicableForThisPeriod = false;
return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this if (this.periodNumbersApplicableForPrincipalGrace.contains(periodNumber)) {
.getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1))); isPrincipalGraceApplicableForThisPeriod = true;
} }
return periodNumber > 0 && periodNumber <= getPrincipalGrace(); return isPrincipalGraceApplicableForThisPeriod;
} }
private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { public boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) {
return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); boolean isInterestPaymentGraceApplicableForThisPeriod = false;
if (this.periodNumbersApplicableForInterestGrace.contains(periodNumber)) {
isInterestPaymentGraceApplicableForThisPeriod = true;
}
return isInterestPaymentGraceApplicableForThisPeriod;
} }
private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) { private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) {
@@ -1099,7 +1170,7 @@ public final class LoanApplicationTerms {
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue(); final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodsElapsed); periodsElapsed);
double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(),
principalDouble, futureValue, false); principalDouble, futureValue, false);
@@ -1165,7 +1236,7 @@ public final class LoanApplicationTerms {
Money principal = this.principal; Money principal = this.principal;
if (this.fixedPrincipalAmount == null) { if (this.fixedPrincipalAmount == null) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(
this.actualNumberOfRepayments, this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber); this.actualNumberOfRepayments, periodNumber);
principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount(); this.fixedPrincipalAmount = principal.getAmount();
} }
@@ -1179,21 +1250,19 @@ public final class LoanApplicationTerms {
public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) { public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments,
this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber - 1); periodNumber - 1);
Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount(); this.fixedPrincipalAmount = principal.getAmount();
} }
private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, private Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) {
final int recurringMoratoriumOnPrincipalPeriods, final int PrincipalGrace, int periodsElapsed) { int principalFeePeriods = 0;
if (PrincipalGrace > periodsElapsed) { for (Integer intNumber : this.periodNumbersApplicableForPrincipalGrace) {
periodsElapsed = PrincipalGrace; if (intNumber > periodsElapsed) {
} principalFeePeriods++;
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed; }
if (recurringMoratoriumOnPrincipalPeriods > 0) {
double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1)));
periodsRemaining = (int) Math.ceil(periodsRemainingDouble);
} }
Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods;
return periodsRemaining; return periodsRemaining;
} }
@@ -1469,10 +1538,7 @@ public final class LoanApplicationTerms {
} }
public void updateTotalInterestDue(Money totalInterestDue) { public void updateTotalInterestDue(Money totalInterestDue) {
this.totalInterestDue = totalInterestDue;
if (totalInterestDue != null) {
this.totalInterestDue = totalInterestDue;
}
} }
public ApplicationCurrency getApplicationCurrency() { public ApplicationCurrency getApplicationCurrency() {
@@ -1576,4 +1642,81 @@ public final class LoanApplicationTerms {
} }
return disbursedAmount; return disbursedAmount;
} }
public void updatePeriodNumberApplicableForPrincipalOrInterestGrace(final Integer periodsApplicationForGrace) {
int applicablePeriodNumber = periodsApplicationForGrace;
int graceOnPrincipal = defaultToZeroIfNull(this.principalGrace);
int graceOnInterest = defaultToZeroIfNull(this.interestPaymentGrace);
while (graceOnPrincipal > 0 || graceOnInterest > 0) {
if (graceOnPrincipal > 0) {
this.periodNumbersApplicableForPrincipalGrace.add(applicablePeriodNumber);
}
if (graceOnInterest > 0) {
this.periodNumbersApplicableForInterestGrace.add(applicablePeriodNumber);
}
applicablePeriodNumber++;
graceOnPrincipal--;
graceOnInterest--;
}
}
/**
* set the value to zero if the provided value is null
*
* @return integer value equal/greater than 0
**/
private Integer defaultToZeroIfNull(Integer value) {
return (value != null) ? value : 0;
}
public void updateExcludePeriodsForCalculation(Integer excludePeriodsForCalculation){
this.excludePeriodsForCalculation = excludePeriodsForCalculation;
this.extraPeriods = 0;
}
public Integer getActualNoOfRepaymnets() {
return this.actualNumberOfRepayments;
}
public Money getTotalInterestDue() {
return this.totalInterestDue;
}
private void updateRecurringMoratoriumOnPrincipalPeriods(Integer periodNumber) {
Boolean isPrincipalGraceApplicableForThisPeriod = false;
Integer numberOfRepayments = this.actualNumberOfRepayments;
if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) {
while (numberOfRepayments > 0) {
isPrincipalGraceApplicableForThisPeriod = ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this
.getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1)));
if (isPrincipalGraceApplicableForThisPeriod) {
this.periodNumbersApplicableForPrincipalGrace.add(periodNumber);
}
numberOfRepayments--;
periodNumber++;
}
}
}
public void setTotalPrincipalAccounted(Money totalPrincipalAccounted) {
this.totalPrincipalAccountedForInterestCalcualtion = totalPrincipalAccounted;
}
//Used for FLAT loans to calculate principal and interest per installment
public void updateAccountedTillPeriod(int periodNumber, Money totalPrincipalAccounted,Money totalInterestAccounted, int extendPeriods){
this.periodsCompleted = periodNumber;
this.totalPrincipalAccounted = totalPrincipalAccounted;
this.totalInterestAccounted = totalInterestAccounted;
this.extraPeriods = this.extraPeriods + extendPeriods;
}
public void updateTotalInterestAccounted(Money totalInterestAccounted){
this.totalInterestAccounted = totalInterestAccounted;
}
} }
@@ -19,22 +19,15 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext; import java.math.MathContext;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
@@ -42,18 +35,12 @@ public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
final HolidayDetailDTO holidayDetailDTO); final HolidayDetailDTO holidayDetailDTO);
LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions, final HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate rescheduleFrom);
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, LocalDate rescheduleFrom);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges, HolidayDetailDTO holidayDetailDTO, LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
List<LoanTransaction> loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance,
CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays);
} }
@@ -86,7 +86,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException; import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException; import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException;
@@ -621,9 +620,8 @@ public class LoanScheduleAssembler {
} }
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges, Loan loan, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom) {
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom) {
final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
@@ -634,15 +632,13 @@ public class LoanScheduleAssembler {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO,
loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel(); loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
} }
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final Long officeId, LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
List<LoanTransaction> loanTransactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
@@ -653,8 +649,8 @@ public class LoanScheduleAssembler {
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO, return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loan, holidayDetailDTO,
loanTransactions, loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor);
} }
@@ -171,9 +171,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
} }
LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan); LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency, LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), today, loanApplicationTerms, loan, loan.getOfficeId(),
loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, loanRepaymentScheduleTransactionProcessor);
loan.getRepaymentScheduleInstallments());
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);
@@ -194,8 +193,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
} }
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.getRepaymentScheduleInstallments(), loan, loanRepaymentScheduleTransactionProcessor, loan.fetchInterestRecalculateFromDate());
loan.fetchInterestRecalculateFromDate());
LoanScheduleData scheduleDate = model.toData(); LoanScheduleData scheduleDate = model.toData();
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods(); Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
for (LoanSchedulePeriodData periodData : periodDatas) { for (LoanSchedulePeriodData periodData : periodDatas) {
@@ -42,9 +42,9 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanReschedulePreviewPlatformService; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanReschedulePreviewPlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -108,7 +108,7 @@ public class RescheduleLoansApiResource {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (compareIgnoreCase(command, "previewLoanReschedule")) { if (compareIgnoreCase(command, "previewLoanReschedule")) {
final LoanRescheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(scheduleId); final LoanScheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(scheduleId);
return this.loanRescheduleToApiJsonSerializer.serialize(settings, loanRescheduleModel.toData(), new HashSet<String>()); return this.loanRescheduleToApiJsonSerializer.serialize(settings, loanRescheduleModel.toData(), new HashSet<String>());
} }
@@ -170,4 +170,20 @@ public class RescheduleLoansApiResource {
private boolean compareIgnoreCase(String firstString, String secondString) { private boolean compareIgnoreCase(String firstString, String secondString) {
return StringUtils.isNotBlank(firstString) && firstString.trim().equalsIgnoreCase(secondString); return StringUtils.isNotBlank(firstString) && firstString.trim().equalsIgnoreCase(secondString);
} }
/*@GET
@Path("{scheduleId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTemplate(@Context final UriInfo uriInfo) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
LoanRescheduleRequestData loanRescheduleReasons = null;
loanRescheduleReasons = this.loanRescheduleRequestReadPlatformService
.retrieveAllRescheduleReasons(RescheduleLoansApiConstants.LOAN_RESCHEDULE_REASON);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleReasons);
}*/
} }
@@ -18,10 +18,10 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data; package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
/** /**
@@ -36,59 +36,55 @@ public class LoanRescheduleRequestData {
private final String loanAccountNumber; private final String loanAccountNumber;
private final LoanRescheduleRequestStatusEnumData statusEnum; private final LoanRescheduleRequestStatusEnumData statusEnum;
private final Integer rescheduleFromInstallment; private final Integer rescheduleFromInstallment;
private final Integer graceOnPrincipal;
private final Integer graceOnInterest;
private final LocalDate rescheduleFromDate; private final LocalDate rescheduleFromDate;
private final Integer extraTerms;
private final BigDecimal interestRate;
private final Boolean recalculateInterest; private final Boolean recalculateInterest;
private final CodeValueData rescheduleReasonCodeValue; private final CodeValueData rescheduleReasonCodeValue;
private final LoanRescheduleRequestTimelineData timeline; private final LoanRescheduleRequestTimelineData timeline;
private final String rescheduleReasonComment; private final String rescheduleReasonComment;
private final LocalDate adjustedDueDate; @SuppressWarnings("unused")
private final Collection<CodeValueData> rescheduleReasons; private final Collection<CodeValueData> rescheduleReasons;
@SuppressWarnings("unused")
private final Collection<LoanTermVariationsData> loanTermVariationsData;
/** /**
* LoanRescheduleRequestData constructor * LoanRescheduleRequestData constructor
* @param loanTermVariationsData TODO
**/ **/
private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum, private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate, Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue,
LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) { final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest,
Collection<CodeValueData> rescheduleReasons, final Collection<LoanTermVariationsData> loanTermVariationsData) {
this.id = id; this.id = id;
this.loanId = loanId; this.loanId = loanId;
this.statusEnum = statusEnum; this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment; this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate; this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue; this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment; this.rescheduleReasonComment = rescheduleReasonComment;
this.adjustedDueDate = adjustedDueDate;
this.timeline = timeline; this.timeline = timeline;
this.clientName = clientName; this.clientName = clientName;
this.loanAccountNumber = loanAccountNumber; this.loanAccountNumber = loanAccountNumber;
this.clientId = clientId; this.clientId = clientId;
this.recalculateInterest = recalculateInterest; this.recalculateInterest = recalculateInterest;
this.rescheduleReasons = rescheduleReasons ; this.rescheduleReasons = rescheduleReasons ;
this.loanTermVariationsData = loanTermVariationsData;
} }
/** /**
* @param loanTermVariationsData TODO
* @return an instance of the LoanRescheduleRequestData class * @return an instance of the LoanRescheduleRequestData class
**/ **/
public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum, public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate, Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue,
LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue,
String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName,
final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection<CodeValueData> rescheduleReasons) { final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest,
Collection<CodeValueData> rescheduleReasons, final Collection<LoanTermVariationsData> loanTermVariationsData) {
return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); rescheduleReasons, loanTermVariationsData);
} }
/** /**
@@ -119,20 +115,6 @@ public class LoanRescheduleRequestData {
return rescheduleFromInstallment; return rescheduleFromInstallment;
} }
/**
* @return the graceOnPrincipal
*/
public Integer getGraceOnPrincipal() {
return graceOnPrincipal;
}
/**
* @return the graceOnInterest
*/
public Integer getGraceOnInterest() {
return graceOnInterest;
}
/** /**
* @return the reschedule from date * @return the reschedule from date
*/ */
@@ -140,20 +122,6 @@ public class LoanRescheduleRequestData {
return rescheduleFromDate; return rescheduleFromDate;
} }
/**
* @return the extraTerms
*/
public Integer getExtraTerms() {
return extraTerms;
}
/**
* @return the interestRate
*/
public BigDecimal getInterestRate() {
return interestRate;
}
/** /**
* @return the rescheduleReasonCodeValueId * @return the rescheduleReasonCodeValueId
*/ */
@@ -175,13 +143,6 @@ public class LoanRescheduleRequestData {
return this.timeline; return this.timeline;
} }
/**
* @return the adjustedDueDate
*/
public LocalDate getAdjustedDueDate() {
return adjustedDueDate;
}
/** /**
* @return the clientName * @return the clientName
*/ */
@@ -32,10 +32,8 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -47,13 +45,10 @@ import com.google.gson.reflect.TypeToken;
public class LoanRescheduleRequestDataValidator { public class LoanRescheduleRequestDataValidator {
private final FromJsonHelper fromJsonHelper; private final FromJsonHelper fromJsonHelper;
private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
@Autowired @Autowired
public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper, public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper) {
LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService) {
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
} }
/** /**
@@ -159,21 +154,8 @@ public class LoanRescheduleRequestDataValidator {
.failWithCode("repayment.schedule.installment.obligation.met", "Repayment schedule installment obligation met"); .failWithCode("repayment.schedule.installment.obligation.met", "Repayment schedule installment obligation met");
} }
if (installment != null && installment.isPartlyPaid()) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("repayment.schedule.installment.partly.paid", "Repayment schedule installment is partly paid");
}
} }
if (loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if (loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
if(loan.isMultiDisburmentLoan()) { if(loan.isMultiDisburmentLoan()) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode, dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode,
"Loan rescheduling is not supported for multidisbursement loans"); "Loan rescheduling is not supported for multidisbursement loans");
@@ -232,7 +214,6 @@ public class LoanRescheduleRequestDataValidator {
final Loan loan = loanRescheduleRequest.getLoan(); final Loan loan = loanRescheduleRequest.getLoan();
if (loan != null) { if (loan != null) {
Long loanId = loan.getId();
if (!loan.status().isActive()) { if (!loan.status().isActive()) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", "Loan is not active"); dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", "Loan is not active");
@@ -251,16 +232,6 @@ public class LoanRescheduleRequestDataValidator {
"loan.repayment.schedule.installment." + "obligation.met", "Repayment schedule installment obligation met"); "loan.repayment.schedule.installment." + "obligation.met", "Repayment schedule installment obligation met");
} }
} }
if (loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if (loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
} }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -1,62 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) {
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case INVALID:
break;
}
return loanRescheduleModel;
}
}
@@ -18,20 +18,27 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain; package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -40,116 +47,97 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_loan_reschedule_request") @Table(name = "m_loan_reschedule_request")
public class LoanRescheduleRequest extends AbstractPersistable<Long> { public class LoanRescheduleRequest extends AbstractPersistable<Long> {
@ManyToOne @ManyToOne
@JoinColumn(name = "loan_id", nullable = false) @JoinColumn(name = "loan_id", nullable = false)
private Loan loan; private Loan loan;
@Column(name = "status_enum", nullable = false) @Column(name = "status_enum", nullable = false)
private Integer statusEnum; private Integer statusEnum;
@Column(name = "reschedule_from_installment") @Column(name = "reschedule_from_installment")
private Integer rescheduleFromInstallment; private Integer rescheduleFromInstallment;
@Column(name = "grace_on_principal") @Temporal(TemporalType.DATE)
private Integer graceOnPrincipal;
@Column(name = "grace_on_interest")
private Integer graceOnInterest;
@Temporal(TemporalType.DATE)
@Column(name = "reschedule_from_date") @Column(name = "reschedule_from_date")
private Date rescheduleFromDate; private Date rescheduleFromDate;
@Temporal(TemporalType.DATE) @Column(name = "recalculate_interest")
@Column(name = "adjusted_due_date") private Boolean recalculateInterest;
private Date adjustedDueDate;
@Column(name = "extra_terms") @ManyToOne
private Integer extraTerms;
@Column(name = "recalculate_interest")
private Boolean recalculateInterest;
@Column(name = "interest_rate", scale = 6, precision = 19)
private BigDecimal interestRate;
@ManyToOne
@JoinColumn(name = "reschedule_reason_cv_id") @JoinColumn(name = "reschedule_reason_cv_id")
private CodeValue rescheduleReasonCodeValue; private CodeValue rescheduleReasonCodeValue;
@Column(name = "reschedule_reason_comment") @Column(name = "reschedule_reason_comment")
private String rescheduleReasonComment; private String rescheduleReasonComment;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "submitted_on_date") @Column(name = "submitted_on_date")
private Date submittedOnDate; private Date submittedOnDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "submitted_by_user_id") @JoinColumn(name = "submitted_by_user_id")
private AppUser submittedByUser; private AppUser submittedByUser;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "approved_on_date") @Column(name = "approved_on_date")
private Date approvedOnDate; private Date approvedOnDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "approved_by_user_id") @JoinColumn(name = "approved_by_user_id")
private AppUser approvedByUser; private AppUser approvedByUser;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "rejected_on_date") @Column(name = "rejected_on_date")
private Date rejectedOnDate; private Date rejectedOnDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "rejected_by_user_id") @JoinColumn(name = "rejected_by_user_id")
private AppUser rejectedByUser; private AppUser rejectedByUser;
/** @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.EAGER)
* LoanRescheduleRequest constructor @JoinColumn(name = "loan_reschedule_request_id", nullable = false)
**/ private Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = new HashSet<>();
protected LoanRescheduleRequest() {}
/** /**
* LoanRescheduleRequest constructor * LoanRescheduleRequest constructor
**/ **/
private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, protected LoanRescheduleRequest() {}
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser,
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
this.loan = loan;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.submittedOnDate = submittedOnDate;
this.submittedByUser = submittedByUser;
this.approvedOnDate = approvedOnDate;
this.approvedByUser = approvedByUser;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUser = rejectedByUser;
this.adjustedDueDate = adjustedDueDate;
this.recalculateInterest = recalculateInterest;
}
/** /**
* @return a new instance of the LoanRescheduleRequest class * LoanRescheduleRequest constructor
**/ **/
public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate, final Date rescheduleFromDate, final Boolean recalculateInterest, final CodeValue rescheduleReasonCodeValue,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue, final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, final Date approvedOnDate,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) { this.loan = loan;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.rescheduleFromDate = rescheduleFromDate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.submittedOnDate = submittedOnDate;
this.submittedByUser = submittedByUser;
this.approvedOnDate = approvedOnDate;
this.approvedByUser = approvedByUser;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUser = rejectedByUser;
this.recalculateInterest = recalculateInterest;
}
return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, /**
rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate, * @return a new instance of the LoanRescheduleRequest class
submittedByUser, approvedOnDate, approvedByUser, rejectedOnDate, rejectedByUser); **/
} public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Date rescheduleFromDate, final Boolean recalculateInterest, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser, final Date approvedOnDate,
final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, rescheduleFromDate, recalculateInterest,
rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate, submittedByUser, approvedOnDate, approvedByUser,
rejectedOnDate, rejectedByUser);
}
/** /**
* @return the reschedule request loan object * @return the reschedule request loan object
@@ -172,20 +160,6 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
return this.rescheduleFromInstallment; return this.rescheduleFromInstallment;
} }
/**
* @return the grace on principal
**/
public Integer getGraceOnPrincipal() {
return this.graceOnPrincipal;
}
/**
* @return the grace on interest
**/
public Integer getGraceOnInterest() {
return this.graceOnInterest;
}
/** /**
* @return due date of the rescheduling start point * @return due date of the rescheduling start point
**/ **/
@@ -200,34 +174,6 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
return localDate; return localDate;
} }
/**
* @return due date of the first rescheduled installment
**/
public LocalDate getAdjustedDueDate() {
LocalDate localDate = null;
if(this.adjustedDueDate != null) {
localDate = new LocalDate(this.adjustedDueDate);
}
return localDate;
}
/**
* @return extra terms to be added after the last loan installment
**/
public Integer getExtraTerms() {
return this.extraTerms;
}
/**
* @return the new interest rate to be applied to unpaid installments
**/
public BigDecimal getInterestRate() {
return this.interestRate;
}
/** /**
* @return the reschedule reason code value object * @return the reschedule reason code value object
**/ **/
@@ -348,4 +294,24 @@ public class LoanRescheduleRequest extends AbstractPersistable<Long> {
this.statusEnum = LoanStatus.REJECTED.getValue(); this.statusEnum = LoanStatus.REJECTED.getValue();
} }
} }
public void updateLoanRescheduleRequestToTermVariationMappings(final List<LoanRescheduleRequestToTermVariationMapping> mapping) {
this.loanRescheduleRequestToTermVariationMappings.addAll(mapping);
}
public Set<LoanRescheduleRequestToTermVariationMapping> getLoanRescheduleRequestToTermVariationMappings() {
return this.loanRescheduleRequestToTermVariationMappings;
}
public LoanTermVariations getDueDateTermVariationIfExists() {
if(this.loanRescheduleRequestToTermVariationMappings != null
&& this.loanRescheduleRequestToTermVariationMappings.size() > 0){
for (LoanRescheduleRequestToTermVariationMapping mapping : this.loanRescheduleRequestToTermVariationMappings) {
if(mapping.getLoanTermVariations().getTermType().isDueDateVariation()){
return mapping.getLoanTermVariations();
}
}
}
return null;
}
} }
@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance,
final Calendar loanCalendar, FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst,
final Integer numberofdays);
}
@@ -18,9 +18,9 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
public interface LoanReschedulePreviewPlatformService { public interface LoanReschedulePreviewPlatformService {
public LoanRescheduleModel previewLoanReschedule(Long requestId); public LoanScheduleModel previewLoanReschedule(Long requestId);
} }
@@ -18,41 +18,33 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.joda.time.LocalDate;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -60,103 +52,94 @@ import org.springframework.stereotype.Service;
public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService { public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService {
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanSummaryWrapper loanSummaryWrapper;
private final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
@Autowired @Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository, public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final LoanUtilService loanUtilService,
final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper) {
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
this.calendarInstanceRepository = calendarInstanceRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.loanUtilService = loanUtilService; this.loanUtilService = loanUtilService;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.loanScheduleFactory = loanScheduleFactory;
this.loanSummaryWrapper = loanSummaryWrapper;
} }
@Override @Override
public LoanRescheduleModel previewLoanReschedule(Long requestId) { public LoanScheduleModel previewLoanReschedule(Long requestId) {
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId); final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); } if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); }
Loan loan = loanRescheduleRequest.getLoan(); Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan,
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan loanRescheduleRequest.getRescheduleFromDate());
.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); LocalDate rescheduleFromDate = null;
final WorkingDays workingDays = this.workingDaysRepository.findOne(); List<LoanTermVariationsData> removeLoanTermVariationsData = new ArrayList<>();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); if(dueDateVariationInCurrentRequest != null){
for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (loanTermVariation.getDateValue().equals(dueDateVariationInCurrentRequest.fetchTermApplicaDate())) {
rescheduleFromDate = loanTermVariation.getTermApplicableFrom();
removeLoanTermVariationsData.add(loanTermVariation);
}
}
}
loanApplicationTerms.getLoanTermVariations().getDueDateVariation().removeAll(removeLoanTermVariationsData);
if (rescheduleFromDate == null) {
rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
}
List<LoanTermVariationsData> loanTermVariationsData = new ArrayList<>();
LocalDate adjustedApplicableDate = null;
Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = loanRescheduleRequest.getLoanRescheduleRequestToTermVariationMappings();
if (!loanRescheduleRequestToTermVariationMappings.isEmpty()) {
for (LoanRescheduleRequestToTermVariationMapping loanRescheduleRequestToTermVariationMapping : loanRescheduleRequestToTermVariationMappings) {
if (loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().getTermType().isDueDateVariation()
&& rescheduleFromDate != null) {
adjustedApplicableDate = loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().fetchDateValue();
loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().setTermApplicableFrom(
rescheduleFromDate.toDate());
}
loanTermVariationsData.add(loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().toData());
}
}
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
false, loanApplicationTerms.getHolidayDetailDTO());
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanTermVariation.setApplicableFromDate(adjustedDate);
loanTermVariationsData.add(loanTermVariation);
}
}
}
loanApplicationTerms.getLoanTermVariations().updateLoanTermVariationsData(loanTermVariationsData);
final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode); final MathContext mathContext = new MathContext(8, roundingMode);
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); .determineProcessor(loan.transactionProcessingStrategy());
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
CalendarInstance restCalendarInstance = null; final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
CalendarInstance compoundingCalendarInstance = null; loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms,
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), loan, loanApplicationTerms.getHolidayDetailDTO(),
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( final LoanScheduleModel loanScheduleModel = loanSchedule.getLoanScheduleModel();
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); LoanScheduleModel loanScheduleModels = LoanScheduleModel.withLoanScheduleModelPeriods(loanScheduleModel.getPeriods(),
} loanScheduleModel);
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
Calendar loanCalendar = null;
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
} return loanScheduleModels;
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods);
return loanRescheduleModelWithOldPeriods;
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
}catch(final FloatingRateNotFoundException ex){
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
} }
} }
@@ -29,6 +29,7 @@ import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -36,6 +37,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanResched
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestEnumerations; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestEnumerations;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestStatusEnumData; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
@@ -71,13 +73,8 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("mc.id as clientId, "); sqlBuilder.append("mc.id as clientId, ");
sqlBuilder.append("ml.account_no as loanAccountNumber, "); sqlBuilder.append("ml.account_no as loanAccountNumber, ");
sqlBuilder.append("lr.reschedule_from_installment as rescheduleFromInstallment, "); sqlBuilder.append("lr.reschedule_from_installment as rescheduleFromInstallment, ");
sqlBuilder.append("lr.grace_on_principal as graceOnPrincipal, ");
sqlBuilder.append("lr.grace_on_interest as graceOnInterest, ");
sqlBuilder.append("lr.reschedule_from_date as rescheduleFromDate, "); sqlBuilder.append("lr.reschedule_from_date as rescheduleFromDate, ");
sqlBuilder.append("lr.adjusted_due_date as adjustedDueDate, ");
sqlBuilder.append("lr.extra_terms as extraTerms, ");
sqlBuilder.append("lr.recalculate_interest as recalculateInterest, "); sqlBuilder.append("lr.recalculate_interest as recalculateInterest, ");
sqlBuilder.append("lr.interest_rate as interestRate, ");
sqlBuilder.append("lr.reschedule_reason_cv_id as rescheduleReasonCvId, "); sqlBuilder.append("lr.reschedule_reason_cv_id as rescheduleReasonCvId, ");
sqlBuilder.append("cv.code_value as rescheduleReasonCvValue, "); sqlBuilder.append("cv.code_value as rescheduleReasonCvValue, ");
sqlBuilder.append("lr.reschedule_reason_comment as rescheduleReasonComment, "); sqlBuilder.append("lr.reschedule_reason_comment as rescheduleReasonComment, ");
@@ -95,7 +92,14 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("lr.rejected_on_date as rejectedOnDate, "); sqlBuilder.append("lr.rejected_on_date as rejectedOnDate, ");
sqlBuilder.append("rbu.username as rejectedByUsername, "); sqlBuilder.append("rbu.username as rejectedByUsername, ");
sqlBuilder.append("rbu.firstname as rejectedByFirstname, "); sqlBuilder.append("rbu.firstname as rejectedByFirstname, ");
sqlBuilder.append("rbu.lastname as rejectedByLastname "); sqlBuilder.append("rbu.lastname as rejectedByLastname, ");
sqlBuilder.append("tv.id as termId,");
sqlBuilder.append("tv.term_type as termType,");
sqlBuilder.append("tv.applicable_date as variationApplicableFrom, ");
sqlBuilder.append("tv.decimal_value as decimalValue, ");
sqlBuilder.append("tv.date_value as dateValue, ");
sqlBuilder.append("tv.is_specific_to_installment as isSpecificToInstallment ");
sqlBuilder.append("from " + loanRescheduleRequestTableName() + " lr "); sqlBuilder.append("from " + loanRescheduleRequestTableName() + " lr ");
sqlBuilder.append("left join m_code_value cv on cv.id = lr.reschedule_reason_cv_id "); sqlBuilder.append("left join m_code_value cv on cv.id = lr.reschedule_reason_cv_id ");
@@ -104,6 +108,8 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
sqlBuilder.append("left join m_appuser rbu on rbu.id = lr.rejected_by_user_id "); sqlBuilder.append("left join m_appuser rbu on rbu.id = lr.rejected_by_user_id ");
sqlBuilder.append("left join m_loan ml on ml.id = lr.loan_id "); sqlBuilder.append("left join m_loan ml on ml.id = lr.loan_id ");
sqlBuilder.append("left join m_client mc on mc.id = ml.client_id "); sqlBuilder.append("left join m_client mc on mc.id = ml.client_id ");
sqlBuilder.append("join m_loan_reschedule_request_term_variations_mapping rrtvm on lr.id = rrtvm.loan_reschedule_request_id ");
sqlBuilder.append("join m_loan_term_variations tv on tv.id = rrtvm.loan_term_variations_id and tv.parent_id is null") ;
this.schema = sqlBuilder.toString(); this.schema = sqlBuilder.toString();
} }
@@ -127,12 +133,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final String loanAccountNumber = rs.getString("loanAccountNumber"); final String loanAccountNumber = rs.getString("loanAccountNumber");
final Long clientId = rs.getLong("clientId"); final Long clientId = rs.getLong("clientId");
final Integer rescheduleFromInstallment = JdbcSupport.getInteger(rs, "rescheduleFromInstallment"); final Integer rescheduleFromInstallment = JdbcSupport.getInteger(rs, "rescheduleFromInstallment");
final Integer graceOnPrincipal = JdbcSupport.getInteger(rs, "graceOnPrincipal");
final Integer graceOnInterest = JdbcSupport.getInteger(rs, "graceOnInterest");
final LocalDate rescheduleFromDate = JdbcSupport.getLocalDate(rs, "rescheduleFromDate"); final LocalDate rescheduleFromDate = JdbcSupport.getLocalDate(rs, "rescheduleFromDate");
final LocalDate adjustedDueDate = JdbcSupport.getLocalDate(rs, "adjustedDueDate");
final Integer extraTerms = JdbcSupport.getInteger(rs, "extraTerms");
final BigDecimal interestRate = rs.getBigDecimal("interestRate");
final Long rescheduleReasonCvId = JdbcSupport.getLong(rs, "rescheduleReasonCvId"); final Long rescheduleReasonCvId = JdbcSupport.getLong(rs, "rescheduleReasonCvId");
final String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue"); final String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue");
final CodeValueData rescheduleReasonCodeValue = CodeValueData.instance(rescheduleReasonCvId, rescheduleReasonCvValue); final CodeValueData rescheduleReasonCodeValue = CodeValueData.instance(rescheduleReasonCvId, rescheduleReasonCvValue);
@@ -158,9 +159,35 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname,
rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname); rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname);
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); do {
Long tempId = rs.getLong("id");
if (id.equals(tempId)) {
loanTermVariations.add(fetchLoanTermVariation(rs));
} else {
rs.previous();
break;
}
} while (rs.next());
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId,
recalculateInterest, rescheduleReasons, loanTermVariations);
}
private LoanTermVariationsData fetchLoanTermVariation(final ResultSet rs) throws SQLException {
final Long id = rs.getLong("termId");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
final int termType = rs.getInt("termType");
final LoanTermVariationsData loanTermVariationsData = new LoanTermVariationsData(id,
LoanEnumerations.loanvariationType(termType), variationApplicableFrom, decimalValue, dateValue,
isSpecificToInstallment);
return loanTermVariationsData;
} }
} }
@@ -209,12 +236,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final Long loanId = null; final Long loanId = null;
final LoanRescheduleRequestStatusEnumData statusEnum = null; final LoanRescheduleRequestStatusEnumData statusEnum = null;
final Integer rescheduleFromInstallment = null; final Integer rescheduleFromInstallment = null;
final Integer graceOnPrincipal = null;
final Integer graceOnInterest = null;
final LocalDate rescheduleFromDate = null; final LocalDate rescheduleFromDate = null;
final LocalDate adjustedDueDate = null;
final Integer extraTerms = null;
final BigDecimal interestRate = null;
final CodeValueData rescheduleReasonCodeValue = null; final CodeValueData rescheduleReasonCodeValue = null;
final String rescheduleReasonComment = null; final String rescheduleReasonComment = null;
final LoanRescheduleRequestTimelineData timeline = null; final LoanRescheduleRequestTimelineData timeline = null;
@@ -222,9 +244,10 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
final String loanAccountNumber = null; final String loanAccountNumber = null;
final Long clientId = null; final Long clientId = null;
final Boolean recalculateInterest = null; final Boolean recalculateInterest = null;
final Collection<LoanTermVariationsData> loanTermVariationsData = null;
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); rescheduleReasons, loanTermVariationsData);
} }
} }
@@ -22,9 +22,9 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -33,64 +33,52 @@ import java.util.Set;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
@@ -107,25 +95,25 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class); private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final PlatformSecurityContext platformSecurityContext; private final PlatformSecurityContext platformSecurityContext;
private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator; private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator;
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepositoryWrapper holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository; private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanSummaryWrapper loanSummaryWrapper;
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final LoanAccountDomainService loanAccountDomainService;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
/** /**
* LoanRescheduleRequestWritePlatformServiceImpl constructor * LoanRescheduleRequestWritePlatformServiceImpl constructor
@@ -133,38 +121,39 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
* @return void * @return void
**/ **/
@Autowired @Autowired
public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper, public LoanRescheduleRequestWritePlatformServiceImpl(final CodeValueRepositoryWrapper codeValueRepositoryWrapper,
CodeValueRepositoryWrapper codeValueRepositoryWrapper, PlatformSecurityContext platformSecurityContext, final PlatformSecurityContext platformSecurityContext,
LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator, final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
LoanRescheduleRequestRepository loanRescheduleRequestRepository, final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
HolidayRepositoryWrapper holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository, final LoanChargeReadPlatformService loanChargeReadPlatformService,
final LoanTransactionRepository loanTransactionRepository, final LoanTransactionRepository loanTransactionRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository,
final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanAssembler loanAssembler, final LoanUtilService loanUtilService,
final LoanUtilService loanUtilService) { final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
this.loanRepositoryWrapper = loanRepositoryWrapper; final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper,
final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
final LoanAccountDomainService loanAccountDomainService,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.platformSecurityContext = platformSecurityContext; this.platformSecurityContext = platformSecurityContext;
this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator; this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository; this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.loanUtilService = loanUtilService; this.loanUtilService = loanUtilService;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.loanScheduleFactory = loanScheduleFactory;
this.loanSummaryWrapper = loanSummaryWrapper;
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
this.loanAccountDomainService = loanAccountDomainService;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
} }
/** /**
@@ -182,7 +171,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final Long loanId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.loanIdParamName); final Long loanId = jsonCommand.longValueOfParameterNamed(RescheduleLoansApiConstants.loanIdParamName);
// use the loan id to get a Loan entity object // use the loan id to get a Loan entity object
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
// validate the request in the JsonCommand object passed as // validate the request in the JsonCommand object passed as
// parameter // parameter
@@ -275,12 +264,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} }
final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(loan, final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(loan,
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, rescheduleFromDate,
rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, recalculateInterest, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate,
rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(), null, null, null, null); this.platformSecurityContext.authenticatedUser(), null, null, null, null);
// update reschedule request to term variations mapping
List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = new ArrayList<>();
final Boolean isActive = false;
final boolean isSpecificToInstallment = false;
BigDecimal decimalValue = null;
Date dueDate = null;
// create term variations for flat and declining balance loans
createLoanTermVariationsForRegularLoans(loan, graceOnPrincipal, graceOnInterest, extraTerms, interestRate, rescheduleFromDate,
adjustedDueDate, loanRescheduleRequest, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, decimalValue, dueDate);
// create a new entry in the m_loan_reschedule_request table // create a new entry in the m_loan_reschedule_request table
this.loanRescheduleRequestRepository.save(loanRescheduleRequest); this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
this.loanRepository.save(loan);
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId()) return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId())
.withLoanId(loan.getId()).build(); .withLoanId(loan.getId()).build();
@@ -295,6 +296,63 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} }
} }
private void createLoanTermVariationsForRegularLoans(final Loan loan, final Integer graceOnPrincipal, final Integer graceOnInterest,
final Integer extraTerms, final BigDecimal interestRate, Date rescheduleFromDate, Date adjustedDueDate,
final LoanRescheduleRequest loanRescheduleRequest,
List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings, final Boolean isActive,
final boolean isSpecificToInstallment, BigDecimal decimalValue, Date dueDate) {
if (rescheduleFromDate != null && adjustedDueDate != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.DUE_DATE.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, adjustedDueDate, loanRescheduleRequestToTermVariationMappings,
isActive, isSpecificToInstallment, decimalValue, parent);
}
if (rescheduleFromDate != null && interestRate != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, interestRate, parent);
}
if (rescheduleFromDate != null && graceOnPrincipal != null) {
final Integer termType = LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue();
LoanTermVariations parent = null;
parent = createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings,
isActive, isSpecificToInstallment, BigDecimal.valueOf(graceOnPrincipal), parent);
BigDecimal extraTermsBasedOnGracePeriods = BigDecimal.valueOf(graceOnPrincipal);
createLoanTermVariations(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue(), loan, rescheduleFromDate, dueDate,
loanRescheduleRequestToTermVariationMappings, isActive, isSpecificToInstallment, extraTermsBasedOnGracePeriods, parent);
}
if (rescheduleFromDate != null && graceOnInterest != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.GRACE_ON_INTEREST.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, BigDecimal.valueOf(graceOnInterest), parent);
}
if (rescheduleFromDate != null && extraTerms != null) {
LoanTermVariations parent = null;
final Integer termType = LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue();
createLoanTermVariations(termType, loan, rescheduleFromDate, dueDate, loanRescheduleRequestToTermVariationMappings, isActive,
isSpecificToInstallment, BigDecimal.valueOf(extraTerms), parent);
}
loanRescheduleRequest.updateLoanRescheduleRequestToTermVariationMappings(loanRescheduleRequestToTermVariationMappings);
}
private LoanTermVariations createLoanTermVariations(final Integer termType, final Loan loan, Date rescheduleFromDate,
Date adjustedDueDate, List<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings,
final Boolean isActive, final boolean isSpecificToInstallment, final BigDecimal decimalValue, LoanTermVariations parent) {
LoanTermVariations loanTermVariation = new LoanTermVariations(termType, rescheduleFromDate, decimalValue, adjustedDueDate,
isSpecificToInstallment, loan, loan.status().getValue(), isActive, parent);
loanRescheduleRequestToTermVariationMappings.add(LoanRescheduleRequestToTermVariationMapping.createNew(loanTermVariation));
return loanTermVariation;
}
@Override @Override
@Transactional @Transactional
public CommandProcessingResult approve(JsonCommand jsonCommand) { public CommandProcessingResult approve(JsonCommand jsonCommand) {
@@ -322,132 +380,112 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter)); changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
changes.put("approvedByUserId", appUser.getId()); changes.put("approvedByUserId", appUser.getId());
if (!changes.isEmpty()) { Loan loan = loanRescheduleRequest.getLoan();
Loan loan = loanRescheduleRequest.getLoan(); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final LoanSummary loanSummary = loan.getSummary(); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan,
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan loanRescheduleRequest.getRescheduleFromDate());
.getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
final MathContext mathContext = new MathContext(8, roundingMode);
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); LocalDate rescheduleFromDate = null;
CalendarInstance restCalendarInstance = null; Set<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
CalendarInstance compoundingCalendarInstance = null; LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (dueDateVariationInCurrentRequest != null && activeLoanTermVariations != null) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( LocalDate fromScheduleDate = dueDateVariationInCurrentRequest.fetchTermApplicaDate();
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); LocalDate currentScheduleDate = fromScheduleDate;
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( LocalDate modifiedScheduleDate = dueDateVariationInCurrentRequest.fetchDateValue();
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); Map<LocalDate, LocalDate> changeMap = new HashMap<>();
} changeMap.put(currentScheduleDate, modifiedScheduleDate);
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), for (LoanTermVariations activeLoanTermVariation : activeLoanTermVariations) {
CalendarEntityType.LOANS.getValue()); if (activeLoanTermVariation.getTermType().isDueDateVariation()
Calendar loanCalendar = null; && activeLoanTermVariation.fetchDateValue().equals(dueDateVariationInCurrentRequest.fetchTermApplicaDate())) {
if (loanCalendarInstance != null) { activeLoanTermVariation.markAsInactive();
loanCalendar = loanCalendarInstance.getCalendar(); rescheduleFromDate = activeLoanTermVariation.fetchTermApplicaDate();
} dueDateVariationInCurrentRequest.setTermApplicableFrom(rescheduleFromDate.toDate());
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); } else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
Boolean isSkipRepaymentOnFirstMonth = false; while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
Integer numberOfDays = 0; currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled(); loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
if(isSkipRepaymentOnFirstMonthEnabled){ modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar); loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); } changeMap.put(currentScheduleDate, modifiedScheduleDate);
}
} if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, activeLoanTermVariation.setTermApplicableFrom(changeMap.get(activeLoanTermVariation.fetchTermApplicaDate())
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, .toDate());
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
Collection<LoanCharge> waiveLoanCharges = new ArrayList<>();
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
for (LoanRescheduleModelRepaymentPeriod period : periods) {
if (period.isNew()) {
LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(loan,
period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(),
period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false, compoundingDetails);
loan.addLoanRepaymentScheduleInstallment(repaymentScheduleInstallment);
repaymentScheduleInstallments.add(repaymentScheduleInstallment) ;
}
else {
for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
if (repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
LocalDate periodDueDate = repaymentScheduleInstallment.getDueDate();
Money zeroAmount = Money.of(currency, new BigDecimal(0));
repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber());
repaymentScheduleInstallment.updateFromDate(period.periodFromDate());
repaymentScheduleInstallment.updateDueDate(period.periodDueDate());
repaymentScheduleInstallment.updatePrincipal(period.principalDue());
repaymentScheduleInstallment.updateInterestCharged(period.interestDue());
if (Money.of(currency, period.principalDue()).isZero() && Money.of(currency, period.interestDue()).isZero()
&& repaymentScheduleInstallment.isNotFullyPaidOff()) {
if (repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isGreaterThan(zeroAmount)
|| repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isGreaterThan(zeroAmount)) {
waiveLoanCharges.addAll(loan.getLoanCharges(periodDueDate));
}
}
break;
}
} }
} }
} }
for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// waive all loan charges of zero instalments
waiveLoanCharges(loan, waiveLoanCharges);
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null);
// update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size());
// update the loan term frequency (loan term frequency = number
// of repayments)
loan.updateTermFrequency(periods.size());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
// update the derived fields of each loan repayments schedule
// instalments
for (final LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
repaymentScheduleInstallment.updateDerivedFields(currency, new LocalDate());
}
// updates maturity date
loan.updateLoanScheduleDependentDerivedFields();
// update the loan object
this.loanRepository.save(loan);
} }
if (rescheduleFromDate == null) {
rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
}
for (LoanRescheduleRequestToTermVariationMapping mapping : loanRescheduleRequest
.getLoanRescheduleRequestToTermVariationMappings()) {
mapping.getLoanTermVariations().updateIsActive(true);
}
BigDecimal annualNominalInterestRate = null;
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
loan.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate, loanTermVariations);
loanApplicationTerms.getLoanTermVariations().setExceptionData(loanTermVariations);
/*for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanTermVariation.setApplicableFromDate(adjustedDate);
}
}
}*/
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms,
loan, loanApplicationTerms.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
loan.updateLoanSchedule(loanSchedule.getInstallments(), appUser);
loan.recalculateAllCharges();
ChangedTransactionDetail changedTransactionDetail = loan.processTransactions();
for (LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
// update the loan object
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.addLoanTransaction(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan, true);
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId) return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build(); .withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build();
@@ -462,60 +500,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} }
} }
/** private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
* waive all charges in the collection try {
* List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
* @param loan for (LoanRepaymentScheduleInstallment installment : installments) {
* Loan object if (installment.getId() == null) {
* @param loanCharges this.repaymentScheduleInstallmentRepository.save(installment);
* collection of LoanCharge objects
* @return void
**/
private void waiveLoanCharges(Loan loan, Collection<LoanCharge> loanCharges) {
AppUser currentUser = this.platformSecurityContext.authenticatedUser();
this.loanAssembler.setHelpers(loan);
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isChargePending()) {
Integer loanInstallmentNumber = null;
if (loanCharge.isInstalmentFee()) {
LoanInstallmentCharge chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
if (chargePerInstallment != null) {
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
}
} }
final Map<String, Object> changes = new LinkedHashMap<>(3);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
LocalDate recalculateFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
recalculateFrom = DateUtils.getLocalDateOfTenant();
}
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
Money accruedCharge = Money.zero(loan.getCurrency());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) {
accruedCharge = accruedCharge.plus(chargePaidByData.getAmount());
}
}
final LoanTransaction loanTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
currentUser);
this.loanTransactionRepository.save(loanTransaction);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
} }
} }
@@ -528,11 +530,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
}
@Override @Override
@Transactional @Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) { public CommandProcessingResult reject(JsonCommand jsonCommand) {
@@ -562,6 +559,11 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
loanRescheduleRequest.reject(appUser, rejectedOnDate); loanRescheduleRequest.reject(appUser, rejectedOnDate);
Set<LoanRescheduleRequestToTermVariationMapping> loanRescheduleRequestToTermVariationMappings = loanRescheduleRequest
.getLoanRescheduleRequestToTermVariationMappings();
for (LoanRescheduleRequestToTermVariationMapping loanRescheduleRequestToTermVariationMapping : loanRescheduleRequestToTermVariationMappings) {
loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().markAsInactive();
}
} }
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId) return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
@@ -593,20 +595,4 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
"Unknown data integrity issue with resource."); "Unknown data integrity issue with resource.");
} }
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
} }
@@ -551,6 +551,22 @@ public class LoanEnumerations {
optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(), optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(),
LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount"); LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount");
break; break;
case GRACE_ON_INTEREST:
optionData = new EnumOptionData(LoanTermVariationType.GRACE_ON_INTEREST.getValue().longValue(),
LoanTermVariationType.GRACE_ON_INTEREST.getCode(), "graceOnInterest");
break;
case GRACE_ON_PRINCIPAL:
optionData = new EnumOptionData(LoanTermVariationType.GRACE_ON_PRINCIPAL.getValue().longValue(),
LoanTermVariationType.GRACE_ON_PRINCIPAL.getCode(), "graceOnPrincipal");
break;
case EXTEND_REPAYMENT_PERIOD:
optionData = new EnumOptionData(LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getValue().longValue(),
LoanTermVariationType.EXTEND_REPAYMENT_PERIOD.getCode(), "extendRepaymentPeriod");
break;
case INTEREST_RATE_FROM_INSTALLMENT:
optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getValue().longValue(),
LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT.getCode(), "interestRateForInstallment");
break;
default: default:
optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(), optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(),
LoanTermVariationType.INVALID.getCode(), "Invalid"); LoanTermVariationType.INVALID.getCode(), "Invalid");
@@ -0,0 +1,141 @@
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `is_active` TINYINT(1) NOT NULL DEFAULT '1' AFTER `applied_on_loan_status`;
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `parent_id` BIGINT(20) NULL DEFAULT NULL AFTER `is_active`;
ALTER TABLE `m_loan_term_variations`
ADD COLUMN `reshedule_request_id` BIGINT(20) NULL DEFAULT NULL AFTER `parent_id`;
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
7 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.grace_on_interest decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.grace_on_interest is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
8 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.grace_on_principal decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.grace_on_principal is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
4 term_type,
mlrr.reschedule_from_date applicable_date,
null decimal_value,
mlrr.adjusted_due_date date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.adjusted_due_date is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
9 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.extra_terms decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.extra_terms is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
2 term_type,
ifnull(mlrr.adjusted_due_date,mlrr.reschedule_from_date) applicable_date,
mlrr.interest_rate decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
null parent_id
from m_loan_reschedule_request mlrr
where mlrr.interest_rate is not null);
insert into m_loan_term_variations(`loan_id`, `term_type`, `applicable_date`, `decimal_value`, `date_value` , `is_specific_to_installment`, `applied_on_loan_status`, `reshedule_request_id` , `is_active` , `parent_id`)
(
select
mlrr.loan_id,
9 term_type,
mlrr.reschedule_from_date applicable_date,
if(ifnull(mlrr.grace_on_principal,0) > ifnull(mlrr.grace_on_interest,0),mlrr.grace_on_principal,mlrr.grace_on_interest) decimal_value,
null date_value,
1 is_specific_to_installment,
300 applied_on_loan_status_id,
mlrr.id reshedule_request_id,
if(mlrr.status_enum = 200 ,1,0) is_active,
(select distinct mlt.id
from m_loan_term_variations mlt
where mlt.reshedule_request_id is not null
and (mlt.term_type =8 or mlt.term_type = 7)
and mlt.reshedule_request_id = mlrr.id
order by mlt.term_type desc limit 1) parent_id
from m_loan_reschedule_request mlrr
where (mlrr.grace_on_interest is not null or mlrr.grace_on_principal is not null));
ALTER TABLE `m_loan_reschedule_request`
DROP COLUMN `grace_on_principal`,
DROP COLUMN `grace_on_interest`,
DROP COLUMN `extra_terms`,
DROP COLUMN `interest_rate`,
DROP COLUMN `adjusted_due_date`;
CREATE TABLE `m_loan_reschedule_request_term_variations_mapping` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_reschedule_request_id` BIGINT(20) NOT NULL,
`loan_term_variations_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
INDEX `FK__m_loan_reschedule_request` (`loan_reschedule_request_id`),
INDEX `FK__m_loan_term_variations` (`loan_term_variations_id`),
CONSTRAINT `FK__m_loan_reschedule_request` FOREIGN KEY (`loan_reschedule_request_id`) REFERENCES `m_loan_reschedule_request` (`id`),
CONSTRAINT `FK__m_loan_term_variations` FOREIGN KEY (`loan_term_variations_id`) REFERENCES `m_loan_term_variations` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;
insert ignore into m_loan_reschedule_request_term_variations_mapping (`loan_reschedule_request_id`, `loan_term_variations_id`)
(
select distinct mltv.reshedule_request_id,mltv.id
from m_loan_term_variations mltv
where reshedule_request_id is not null
);
ALTER TABLE `m_loan_term_variations`
DROP COLUMN `reshedule_request_id`;