From fe6171237e09cb1f347dc6577800ca4c665e361e Mon Sep 17 00:00:00 2001 From: venkatconflux Date: Fri, 29 Jul 2016 18:22:46 +0530 Subject: [PATCH] FINERACT-202 : enabling the option for multi reshedule --- api-docs/apiLive.htm | 90 ++- .../LoanRescheduleRequestTest.java | 2 +- .../data/LoanTermVariationsData.java | 6 +- .../data/LoanTermVariationsDataWrapper.java | 78 +- .../portfolio/loanaccount/domain/Loan.java | 55 +- .../domain/LoanAccountDomainService.java | 2 + .../domain/LoanAccountDomainServiceJpa.java | 23 +- .../LoanRepaymentScheduleInstallment.java | 6 + ...scheduleRequestToTermVariationMapping.java | 53 ++ .../domain/LoanTermVariationType.java | 34 +- .../domain/LoanTermVariations.java | 43 +- .../loanaccount/domain/LoanTransaction.java | 6 + .../domain/AbstractLoanScheduleGenerator.java | 752 +++++++----------- ...gBalanceInterestLoanScheduleGenerator.java | 6 +- .../FlatInterestLoanScheduleGenerator.java | 8 +- .../domain/LoanApplicationTerms.java | 217 ++++- .../domain/LoanScheduleGenerator.java | 25 +- .../service/LoanScheduleAssembler.java | 20 +- ...cheduleCalculationPlatformServiceImpl.java | 8 +- .../api/RescheduleLoansApiResource.java | 22 +- .../data/LoanRescheduleRequestData.java | 71 +- .../LoanRescheduleRequestDataValidator.java | 31 +- .../domain/DefaultLoanReschedulerFactory.java | 62 -- .../domain/LoanRescheduleRequest.java | 240 +++--- .../domain/LoanReschedulerFactory.java | 37 - .../LoanReschedulePreviewPlatformService.java | 4 +- ...nReschedulePreviewPlatformServiceImpl.java | 193 ++--- ...cheduleRequestReadPlatformServiceImpl.java | 69 +- ...heduleRequestWritePlatformServiceImpl.java | 486 ++++++----- .../loanproduct/service/LoanEnumerations.java | 16 + .../V313__multi_rescheduling_script.sql | 141 ++++ 31 files changed, 1496 insertions(+), 1310 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRescheduleRequestToTermVariationMapping.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V313__multi_rescheduling_script.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 696e06622..7fe3fc9de 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -11877,11 +11877,11 @@ Content-Type: application/json Request Body: { "loanId": 1, "graceOnPrincipal": 2, - "rescheduleFromDate": "04 December 2014", - "adjustedDueDate": "20 December 2014", + "rescheduleFromDate": "25 December 2013", + "adjustedDueDate": "31 December 2013", "dateFormat": "dd MMMM yyyy", "locale": "en", - "submittedOnDate": "04 September 2014", + "submittedOnDate": "04 September 2013", "rescheduleReasonComment" : "Client has gone AWOL", "rescheduleReasonId": 1 } @@ -11907,52 +11907,80 @@ GET https://DomainName/api/v1/rescheduleloans/{requestId} { - "id": 2, - "loanId": 17, - "clientId": 4, - "clientName": "Fernando Michael Torres", - "loanAccountNumber": "000000017", + "id": 1, + "loanId": 1, + "clientId": 1, + "clientName": "test test", + "loanAccountNumber": "000000001", "statusEnum": { - "id": 500, - "code": "loanStatusType.rejected", - "value": "Rejected", - "pendingApproval": false, + "id": 100, + "code": "loanStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "pendingApproval": true, "approved": false, - "rejected": true + "rejected": false }, "rescheduleFromInstallment": 5, - "graceOnPrincipal": 3, "rescheduleFromDate": [ 2013, 12, - 6 + 25 ], "recalculateInterest": false, "rescheduleReasonCodeValue": { - "id": 239, - "name": "client cannot pay on time" + "id": 1, + "name": "Passport", + "isActive": false }, "timeline": { "submittedOnDate": [ - 2014, - 8, + 2013, + 9, 4 ], "submittedByUsername": "mifos", "submittedByFirstname": "App", - "submittedByLastname": "Administrator", - "rejectedOnDate": [ - 2014, - 8, - 5 - ], - "rejectedByUsername": "mifos", - "rejectedByFirstname": "App", - "rejectedByLastname": "Administrator" + "submittedByLastname": "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 + } + ] +} +   diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java index 2b00a98b8..ae3943365 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java @@ -211,7 +211,7 @@ public class LoanRescheduleRequestTest { final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); 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()); System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java index f5d2cc034..a19d3b10e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java @@ -29,7 +29,7 @@ public class LoanTermVariationsData implements Comparable exceptionData; - private final ListIterator iterator; + private ListIterator iterator; private final List interestRateChanges; + private final List interestRateFromInstallment; private final List dueDateVariation; - private final ListIterator dueDateIterator; + private ListIterator dueDateIterator; public LoanTermVariationsDataWrapper(final List exceptionData) { if (exceptionData == null) { this.exceptionData = new ArrayList<>(1); } else { this.exceptionData = exceptionData; - Collections.sort(this.exceptionData); } this.interestRateChanges = new ArrayList<>(); this.dueDateVariation = new ArrayList<>(); - for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) { - if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) { - 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(); + this.interestRateFromInstallment = new ArrayList<>(); + deriveLoanTermVariations(); } public boolean hasVariation(final LocalDate date) { @@ -84,6 +74,10 @@ public class LoanTermVariationsDataWrapper { public LoanTermVariationsData nextDueDateVariation() { return this.dueDateIterator.next(); } + + public LoanTermVariationsData previousDueDateVariation() { + return this.dueDateIterator.previous(); + } public List getInterestRateChanges() { return this.interestRateChanges; @@ -96,6 +90,23 @@ public class LoanTermVariationsDataWrapper { public List getExceptionData() { return this.exceptionData; } + + public void setExceptionData(final List exceptionData) { + clearTerms(); + this.exceptionData.addAll(exceptionData); + deriveLoanTermVariations(); + } + + public void clearTerms() { + this.exceptionData.clear(); + this.interestRateChanges.clear(); + this.dueDateVariation.clear(); + this.interestRateFromInstallment.clear(); + } + + public List getInterestRateFromInstallment() { + return this.interestRateFromInstallment; + } public int adjustNumberOfRepayments() { int repaymetsForAdjust = 0; @@ -108,7 +119,7 @@ public class LoanTermVariationsDataWrapper { } return repaymetsForAdjust; } - + public LoanTermVariationsData fetchLoanTermDueDateVariationsData(final LocalDate onDate) { LoanTermVariationsData data = null; for (LoanTermVariationsData termVariationsData : this.dueDateVariation) { @@ -125,4 +136,39 @@ public class LoanTermVariationsDataWrapper { return hasNext(date, iterator); } + public void updateLoanTermVariationsData(final List 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(); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 5bd7a0bf6..adb516e47 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2734,6 +2734,8 @@ public class Loan extends AbstractPersistable { final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, currentStatus); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate()); + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); if (!statusEnum.hasStateOf(currentStatus)) { this.loanStatus = statusEnum.getValue(); actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); @@ -2750,6 +2752,7 @@ public class Loan extends AbstractPersistable { } } boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; + updateLoanToPreDisbursalState(); if (isScheduleRegenerateRequired || isDisbursedAmountChanged || isEmiAmountChanged || this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { @@ -2760,14 +2763,14 @@ public class Loan extends AbstractPersistable { if (isDisbursedAmountChanged) { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } + }else if(isPeriodicAccrualAccountingEnabledOnLoanProduct()){ + for (final LoanRepaymentScheduleInstallment period : getRepaymentScheduleInstallments()) { + period.resetAccrualComponents(); + } } actualChanges.put("actualDisbursementDate", ""); - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - this.accruedTill = null; - reverseExistingTransactions(); updateLoanSummaryDerivedFields(); } @@ -2776,14 +2779,22 @@ public class Loan extends AbstractPersistable { } private final void reverseExistingTransactions() { - + Collection retainTransactions = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { transaction.reverse(); + if(transaction.getId() != null){ + retainTransactions.add(transaction); + } } + this.loanTransactions.retainAll(retainTransactions); + isTransactionsListDirty = true ; } private void updateLoanToPreDisbursalState() { this.actualDisbursementDate = null; + + this.accruedTill = null; + reverseExistingTransactions(); for (final LoanCharge charge : charges()) { if (charge.isOverdueInstallmentCharge()) { @@ -2796,13 +2807,11 @@ public class Loan extends AbstractPersistable { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { currentInstallment.resetDerivedComponents(); } - final List removeTerms = new ArrayList<>(this.loanTermVariations.size()); for (LoanTermVariations variations : this.loanTermVariations) { if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) { - removeTerms.add(variations); + variations.markAsInactive(); } } - this.loanTermVariations.removeAll(removeTerms); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), charges()); @@ -5024,7 +5033,7 @@ public class Loan extends AbstractPersistable { } - private ChangedTransactionDetail processTransactions() { + public ChangedTransactionDetail processTransactions() { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); @@ -5292,9 +5301,8 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(), - retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, - getRepaymentScheduleInstallments(), generatorDTO.getRecalculateFrom()); + return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(), + loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom()); } public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) { @@ -5310,9 +5318,8 @@ public class Loan extends AbstractPersistable { final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(), - scheduleGeneratorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), - loanRepaymentScheduleTransactionProcessor, getRepaymentScheduleInstallments()); + installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this, + scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor); } else { installment = this.getTotalOutstandingOnLoan(); } @@ -5377,10 +5384,12 @@ public class Loan extends AbstractPersistable { return loanApplicationTerms; } - private BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate, + public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate, List loanTermVariations) { for (LoanTermVariations variationTerms : this.loanTermVariations) { - loanTermVariations.add(variationTerms.toData()); + if(variationTerms.isActive()) { + loanTermVariations.add(variationTerms.toData()); + } } annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); return annualNominalInterestRate; @@ -6332,4 +6341,16 @@ public class Loan extends AbstractPersistable { return isForeClosure; } + public Set getActiveLoanTermVariations() { + Set 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; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java index b1052c3f8..407a70677 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -65,4 +65,6 @@ public interface LoanAccountDomainService { * @param loan {@link Loan} object */ void disableStandingInstructionsLinkedToClosedLoan(Loan loan); + + void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index a46b43fc0..f7b1a6b35 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -447,15 +447,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { */ @Override 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(); + if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !isInterestCalcualtionHappened + || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; } + boolean isOrganisationDateEnabled = this.configurationDomainService.isOrganisationstartDateEnabled(); Date organisationStartDate = new Date(); if(isOrganisationDateEnabled){ organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate(); } - - if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled() - || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; } Collection loanScheduleAccrualDatas = new ArrayList<>(); List installments = loan.getRepaymentScheduleInstallments(); 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 constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { Map map = new HashMap<>(1); map.put(entityEvent, entity); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index d95d7412a..c31d1ecb3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -344,6 +344,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.obligationsMet = false; this.obligationsMetOnDate = null; } + + public void resetAccrualComponents() { + this.interestAccrued = null; + this.feeAccrued = null; + this.penaltyAccrued = null; + } public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRescheduleRequestToTermVariationMapping.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRescheduleRequestToTermVariationMapping.java new file mode 100644 index 000000000..af9a8e24e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRescheduleRequestToTermVariationMapping.java @@ -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 { + + + @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; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java index c0df3bbd6..0b12182eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java @@ -26,7 +26,11 @@ public enum LoanTermVariationType { PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), // DUE_DATE(4, "loanTermType.dueDate"), // 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 String code; @@ -66,6 +70,18 @@ public enum LoanTermVariationType { case 6: enumeration = LoanTermVariationType.DELETE_INSTALLMENT; 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; } @@ -93,4 +109,20 @@ public enum LoanTermVariationType { public boolean isDeleteInstallment() { 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()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java index 01245b92a..6dbadad9f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariations.java @@ -23,12 +23,13 @@ import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; - import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; @@ -62,6 +63,13 @@ public class LoanTermVariations extends AbstractPersistable { @Column(name = "applied_on_loan_status", nullable = false) 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, final boolean isSpecificToInstallment, final Loan loan) { @@ -72,6 +80,8 @@ public class LoanTermVariations extends AbstractPersistable { this.dateValue = dateValue; this.isSpecificToInstallment = isSpecificToInstallment; 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, @@ -83,6 +93,21 @@ public class LoanTermVariations extends AbstractPersistable { this.dateValue = dateValue; this.isSpecificToInstallment = isSpecificToInstallment; 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() { @@ -135,5 +160,21 @@ public class LoanTermVariations extends AbstractPersistable { public Integer getOnLoanStatus() { 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; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 414ca14e4..7b16a5c54 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -777,4 +777,10 @@ public final class LoanTransaction extends AbstractPersistable { public boolean isAccrualTransaction() { return isAccrual(); } + + public boolean isPaymentTransaction() { + return this.isNotReversed() + && !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this + .isIncomePosting()); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index d9e57a008..b380b343c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -36,11 +35,9 @@ import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; -import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; @@ -48,7 +45,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; @@ -56,10 +52,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException; -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.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -96,7 +88,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(); - LoanScheduleParams scheduleParams = null; if (loanScheduleParams == null) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), @@ -114,14 +105,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = scheduleParams .getLoanRepaymentScheduleTransactionProcessor(); - final Collection periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, - loanApplicationTerms, chargesDueAtTimeOfDisbursement); + Collection periods = new ArrayList<>(); + if(!scheduleParams.isPartialUpdate()) { + periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, + loanApplicationTerms, chargesDueAtTimeOfDisbursement); + } // Determine the total interest owed over the full loan for FLAT // interest method . - final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( - this.paymentPeriodsInOneYearCalculator, mc); - + if (!scheduleParams.isPartialUpdate()) { + Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( + this.paymentPeriodsInOneYearCalculator, mc); + loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm); + } + boolean isFirstRepayment = true; LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); @@ -167,6 +164,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } final Collection interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); + final Collection interestRatesForInstallments = loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment(); // this block is to start the schedule generation from specified date if (scheduleParams.isPartialUpdate()) { @@ -174,7 +172,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.setPrincipal(scheduleParams.getPrincipalToBeScheduled()); } - applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates); + applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates, interestRatesForInstallments); isFirstRepayment = false; } @@ -185,7 +183,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); - + // calculated interest start date for the period LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, @@ -194,7 +192,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // Loan Schedule Exceptions that need to be applied for Loan Account LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, - previousRepaymentDate, scheduledDueDate); + previousRepaymentDate, scheduledDueDate, interestRatesForInstallments, this.paymentPeriodsInOneYearCalculator, mc); scheduledDueDate = termVariationParams.getScheduledDueDate(); // Updates total days in term @@ -244,7 +242,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // for a loan repayment which falls between the // two periods for interest first repayment strategies handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods, - totalInterestChargedForFullLoanTerm, idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, + loanApplicationTerms.getTotalInterestDue(), idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); if (currentPeriodParams.isSkipCurrentLoop()) { @@ -262,10 +260,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), - scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, + scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, scheduledDueDate, interestRates); + // will check for EMI amount greater than interest calculated if (loanApplicationTerms.getFixedEmiAmount() != null @@ -320,7 +319,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, - loanRepaymentScheduleTransactionProcessor, totalInterestChargedForFullLoanTerm, lastRestDate, scheduledDueDate, + loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); periods.add(installment); @@ -960,11 +959,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param priviousScheduledDueDate * @param previousRepaymentDate * @param scheduledDueDate + * @param interestRatesForInstallments + * @param mc + * @param paymentPeriodsInOneYearCalculator + * @param interestRates + * @param periodsApplicableForGrace * @param scheduleDateForReversal * @return */ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, - final LoanScheduleParams scheduleParams, final LocalDate previousRepaymentDate, final LocalDate scheduledDueDate) { + final LoanScheduleParams scheduleParams, final LocalDate previousRepaymentDate, final LocalDate scheduledDueDate, + Collection interestRatesForInstallments, PaymentPeriodsInOneYearCalculator calculator, + MathContext mc) { boolean skipPeriod = false; boolean recalculateAmounts = false; LocalDate modifiedScheduledDueDate = scheduledDueDate; @@ -981,7 +987,28 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanTermVariationsData.setProcessed(true); } } + + for (LoanTermVariationsData variation : interestRatesForInstallments) { + if (variation.isApplicable(modifiedScheduledDueDate) && variation.getDecimalValue() != null && !variation.isProcessed()) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) { + if (loanApplicationTerms.getActualFixedEmiAmount() == null) { + loanApplicationTerms.setFixedEmiAmount(null); + } + } else { + Money totalInterestDueForLoan = Money.zero(loanApplicationTerms.getCurrency()); + loanApplicationTerms.setTotalPrincipalAccounted(scheduleParams.getTotalCumulativePrincipal()); + totalInterestDueForLoan = loanApplicationTerms.calculateTotalInterestCharged(calculator, mc); + totalInterestDueForLoan = totalInterestDueForLoan.plus(scheduleParams.getTotalCumulativeInterest()); + loanApplicationTerms.updateTotalInterestDue(totalInterestDueForLoan); + // exclude till last period in calculations + loanApplicationTerms.updateExcludePeriodsForCalculation(scheduleParams.getPeriodNumber() - 1); + } + variation.setProcessed(true); + } + } + while (loanApplicationTerms.getLoanTermVariations().hasVariation(modifiedScheduledDueDate)) { LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextVariation(); if (loanTermVariationsData.isProcessed()) { @@ -1026,6 +1053,33 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } loanTermVariationsData.setProcessed(true); break; + case EXTEND_REPAYMENT_PERIOD: + Integer rescheduleNumberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); + rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); + loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); + LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, loanApplicationTerms.getHolidayDetailDTO()); + loanApplicationTerms.updateLoanEndDate(loanEndDate); + loanApplicationTerms.updateAccountedTillPeriod(scheduleParams.getPeriodNumber() - 1, + scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getTotalCumulativeInterest(), + loanTermVariationsData.getDecimalValue().intValue()); + loanApplicationTerms.setFixedEmiAmount(null); + loanTermVariationsData.setProcessed(true); + break; + case GRACE_ON_PRINCIPAL: + loanApplicationTerms.updatePrincipalGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer interestPaymentGrace = 0; + loanApplicationTerms.updateInterestPaymentGrace(interestPaymentGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(scheduleParams.getPeriodNumber()); + loanTermVariationsData.setProcessed(true); + break; + case GRACE_ON_INTEREST: + loanApplicationTerms.updateInterestPaymentGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer principalGrace = 0; + loanApplicationTerms.updatePrincipalGrace(principalGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(scheduleParams.getPeriodNumber()); + loanApplicationTerms.updateTotalInterestAccounted(scheduleParams.getTotalCumulativeInterest()); + loanTermVariationsData.setProcessed(true); + break; default: break; @@ -1040,14 +1094,33 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param loanApplicationTerms * @param scheduledDueDate * @param exceptionDataListIterator + * @param instalmentNumber + * @param totalCumulativePrincipal TODO + * @param totalCumulativeInterest TODO + * @param mc TODO + * @param periodNumber * @return */ private LoanTermVariationParams applyExceptionLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, - final LocalDate scheduledDueDate, final ListIterator exceptionDataListIterator) { + final LocalDate scheduledDueDate, final ListIterator exceptionDataListIterator, int instalmentNumber, + Money totalCumulativePrincipal, Money totalCumulativeInterest, MathContext mc) { boolean skipPeriod = false; boolean recalculateAmounts = false; LocalDate modifiedScheduledDueDate = scheduledDueDate; ArrayList variationsData = new ArrayList<>(); + + for (LoanTermVariationsData variation : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) { + if (variation.isApplicable(modifiedScheduledDueDate) && variation.getDecimalValue() != null && !variation.isProcessed()) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) { + adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, instalmentNumber, mc); + } else { + loanApplicationTerms.setTotalPrincipalAccounted(totalCumulativePrincipal); + loanApplicationTerms.updateExcludePeriodsForCalculation(instalmentNumber - 1); + } + variation.setProcessed(true); + } + } while (loanApplicationTerms.getLoanTermVariations().hasExceptionVariation(modifiedScheduledDueDate, exceptionDataListIterator)) { LoanTermVariationsData loanTermVariationsData = exceptionDataListIterator.next(); @@ -1057,14 +1130,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener switch (loanTermVariationsData.getTermVariationType()) { case INSERT_INSTALLMENT: modifiedScheduledDueDate = loanTermVariationsData.getTermApplicableFrom(); - variationsData.add(loanTermVariationsData) ; + variationsData.add(loanTermVariationsData); break; case DELETE_INSTALLMENT: if (loanTermVariationsData.getTermApplicableFrom().isEqual(modifiedScheduledDueDate)) { skipPeriod = true; - variationsData.add(loanTermVariationsData) ; + variationsData.add(loanTermVariationsData); } - + break; + case GRACE_ON_PRINCIPAL: + loanApplicationTerms.updatePrincipalGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer interestPaymentGrace = 0; + loanApplicationTerms.updateInterestPaymentGrace(interestPaymentGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(instalmentNumber); + variationsData.add(loanTermVariationsData); + break; + case GRACE_ON_INTEREST: + loanApplicationTerms.updateInterestPaymentGrace(loanTermVariationsData.getDecimalValue().intValue()); + Integer principalGrace = 0; + loanApplicationTerms.updatePrincipalGrace(principalGrace); + loanApplicationTerms.updatePeriodNumberApplicableForPrincipalOrInterestGrace(instalmentNumber); + loanApplicationTerms.updateTotalInterestAccounted(totalCumulativeInterest); + variationsData.add(loanTermVariationsData); + break; + case EXTEND_REPAYMENT_PERIOD: + Integer rescheduleNumberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); + rescheduleNumberOfRepayments += loanTermVariationsData.getDecimalValue().intValue(); + loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); + // generate list of proposed schedule due dates + LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, + loanApplicationTerms.getHolidayDetailDTO()); + loanApplicationTerms.updateLoanEndDate(loanEndDate); + adjustInstallmentOrPrincipalAmount(loanApplicationTerms, totalCumulativePrincipal, instalmentNumber, mc); + loanTermVariationsData.setProcessed(true); + loanApplicationTerms.updateAccountedTillPeriod(instalmentNumber-1, totalCumulativePrincipal, totalCumulativeInterest, loanTermVariationsData.getDecimalValue().intValue()); break; default: break; @@ -1080,9 +1179,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param loanApplicationTerms * @param scheduleParams * @param interestRates + * @param interestRatesForInstallments + * @param periodsApplicableForGrace */ private void applyLoanVariationsForPartialScheduleGenerate(final LoanApplicationTerms loanApplicationTerms, - LoanScheduleParams scheduleParams, final Collection interestRates) { + LoanScheduleParams scheduleParams, final Collection interestRates, + final Collection interestRatesForInstallments) { // Applies loan variations while (loanApplicationTerms.getLoanTermVariations().hasVariation(scheduleParams.getPeriodStartDate())) { LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextVariation(); @@ -1109,6 +1211,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); } } + + // Applies interest rate changes for installments + for (LoanTermVariationsData variation : interestRatesForInstallments) { + if (variation.getTermVariationType().isInterestRateFromInstallment() && variation.isApplicable(scheduleParams.getPeriodStartDate()) + && variation.getDecimalValue() != null) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getDecimalValue()); + variation.setProcessed(true); + } + } } /** @@ -1189,6 +1300,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Collection applicableVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); Money uncompoundedFromLastInstallment = params.getUnCompoundedAmount(); LocalDate additionalPeriodsStartDate = params.getPeriodStartDate(); + do { params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(), @@ -1327,18 +1439,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return applicableTransactions; } - private Collection getApplicableTermVariationsForPeriod(final LocalDate fromDate, final LocalDate dueDate, - final Collection variations) { - Collection applicableVariations = new ArrayList<>(); - for (LoanTermVariationsData detail : variations) { - if (detail.isApplicable(fromDate, dueDate)) { - applicableVariations.add(detail); - } - } - variations.removeAll(applicableVariations); - return applicableVariations; - } - private List createCurrentTransactionList(RecalculationDetail detail) { List currentTransactions = new ArrayList<>(2); currentTransactions.add(detail.getTransaction()); @@ -1511,7 +1611,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final boolean isPastDate = params.applyInterestRecalculation() && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); boolean periodHasCompoundingDate = false; - Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + Money amountCharged = Money.zero(currency); + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod() != null) { + amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + } final Map compoundingMap = params.getCompoundingMap(); LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate(); if (loanApplicationTerms.allowCompoundingOnEod()) { @@ -1756,316 +1859,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } - @Override - public LoanRescheduleModel reschedule(final MathContext mathContext, 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) { - - final Loan loan = loanRescheduleRequest.getLoan(); - final LoanSummary loanSummary = loan.getSummary(); - final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); - final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); - - // create an archive of the current loan schedule installments - Collection loanRepaymentScheduleHistoryList = null; - - // get the initial list of repayment installments - List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); - - // sort list by installment number in ASC order - Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator); - - final Collection periods = new ArrayList<>(); - - Money outstandingLoanBalance = loan.getPrincpal(); - - for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) { - - Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber(); - LocalDate fromDate = repaymentScheduleInstallment.getFromDate(); - LocalDate dueDate = repaymentScheduleInstallment.getDueDate(); - Money principalDue = repaymentScheduleInstallment.getPrincipal(currency); - Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency); - Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency); - Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency); - Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue); - - outstandingLoanBalance = outstandingLoanBalance.minus(principalDue); - - LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod - .instance(oldPeriodNumber, oldPeriodNumber, fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue, - feeChargesDue, penaltyChargesDue, totalDue, false); - - periods.add(period); - } - - Money outstandingBalance = loan.getPrincpal(); - Money totalCumulativePrincipal = Money.zero(currency); - Money totalCumulativeInterest = Money.zero(currency); - Money actualTotalCumulativeInterest = Money.zero(currency); - Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); - Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency); - - LocalDate installmentDueDate = null; - LocalDate adjustedInstallmentDueDate = null; - LocalDate installmentFromDate = null; - Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment()); - Integer installmentNumber = rescheduleFromInstallmentNo; - Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal()); - Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest()); - Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms()); - final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest(); - Integer numberOfRepayments = repaymentScheduleInstallments.size(); - Integer rescheduleNumberOfRepayments = numberOfRepayments; - final Money principal = loan.getPrincpal(); - final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding()); - LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate(); - BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate(); - int loanTermInDays = Integer.valueOf(0); - - if (rescheduleFromInstallmentNo > 0) { - // this will hold the loan repayment installment that is before the - // reschedule start installment - // (rescheduleFrominstallment) - LoanRepaymentScheduleInstallment previousInstallment = null; - - // get the install number of the previous installment - int previousInstallmentNo = rescheduleFromInstallmentNo - 1; - - // only fetch the installment if the number is greater than 0 - if (previousInstallmentNo > 0) { - previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo); - } - - LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1); - - // the "installment from date" is equal to the due date of the - // previous installment, if it exists - if (previousInstallment != null) { - installmentFromDate = previousInstallment.getDueDate(); - } - - else { - installmentFromDate = firstInstallment.getFromDate(); - } - - installmentDueDate = installmentFromDate; - LocalDate periodStartDateApplicableForInterest = installmentFromDate; - Integer periodNumber = 1; - outstandingLoanBalance = loan.getPrincpal(); - - for (LoanRescheduleModelRepaymentPeriod period : periods) { - - if (period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) { - - totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue()); - actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue()); - rescheduleNumberOfRepayments--; - outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue()); - outstandingBalance = outstandingBalance.minus(period.principalDue()); - } - } - - while (graceOnPrincipal > 0 || graceOnInterest > 0) { - - LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(), - new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency), - Money.zero(currency), Money.zero(currency), true); - - periods.add(period); - - if (graceOnPrincipal > 0) { - graceOnPrincipal--; - } - - if (graceOnInterest > 0) { - graceOnInterest--; - } - - rescheduleNumberOfRepayments++; - numberOfRepayments++; - } - - while (extraTerms > 0) { - - LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(), - new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency), - Money.zero(currency), Money.zero(currency), true); - - periods.add(period); - - extraTerms--; - rescheduleNumberOfRepayments++; - numberOfRepayments++; - } - - // get the loan application terms from the Loan object - final LoanApplicationTerms loanApplicationTerms = loan - .getLoanApplicationTerms(applicationCurrency, restCalendarInstance, compoundingCalendarInstance, loanCalendar, - floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays, holidayDetailDTO); - - // for applying variations - Collection loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); - - // update the number of repayments - loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); - - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); - LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations().fetchLoanTermDueDateVariationsData( - loanEndDate); - if (lastDueDateVariation != null) { - loanEndDate = lastDueDateVariation.getDateValue(); - } - loanApplicationTerms.updateLoanEndDate(loanEndDate); - - if (newInterestRate != null) { - loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate); - loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate); - } - - graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal()); - graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest()); - - loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest); - loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal); - - loanApplicationTerms.setPrincipal(totalPrincipalOutstanding); - loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments); - loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments); - loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest); - - Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( - this.paymentPeriodsInOneYearCalculator, mathContext); - - if (!recalculateInterest && newInterestRate == null) { - totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged()); - totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest); - - loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm); - } - - for (LoanRescheduleModelRepaymentPeriod period : periods) { - - if (period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate()) - || period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) || period.isNew()) { - - installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms, - false, holidayDetailDTO); - - if (adjustedDueDate != null && periodNumber == 1) { - installmentDueDate = adjustedDueDate; - } - - adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms, - holidayDetailDTO); - - final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays(); - - period.updatePeriodNumber(installmentNumber); - period.updatePeriodFromDate(installmentFromDate); - period.updatePeriodDueDate(adjustedInstallmentDueDate); - - double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator - .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, - adjustedInstallmentDueDate, periodStartDateApplicableForInterest, - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); - - // ========================= Calculate the interest due - // ======================================== - - // change the principal to => Principal Disbursed - Total - // Principal Paid - // interest calculation is always based on the total - // principal outstanding - loanApplicationTerms.setPrincipal(totalPrincipalOutstanding); - - // for applying variations - Collection applicableVariations = getApplicableTermVariationsForPeriod(installmentFromDate, - adjustedInstallmentDueDate, loanTermVariations); - - // determine the interest & principal for the period - PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, - totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, - totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, - null, null, installmentFromDate, adjustedInstallmentDueDate, applicableVariations); - - // update the interest due for the period - period.updateInterestDue(principalInterestForThisPeriod.interest()); - - // ============================================================================================= - - // ========================== Calculate the principal due - // ====================================== - - // change the principal to => Principal Disbursed - Total - // cumulative Principal Amount before the reschedule - // installment - loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod)); - - principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator, - interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, - totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, - loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate, - applicableVariations); - - period.updatePrincipalDue(principalInterestForThisPeriod.principal()); - - // ============================================================================================== - - outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue()); - period.updateOutstandingLoanBalance(outstandingLoanBalance); - - Money principalDue = Money.of(currency, period.principalDue()); - Money interestDue = Money.of(currency, period.interestDue()); - - if (principalDue.isZero() && interestDue.isZero()) { - period.updateFeeChargesDue(Money.zero(currency)); - period.updatePenaltyChargesDue(Money.zero(currency)); - } - - Money feeChargesDue = Money.of(currency, period.feeChargesDue()); - Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue()); - - Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue); - - period.updateTotalDue(totalDue); - - // update cumulative fields for principal & interest - totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue()); - totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue()); - actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue()); - totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); - - installmentFromDate = adjustedInstallmentDueDate; - installmentNumber++; - periodNumber++; - loanTermInDays += daysInInstallment; - - outstandingBalance = outstandingBalance.minus(period.principalDue()); - } - } - } - - final Money totalRepaymentExpected = principal // get the loan Principal - // amount - .plus(actualTotalCumulativeInterest) // add the actual total - // cumulative interest - .plus(loanSummary.getTotalFeeChargesCharged()) // add the total - // fees charged - .plus(loanSummary.getTotalPenaltyChargesCharged()); // finally - // add the - // total - // penalty - // charged - - return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays, - loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(), - actualTotalCumulativeInterest.getAmount(), loanSummary.getTotalFeeChargesCharged(), - loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding()); - } - public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, @@ -2230,28 +2023,28 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * fields to generate the schedule */ @Override - public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List repaymentScheduleInstallments, final LocalDate rescheduleFrom) { + public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, Loan loan, + final HolidayDetailDTO holidayDetailDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom) { // Fixed schedule End Date for generating schedule final LocalDate scheduleTillDate = null; - return rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, transactions, - loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom, scheduleTillDate); + return rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, + rescheduleFrom, scheduleTillDate); } private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List repaymentScheduleInstallments, final LocalDate rescheduleFrom, + Loan loan, final HolidayDetailDTO holidayDetailDTO, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final LocalDate rescheduleFrom, final LocalDate scheduleTillDate) { // Loan transactions to process and find the variation on payments Collection recalculationDetails = new ArrayList<>(); - for (LoanTransaction loanTransaction : transactions) { - recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction - .copyTransactionProperties(loanTransaction))); + for (LoanTransaction loanTransaction : loan.getLoanTransactions()) { + if (loanTransaction.isPaymentTransaction()) { + recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction + .copyTransactionProperties(loanTransaction))); + } } final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled(); @@ -2271,10 +2064,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // this is required to update total fee amounts in the // LoanScheduleModel - final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); + final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.charges()); periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(), loanApplicationTerms, chargesDueAtTimeOfDisbursement); - final List newRepaymentScheduleInstallments = new ArrayList<>(); MonetaryCurrency currency = outstandingBalance.getCurrency(); // early payments will be added here and as per the selected @@ -2302,7 +2094,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Map> compoundingDateVariations = new HashMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; - if (loanApplicationTerms.getRestCalendarInstance() != null) { + if (loanApplicationTerms.isInterestRecalculationEnabled()) { lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); @@ -2323,6 +2115,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate(); // Set fixed Amortization Amounts(either EMI or Principal ) updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance); + + // count periods without interest grace to exclude for flat loan calculations final Map disburseDetailMap = new HashMap<>(); if (loanApplicationTerms.isMultiDisburseLoan()) { @@ -2340,12 +2134,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List exceptionDataList = loanApplicationTerms.getLoanTermVariations().getExceptionData(); final ListIterator exceptionDataListIterator = exceptionDataList.listIterator(); LoanTermVariationParams loanTermVariationParams = null; + + // identify retain installments + final List processInstallmentsInstallments = fetchRetainedInstallments( + loan.getRepaymentScheduleInstallments(), rescheduleFrom, currency); + final List newRepaymentScheduleInstallments = new ArrayList<>(); // Block process the installment and creates the period if it falls // before reschedule from date // This will create the recalculation details by applying the // transactions - for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { + for (LoanRepaymentScheduleInstallment installment : processInstallmentsInstallments) { // this will generate the next schedule due date and allows to // process the installment only if recalculate from date is // greater than due date @@ -2353,32 +2152,44 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { break; } - LocalDate previousRepaymentDate = actualRepaymentDate; ArrayList dueDateVariationsDataList = new ArrayList<>(); + // check for date changes - while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { - LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); - if (!variation.isSpecificToInstallment()) { - actualRepaymentDate = variation.getDateValue(); - } - dueDateVariationsDataList.add(variation); - } - + do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); + while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { + LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); + if (!variation.isSpecificToInstallment()) { + actualRepaymentDate = variation.getDateValue(); + /*if (!isDueDateChangeApplied) { + previousRepaymentDate = actualRepaymentDate; + isDueDateChangeApplied = true; + }*/ + lastInstallmentDate = actualRepaymentDate; + } + dueDateVariationsDataList.add(variation); + } loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate, - exceptionDataListIterator); + exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc); } while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()); - if (!lastInstallmentDate.isBefore(rescheduleFrom)) { + /*if (!lastInstallmentDate.isBefore(rescheduleFrom)) { actualRepaymentDate = previousRepaymentDate; + if(isDueDateChangeApplied){ + int numberOfDateChangesApplied = dueDateVariationsDataList.size(); + while(numberOfDateChangesApplied >0 ){ + loanApplicationTerms.getLoanTermVariations().previousDueDateVariation(); + numberOfDateChangesApplied--; + } + } break; - } + }*/ periodNumber++; for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) { @@ -2422,66 +2233,81 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalPenaltyChargesCharged = totalPenaltyChargesCharged.plus(installment.getPenaltyChargesCharged(currency)); instalmentNumber++; loanTermInDays = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays(); + + + if (loanApplicationTerms.isInterestRecalculationEnabled()) { - // populates the collection with transactions till the due date - // of - // the period for interest recalculation enabled loans - Collection applicableTransactions = getApplicableTransactionsForPeriod(applyInterestRecalculation, - installment.getDueDate(), recalculationDetails); + // populates the collection with transactions till the due + // date + // of + // the period for interest recalculation enabled loans + Collection applicableTransactions = getApplicableTransactionsForPeriod(applyInterestRecalculation, + installment.getDueDate(), recalculationDetails); - // calculates the expected principal value for this repayment - // schedule - Money principalPortionCalculated = principalToBeScheduled.zero(); - if (!installment.isRecalculatedInterestComponent()) { - principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency), - loanApplicationTerms); + // calculates the expected principal value for this + // repayment + // schedule + Money principalPortionCalculated = principalToBeScheduled.zero(); + if (!installment.isRecalculatedInterestComponent()) { + principalPortionCalculated = calculateExpectedPrincipalPortion(installment.getInterestCharged(currency), + loanApplicationTerms); + } + + // expected principal considering the previously paid excess + // amount + Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); + if (actualPrincipalPortion.isLessThanZero()) { + actualPrincipalPortion = principalPortionCalculated.zero(); + } + + Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, + installment, applicableTransactions, actualPrincipalPortion); + + // this block is to adjust the period number based on the + // actual + // schedule due date and installment due date + // recalculatedInterestComponent installment shouldn't be + // considered while calculating fixed EMI amounts + int period = periodNumber; + if (!lastInstallmentDate.isEqual(installment.getDueDate())) { + period--; + } + reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, + loanApplicationTerms, totalCumulativePrincipal, period, mc); + // Updates principal paid map with efective date for + // reducing + // the amount from outstanding balance(interest calculation) + LocalDate amountApplicableDate = null; + if (loanApplicationTerms.getRestCalendarInstance() != null) { + amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + } + + // updates map with the installment principal amount + // excluding + // unprocessed amount since this amount is already + // accounted. + updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); + uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, + principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, + holidayDetailDTO); + + // update outstanding balance for interest calculation + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), + outstandingBalanceAsPerRest, false); + outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), + outstandingBalanceAsPerRest, true); + // updates the map with over due amounts + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, + newRepaymentScheduleInstallments, true, lastRestDate); + } else { + outstandingBalanceAsPerRest = outstandingBalance; } - - // expected principal considering the previously paid excess - // amount - Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); - if (actualPrincipalPortion.isLessThanZero()) { - actualPrincipalPortion = principalPortionCalculated.zero(); - } - - Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, - loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, - installment, applicableTransactions, actualPrincipalPortion); - - // this block is to adjust the period number based on the actual - // schedule due date and installment due date - // recalculatedInterestComponent installment shouldn't be - // considered while calculating fixed EMI amounts - int period = periodNumber; - if (!lastInstallmentDate.isEqual(installment.getDueDate())) { - period--; - } - reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, - loanApplicationTerms, totalCumulativePrincipal, period, mc); - // Updates principal paid map with efective date for reducing - // the amount from outstanding balance(interest calculation) - LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, - holidayDetailDTO); - // updates map with the installment principal amount excluding - // unprocessed amount since this amount is already accounted. - updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); - uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, - principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, - holidayDetailDTO); - // update outstanding balance for interest calculation - outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), - outstandingBalanceAsPerRest, false); - outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), - outstandingBalanceAsPerRest, true); - } totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest).plus(totalFeeChargesCharged) .plus(totalPenaltyChargesCharged); - // updates the map with over due amounts - updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, - newRepaymentScheduleInstallments, true, lastRestDate); - // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) { Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); @@ -2494,6 +2320,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); + loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getLoanSummary().getTotalInterestCharged())); + }else{ + loanApplicationTerms.getLoanTermVariations().resetVariations(); } } @@ -2501,9 +2330,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (loanScheduleParams == null) { loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); + periods.clear(); } - - LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleParams); + LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.charges(), holidayDetailDTO, loanScheduleParams); for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { // adding newly created repayment periods to installments @@ -2515,6 +2344,41 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); } + public List fetchRetainedInstallments( + final List repaymentScheduleInstallments, final LocalDate rescheduleFrom, + MonetaryCurrency currency) { + List newRepaymentScheduleInstallments = new ArrayList<>(); + int lastInterestAvilablePeriod = 0; + int processedPeriod = 0; + for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { + if (installment.getDueDate().isBefore(rescheduleFrom)) { + newRepaymentScheduleInstallments.add(installment); + if(installment.getInterestCharged(currency).isGreaterThanZero()){ + lastInterestAvilablePeriod = installment.getInstallmentNumber(); + } + processedPeriod = installment.getInstallmentNumber(); + } else { + break; + } + } + + // this block is to remove the periods till last interest available + // period. + // if the last retained period is interest grace period then we + // can't get the interest of last period without calculating again + // to fix this adjusting retained periods + if(lastInterestAvilablePeriod != processedPeriod){ + final List retainRepaymentScheduleInstallments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment installment : newRepaymentScheduleInstallments) { + if(installment.getInstallmentNumber() <= lastInterestAvilablePeriod){ + retainRepaymentScheduleInstallments.add(installment); + } + } + newRepaymentScheduleInstallments.retainAll(retainRepaymentScheduleInstallments); + } + return newRepaymentScheduleInstallments; + } + /** * Method identifies the early paid amounts for a installment and update the * principal map for further calculations @@ -2630,7 +2494,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener earlyPaidAmount = earlyPaidAmount.zero(); } - if (isEarlyPaid) { + if (isEarlyPaid && applicationTerms.getRescheduleStrategyMethod() != null) { switch (applicationTerms.getRescheduleStrategyMethod()) { case REDUCE_EMI_AMOUNT: adjustInstallmentOrPrincipalAmount(applicationTerms, totalCumulativePrincipal, periodNumber, mc); @@ -2727,21 +2591,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener */ @Override public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate, - final LoanApplicationTerms loanApplicationTerms, final MathContext mc, final Set charges, - final HolidayDetailDTO holidayDetailDTO, final List loanTransactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List repaymentScheduleInstallments) { + final LoanApplicationTerms loanApplicationTerms, final MathContext mc, Loan loan, final HolidayDetailDTO holidayDetailDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { LocalDate calculateTill = onDate; if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { calculateTill = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } - LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, charges, holidayDetailDTO, loanTransactions, - loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, onDate, calculateTill); + LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, + onDate, calculateTill); + List loanTransactions = loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, - currency, loanScheduleDTO.getInstallments(), charges); + currency, loanScheduleDTO.getInstallments(), loan.charges()); Money feeCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency); Money totalPrincipal = Money.zero(currency); @@ -2759,15 +2622,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } - /** - * 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; - } private final class LoanTermVariationParams { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index 1bbefa4bd..1bbf261d2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -73,6 +73,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; Map interestRates = new HashMap<>(termVariations.size()); + for (LoanTermVariationsData loanTermVariation : termVariations) { if (loanTermVariation.getTermVariationType().isInterestRateVariation() && loanTermVariation.isApplicable(periodStartDate, periodEndDate)) { @@ -86,6 +87,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } } + if (principalVariation != null) { for (Map.Entry principal : principalVariation.entrySet()) { @@ -99,6 +101,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); interestStartDate = principal.getKey(); + } Money compoundFee = totalCumulativePrincipal.zero(); if (compoundingMap.containsKey(principal.getKey())) { @@ -118,13 +121,14 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); } } - } } + final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, balanceForInterestCalculation, interestStartDate, periodEndDate); + interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java index 31cdbe52b..5f3d8d7c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java @@ -32,11 +32,12 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener @Override public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator, 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, @SuppressWarnings("unused") TreeMap principalVariation, @SuppressWarnings("unused") Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, @SuppressWarnings("unused") Collection termVariations) { + Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance, periodNumber, mc, null); @@ -54,6 +55,11 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment, 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, totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index c1f91eaf0..074652222 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -21,7 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.math.MathContext; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; @@ -192,6 +194,25 @@ public final class LoanApplicationTerms { private final boolean isSkipRepaymentOnFirstDayOfMonth; private final HolidayDetailDTO holidayDetailDTO; + + private final Set periodNumbersApplicableForPrincipalGrace = new HashSet<>(); + + private final Set 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, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -482,6 +503,12 @@ public final class LoanApplicationTerms { this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; this.holidayDetailDTO = holidayDetailDTO; 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, @@ -706,7 +733,8 @@ public final class LoanApplicationTerms { switch (this.interestMethod) { case FLAT: final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); - totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); + totalInterestDue = this.principal.minus(totalPrincipalAccountedForInterestCalcualtion).multiplyRetainScale(interestRateForLoanTerm, + mc.getRoundingMode()); break; case DECLINING_BALANCE: @@ -715,10 +743,6 @@ public final class LoanApplicationTerms { break; } - if (this.totalInterestDue != null) { - totalInterestDue = this.totalInterestDue; - } - return totalInterestDue; } @@ -844,13 +868,19 @@ public final class LoanApplicationTerms { private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); - - return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode()); + 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 interestPerInstallment; } private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) { 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); if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { principalPerPeriod = principalPerPeriod.zero(); @@ -906,11 +936,30 @@ public final class LoanApplicationTerms { final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace()); final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree); - - final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments); - + + + + Integer interestPaymentDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments, + this.excludePeriodsForCalculation); interestForInstallment = realTotalInterestForLoan .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; @@ -1032,24 +1081,46 @@ public final class LoanApplicationTerms { private int calculateNumberOfRepaymentsWithPrincipalPayment() { int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), 0); + this.periodsCompleted); +// numPeriods = numPeriods - this.periodsCompleted; return numPeriods; } - - private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) { - return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace()); + + private Integer calculateNumberOfRemainingInterestPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) { + 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) { - if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) { - return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this - .getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1))); + boolean isPrincipalGraceApplicableForThisPeriod = false; + if (this.periodNumbersApplicableForPrincipalGrace.contains(periodNumber)) { + isPrincipalGraceApplicableForThisPeriod = true; } - return periodNumber > 0 && periodNumber <= getPrincipalGrace(); + return isPrincipalGraceApplicableForThisPeriod; } - private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); + public boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { + boolean isInterestPaymentGraceApplicableForThisPeriod = false; + if (this.periodNumbersApplicableForInterestGrace.contains(periodNumber)) { + isInterestPaymentGraceApplicableForThisPeriod = true; + } + return isInterestPaymentGraceApplicableForThisPeriod; } 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 Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodsElapsed); + periodsElapsed); double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), principalDouble, futureValue, false); @@ -1165,7 +1236,7 @@ public final class LoanApplicationTerms { Money principal = this.principal; if (this.fixedPrincipalAmount == null) { final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( - this.actualNumberOfRepayments, this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber); + this.actualNumberOfRepayments, periodNumber); principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); this.fixedPrincipalAmount = principal.getAmount(); } @@ -1179,21 +1250,19 @@ public final class LoanApplicationTerms { public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) { final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber - 1); + periodNumber - 1); Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); this.fixedPrincipalAmount = principal.getAmount(); } - private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, - final int recurringMoratoriumOnPrincipalPeriods, final int PrincipalGrace, int periodsElapsed) { - if (PrincipalGrace > periodsElapsed) { - periodsElapsed = PrincipalGrace; - } - Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed; - if (recurringMoratoriumOnPrincipalPeriods > 0) { - double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1))); - periodsRemaining = (int) Math.ceil(periodsRemainingDouble); + private Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, int periodsElapsed) { + int principalFeePeriods = 0; + for (Integer intNumber : this.periodNumbersApplicableForPrincipalGrace) { + if (intNumber > periodsElapsed) { + principalFeePeriods++; + } } + Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods; return periodsRemaining; } @@ -1469,10 +1538,7 @@ public final class LoanApplicationTerms { } public void updateTotalInterestDue(Money totalInterestDue) { - - if (totalInterestDue != null) { - this.totalInterestDue = totalInterestDue; - } + this.totalInterestDue = totalInterestDue; } public ApplicationCurrency getApplicationCurrency() { @@ -1576,4 +1642,81 @@ public final class LoanApplicationTerms { } 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; + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index ae6745171..9583ac4f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -19,22 +19,15 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; -import java.util.List; import java.util.Set; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; 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.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; 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.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; public interface LoanScheduleGenerator { @@ -42,18 +35,12 @@ public interface LoanScheduleGenerator { LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, final HolidayDetailDTO holidayDetailDTO); - LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, - final HolidayDetailDTO holidayDetailDTO, List transactions, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - List repaymentScheduleInstallments, LocalDate rescheduleFrom); + LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan, + final HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + LocalDate rescheduleFrom); LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, - LoanApplicationTerms loanApplicationTerms, MathContext mc, Set charges, HolidayDetailDTO holidayDetailDTO, - List loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - List repaymentScheduleInstallments); + LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO, + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); - 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); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index ac4a368fa..9565ff3f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -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.LoanTermVariationType; 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.exception.LoanApplicationDateException; import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException; @@ -621,9 +620,8 @@ public class LoanScheduleAssembler { } public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, - List transactions, final Set loanCharges, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List repaymentScheduleInstallments, final LocalDate rescheduleFrom) { + Loan loan, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final LocalDate rescheduleFrom) { final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final MathContext mc = new MathContext(8, roundingMode); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); @@ -634,15 +632,13 @@ public class LoanScheduleAssembler { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, - loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments, rescheduleFrom).getLoanScheduleModel(); + return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO, + loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel(); } public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, - LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final Long officeId, - List loanTransactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List repaymentScheduleInstallments) { + LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final MathContext mc = new MathContext(8, roundingMode); @@ -653,8 +649,8 @@ public class LoanScheduleAssembler { final WorkingDays workingDays = this.workingDaysRepository.findOne(); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loanCharges, holidayDetailDTO, - loanTransactions, loanRepaymentScheduleTransactionProcessor, repaymentScheduleInstallments); + return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loan, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 41e8313e6..a8ea977ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -171,9 +171,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan); LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency, - today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), - loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, - loan.getRepaymentScheduleInstallments()); + today, loanApplicationTerms, loan, loan.getOfficeId(), + loanRepaymentScheduleTransactionProcessor); Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); Money interestDue = Money.zero(currency); @@ -194,8 +193,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), - modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, loan.getRepaymentScheduleInstallments(), - loan.fetchInterestRecalculateFromDate()); + loan, loanRepaymentScheduleTransactionProcessor, loan.fetchInterestRecalculateFromDate()); LoanScheduleData scheduleDate = model.toData(); Collection periodDatas = scheduleDate.getPeriods(); for (LoanSchedulePeriodData periodData : periodDatas) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java index 2094a0596..591e21b43 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/api/RescheduleLoansApiResource.java @@ -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.security.service.PlatformSecurityContext; 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.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.LoanRescheduleRequestReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; @@ -108,14 +108,14 @@ public class RescheduleLoansApiResource { final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); 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()); } final LoanRescheduleRequestData loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService .readLoanRescheduleRequest(scheduleId); - + return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestData); } @@ -170,4 +170,20 @@ public class RescheduleLoansApiResource { private boolean compareIgnoreCase(String firstString, String 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); + }*/ } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java index b74388cae..2a6dfbd80 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data; -import java.math.BigDecimal; import java.util.Collection; import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.joda.time.LocalDate; /** @@ -36,59 +36,55 @@ public class LoanRescheduleRequestData { private final String loanAccountNumber; private final LoanRescheduleRequestStatusEnumData statusEnum; private final Integer rescheduleFromInstallment; - private final Integer graceOnPrincipal; - private final Integer graceOnInterest; private final LocalDate rescheduleFromDate; - private final Integer extraTerms; - private final BigDecimal interestRate; private final Boolean recalculateInterest; private final CodeValueData rescheduleReasonCodeValue; private final LoanRescheduleRequestTimelineData timeline; private final String rescheduleReasonComment; - private final LocalDate adjustedDueDate; + @SuppressWarnings("unused") private final Collection rescheduleReasons; + @SuppressWarnings("unused") + private final Collection loanTermVariationsData; /** * LoanRescheduleRequestData constructor + * @param loanTermVariationsData TODO **/ private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum, - Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate, - LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue, + Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName, - final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection rescheduleReasons) { + final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, + Collection rescheduleReasons, final Collection loanTermVariationsData) { this.id = id; this.loanId = loanId; 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.adjustedDueDate = adjustedDueDate; this.timeline = timeline; this.clientName = clientName; this.loanAccountNumber = loanAccountNumber; this.clientId = clientId; this.recalculateInterest = recalculateInterest; this.rescheduleReasons = rescheduleReasons ; + this.loanTermVariationsData = loanTermVariationsData; } /** + * @param loanTermVariationsData TODO * @return an instance of the LoanRescheduleRequestData class **/ public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum, - Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest, LocalDate rescheduleFromDate, - LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate, CodeValueData rescheduleReasonCodeValue, + Integer rescheduleFromInstallment, LocalDate rescheduleFromDate, CodeValueData rescheduleReasonCodeValue, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline, final String clientName, - final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, Collection rescheduleReasons) { + final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest, + Collection rescheduleReasons, final Collection loanTermVariationsData) { - return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, - rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, - timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); + return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate, + rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest, + rescheduleReasons, loanTermVariationsData); } /** @@ -119,20 +115,6 @@ public class LoanRescheduleRequestData { return rescheduleFromInstallment; } - /** - * @return the graceOnPrincipal - */ - public Integer getGraceOnPrincipal() { - return graceOnPrincipal; - } - - /** - * @return the graceOnInterest - */ - public Integer getGraceOnInterest() { - return graceOnInterest; - } - /** * @return the reschedule from date */ @@ -140,20 +122,6 @@ public class LoanRescheduleRequestData { return rescheduleFromDate; } - /** - * @return the extraTerms - */ - public Integer getExtraTerms() { - return extraTerms; - } - - /** - * @return the interestRate - */ - public BigDecimal getInterestRate() { - return interestRate; - } - /** * @return the rescheduleReasonCodeValueId */ @@ -175,13 +143,6 @@ public class LoanRescheduleRequestData { return this.timeline; } - /** - * @return the adjustedDueDate - */ - public LocalDate getAdjustedDueDate() { - return adjustedDueDate; - } - /** * @return the clientName */ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java index 525418706..67e0f8082 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java @@ -32,10 +32,8 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; 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.domain.LoanRescheduleRequest; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -47,13 +45,10 @@ import com.google.gson.reflect.TypeToken; public class LoanRescheduleRequestDataValidator { private final FromJsonHelper fromJsonHelper; - private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService; @Autowired - public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper, - LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService) { + public LoanRescheduleRequestDataValidator(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"); } - 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 = 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()) { dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode, "Loan rescheduling is not supported for multidisbursement loans"); @@ -232,7 +214,6 @@ public class LoanRescheduleRequestDataValidator { final Loan loan = loanRescheduleRequest.getLoan(); if (loan != null) { - Long loanId = loan.getId(); if (!loan.status().isActive()) { 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"); } } - - if (loanId != null) { - List 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); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java deleted file mode 100644 index bcb7bc4c2..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java +++ /dev/null @@ -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; - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java index 88930547a..8a0a8441c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java @@ -18,20 +18,27 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain; -import java.math.BigDecimal; 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.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.apache.fineract.infrastructure.codes.domain.CodeValue; 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.LoanTermVariations; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -40,116 +47,97 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_loan_reschedule_request") public class LoanRescheduleRequest extends AbstractPersistable { - @ManyToOne + @ManyToOne @JoinColumn(name = "loan_id", nullable = false) private Loan loan; - @Column(name = "status_enum", nullable = false) - private Integer statusEnum; - - @Column(name = "reschedule_from_installment") - private Integer rescheduleFromInstallment; - - @Column(name = "grace_on_principal") - private Integer graceOnPrincipal; - - @Column(name = "grace_on_interest") - private Integer graceOnInterest; - - @Temporal(TemporalType.DATE) + @Column(name = "status_enum", nullable = false) + private Integer statusEnum; + + @Column(name = "reschedule_from_installment") + private Integer rescheduleFromInstallment; + + @Temporal(TemporalType.DATE) @Column(name = "reschedule_from_date") - private Date rescheduleFromDate; - - @Temporal(TemporalType.DATE) - @Column(name = "adjusted_due_date") - private Date adjustedDueDate; - - @Column(name = "extra_terms") - private Integer extraTerms; - - @Column(name = "recalculate_interest") - private Boolean recalculateInterest; - - @Column(name = "interest_rate", scale = 6, precision = 19) - private BigDecimal interestRate; - - @ManyToOne + private Date rescheduleFromDate; + + @Column(name = "recalculate_interest") + private Boolean recalculateInterest; + + @ManyToOne @JoinColumn(name = "reschedule_reason_cv_id") - private CodeValue rescheduleReasonCodeValue; - - @Column(name = "reschedule_reason_comment") - private String rescheduleReasonComment; - - @Temporal(TemporalType.DATE) + private CodeValue rescheduleReasonCodeValue; + + @Column(name = "reschedule_reason_comment") + private String rescheduleReasonComment; + + @Temporal(TemporalType.DATE) @Column(name = "submitted_on_date") - private Date submittedOnDate; - - @ManyToOne + private Date submittedOnDate; + + @ManyToOne @JoinColumn(name = "submitted_by_user_id") - private AppUser submittedByUser; - - @Temporal(TemporalType.DATE) + private AppUser submittedByUser; + + @Temporal(TemporalType.DATE) @Column(name = "approved_on_date") - private Date approvedOnDate; + private Date approvedOnDate; - @ManyToOne + @ManyToOne @JoinColumn(name = "approved_by_user_id") - private AppUser approvedByUser; + private AppUser approvedByUser; - @Temporal(TemporalType.DATE) + @Temporal(TemporalType.DATE) @Column(name = "rejected_on_date") - private Date rejectedOnDate; + private Date rejectedOnDate; - @ManyToOne + @ManyToOne @JoinColumn(name = "rejected_by_user_id") - private AppUser rejectedByUser; + private AppUser rejectedByUser; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.EAGER) + @JoinColumn(name = "loan_reschedule_request_id", nullable = false) + private Set loanRescheduleRequestToTermVariationMappings = new HashSet<>(); - /** - * LoanRescheduleRequest constructor - **/ - protected LoanRescheduleRequest() {} + /** + * LoanRescheduleRequest constructor + **/ + protected LoanRescheduleRequest() {} + + /** + * LoanRescheduleRequest constructor + **/ + private LoanRescheduleRequest(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) { + 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; + } - /** - * LoanRescheduleRequest constructor - **/ - private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, - 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 - **/ - public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment, - 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) { - - return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, - rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate, - submittedByUser, approvedOnDate, approvedByUser, rejectedOnDate, rejectedByUser); - } + /** + * @return a new instance of the LoanRescheduleRequest class + **/ + 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 @@ -172,20 +160,6 @@ public class LoanRescheduleRequest extends AbstractPersistable { 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 **/ @@ -200,34 +174,6 @@ public class LoanRescheduleRequest extends AbstractPersistable { 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 **/ @@ -348,4 +294,24 @@ public class LoanRescheduleRequest extends AbstractPersistable { this.statusEnum = LoanStatus.REJECTED.getValue(); } } + + public void updateLoanRescheduleRequestToTermVariationMappings(final List mapping) { + this.loanRescheduleRequestToTermVariationMappings.addAll(mapping); + } + + public Set 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; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java deleted file mode 100644 index f2e8bc765..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java +++ /dev/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); -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java index b300b4df0..f91649eeb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformService.java @@ -18,9 +18,9 @@ */ 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 LoanRescheduleModel previewLoanReschedule(Long requestId); + public LoanScheduleModel previewLoanReschedule(Long requestId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index 64040af41..f6a918d59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -18,41 +18,33 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; -import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; +import java.util.ArrayList; 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.workingdays.domain.WorkingDays; -import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; -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.LoanTermVariationsData; +import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; -import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping; +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.LoanRescheduleRequestRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; 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.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -60,103 +52,94 @@ import org.springframework.stereotype.Service; public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService { 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 LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; + private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final LoanSummaryWrapper loanSummaryWrapper; + private final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); @Autowired public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository, - final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, - final WorkingDaysRepositoryWrapper workingDaysRepository, - final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, - final CalendarInstanceRepository calendarInstanceRepository, - final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService) { + final LoanUtilService loanUtilService, + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper) { 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.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; + this.loanScheduleFactory = loanScheduleFactory; + this.loanSummaryWrapper = loanSummaryWrapper; } @Override - public LoanRescheduleModel previewLoanReschedule(Long requestId) { + public LoanScheduleModel previewLoanReschedule(Long requestId) { final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId); if (loanRescheduleRequest == null) { throw new LoanRescheduleRequestNotFoundException(requestId); } Loan loan = loanRescheduleRequest.getLoan(); - final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan - .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); - 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(); + ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, + loanRescheduleRequest.getRescheduleFromDate()); + LocalDate rescheduleFromDate = null; + List removeLoanTermVariationsData = new ArrayList<>(); + final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists(); + 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 = new ArrayList<>(); + LocalDate adjustedApplicableDate = null; + Set 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()); + } + } + + 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 MathContext mathContext = new MathContext(8, roundingMode); - List oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( - loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); - HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - CalendarInstance restCalendarInstance = null; - CalendarInstance compoundingCalendarInstance = null; - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); - compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); - } - 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(); } - - } - 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 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; + 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); + final LoanScheduleModel loanScheduleModel = loanSchedule.getLoanScheduleModel(); + LoanScheduleModel loanScheduleModels = LoanScheduleModel.withLoanScheduleModelPeriods(loanScheduleModel.getPeriods(), + loanScheduleModel); + + return loanScheduleModels; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java index c88036476..19f6b792d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestReadPlatformServiceImpl.java @@ -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.core.domain.JdbcSupport; 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.LoanRepository; 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.LoanRescheduleRequestStatusEnumData; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData; +import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -71,13 +73,8 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched sqlBuilder.append("mc.id as clientId, "); sqlBuilder.append("ml.account_no as loanAccountNumber, "); 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.adjusted_due_date as adjustedDueDate, "); - sqlBuilder.append("lr.extra_terms as extraTerms, "); 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("cv.code_value as rescheduleReasonCvValue, "); 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("rbu.username as rejectedByUsername, "); 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("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_loan ml on ml.id = lr.loan_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(); } @@ -127,12 +133,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched final String loanAccountNumber = rs.getString("loanAccountNumber"); final Long clientId = rs.getLong("clientId"); 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 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 String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue"); final CodeValueData rescheduleReasonCodeValue = CodeValueData.instance(rescheduleReasonCvId, rescheduleReasonCvValue); @@ -157,14 +158,40 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched final LoanRescheduleRequestTimelineData timeline = new LoanRescheduleRequestTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname); + + Collection loanTermVariations = new ArrayList<>(); + + 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, graceOnPrincipal, graceOnInterest, - rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, - timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); + 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; } } - + @Override public List readLoanRescheduleRequests(Long loanId) { final Loan loan = this.loanRepository.findOne(loanId); @@ -209,12 +236,7 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched final Long loanId = null; final LoanRescheduleRequestStatusEnumData statusEnum = null; final Integer rescheduleFromInstallment = null; - final Integer graceOnPrincipal = null; - final Integer graceOnInterest = null; final LocalDate rescheduleFromDate = null; - final LocalDate adjustedDueDate = null; - final Integer extraTerms = null; - final BigDecimal interestRate = null; final CodeValueData rescheduleReasonCodeValue = null; final String rescheduleReasonComment = null; final LoanRescheduleRequestTimelineData timeline = null; @@ -222,9 +244,10 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched final String loanAccountNumber = null; final Long clientId = null; final Boolean recalculateInterest = null; + final Collection loanTermVariationsData = null; - return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, - rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, - timeline, clientName, loanAccountNumber, clientId, recalculateInterest, rescheduleReasons); + return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, rescheduleFromDate, + rescheduleReasonCodeValue, rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest, + rescheduleReasons, loanTermVariationsData); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 74ec62413..6c70d7c55 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -22,9 +22,9 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,64 +33,52 @@ import java.util.Set; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.codes.domain.CodeValue; 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.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.service.DateUtils; 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.ApplicationCurrencyRepositoryWrapper; 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.workingdays.domain.WorkingDays; -import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; -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.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; 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.LoanCharge; -import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; -import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; 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.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping; 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.LoanTermVariationType; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; 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.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.rescheduleloan.RescheduleLoansApiConstants; 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.LoanRescheduleRequestRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; 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.loanproduct.domain.InterestMethod; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; 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 LoanRepositoryWrapper loanRepositoryWrapper; private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; private final PlatformSecurityContext platformSecurityContext; private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator; private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; - private final ConfigurationDomainService configurationDomainService; - private final HolidayRepositoryWrapper holidayRepository; - private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; - private final CalendarInstanceRepository calendarInstanceRepository; - private final LoanChargeReadPlatformService loanChargeReadPlatformService; private final LoanTransactionRepository loanTransactionRepository; private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final LoanRepository loanRepository; private final LoanAssembler loanAssembler; - private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; 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 @@ -133,38 +121,39 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche * @return void **/ @Autowired - public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper, - CodeValueRepositoryWrapper codeValueRepositoryWrapper, PlatformSecurityContext platformSecurityContext, - LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator, - LoanRescheduleRequestRepository loanRescheduleRequestRepository, - ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService, - HolidayRepositoryWrapper holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, - LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository, + public LoanRescheduleRequestWritePlatformServiceImpl(final CodeValueRepositoryWrapper codeValueRepositoryWrapper, + final PlatformSecurityContext platformSecurityContext, + final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator, + final LoanRescheduleRequestRepository loanRescheduleRequestRepository, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository, final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, - final CalendarInstanceRepository calendarInstanceRepository, final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanTransactionRepository loanTransactionRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository, - final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService, - final LoanUtilService loanUtilService) { - this.loanRepositoryWrapper = loanRepositoryWrapper; + final LoanAssembler loanAssembler, final LoanUtilService loanUtilService, + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper, + final AccountTransfersWritePlatformService accountTransfersWritePlatformService, + final LoanAccountDomainService loanAccountDomainService, + final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) { this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; this.platformSecurityContext = platformSecurityContext; this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator; this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.applicationCurrencyRepository = applicationCurrencyRepository; - this.configurationDomainService = configurationDomainService; - this.holidayRepository = holidayRepository; - this.workingDaysRepository = workingDaysRepository; this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; - this.calendarInstanceRepository = calendarInstanceRepository; - this.loanChargeReadPlatformService = loanChargeReadPlatformService; this.loanTransactionRepository = loanTransactionRepository; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.loanRepository = loanRepository; this.loanAssembler = loanAssembler; - this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; 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); // 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 // parameter @@ -275,12 +264,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche } final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(loan, - LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal, graceOnInterest, - rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, - rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(), null, null, null, null); + LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, rescheduleFromDate, + recalculateInterest, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate, + this.platformSecurityContext.authenticatedUser(), null, null, null, null); + + // update reschedule request to term variations mapping + List 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 this.loanRescheduleRequestRepository.save(loanRescheduleRequest); + this.loanRepository.save(loan); return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequest.getId()) .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 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 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 @Transactional public CommandProcessingResult approve(JsonCommand jsonCommand) { @@ -322,132 +380,112 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter)); changes.put("approvedByUserId", appUser.getId()); - if (!changes.isEmpty()) { - Loan loan = loanRescheduleRequest.getLoan(); - final LoanSummary loanSummary = loan.getSummary(); + Loan loan = loanRescheduleRequest.getLoan(); + final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); - final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan - .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); + ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, + loanRescheduleRequest.getRescheduleFromDate()); - final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mathContext = new MathContext(8, roundingMode); + Collection loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService + .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); - Collection loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService - .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); + final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); - HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); - CalendarInstance restCalendarInstance = null; - CalendarInstance compoundingCalendarInstance = null; - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); - compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); - } - final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), - CalendarEntityType.LOANS.getValue()); - Calendar loanCalendar = null; - if (loanCalendarInstance != null) { - loanCalendar = loanCalendarInstance.getCalendar(); - } - 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(); } - - } - LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays); - - final Collection periods = loanRescheduleModel.getPeriods(); - List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); - Collection waiveLoanCharges = new ArrayList<>(); - final Set 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; - } + LocalDate rescheduleFromDate = null; + Set activeLoanTermVariations = loan.getActiveLoanTermVariations(); + LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists(); + if (dueDateVariationInCurrentRequest != null && activeLoanTermVariations != null) { + LocalDate fromScheduleDate = dueDateVariationInCurrentRequest.fetchTermApplicaDate(); + LocalDate currentScheduleDate = fromScheduleDate; + LocalDate modifiedScheduleDate = dueDateVariationInCurrentRequest.fetchDateValue(); + Map changeMap = new HashMap<>(); + changeMap.put(currentScheduleDate, modifiedScheduleDate); + for (LoanTermVariations activeLoanTermVariation : activeLoanTermVariations) { + if (activeLoanTermVariation.getTermType().isDueDateVariation() + && activeLoanTermVariation.fetchDateValue().equals(dueDateVariationInCurrentRequest.fetchTermApplicaDate())) { + activeLoanTermVariation.markAsInactive(); + rescheduleFromDate = activeLoanTermVariation.fetchTermApplicaDate(); + dueDateVariationInCurrentRequest.setTermApplicableFrom(rescheduleFromDate.toDate()); + } else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) { + while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) { + currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate, + loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO()); + modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate, + loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO()); + changeMap.put(currentScheduleDate, modifiedScheduleDate); + } + if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) { + activeLoanTermVariation.setTermApplicableFrom(changeMap.get(activeLoanTermVariation.fetchTermApplicaDate()) + .toDate()); } } } - - 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 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 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) .withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).build(); @@ -462,60 +500,24 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche } } - /** - * waive all charges in the collection - * - * @param loan - * Loan object - * @param loanCharges - * collection of LoanCharge objects - * @return void - **/ - private void waiveLoanCharges(Loan loan, Collection 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(); - } + private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { + try { + List installments = loan.getRepaymentScheduleInstallments(); + for (LoanRepaymentScheduleInstallment installment : installments) { + if (installment.getId() == null) { + this.repaymentScheduleInstallmentRepository.save(installment); } - - final Map changes = new LinkedHashMap<>(3); - - final List existingTransactionIds = new ArrayList<>(); - final List 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 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 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); } - private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); - } - @Override @Transactional public CommandProcessingResult reject(JsonCommand jsonCommand) { @@ -562,6 +559,11 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche if (!changes.isEmpty()) { loanRescheduleRequest.reject(appUser, rejectedOnDate); + Set loanRescheduleRequestToTermVariationMappings = loanRescheduleRequest + .getLoanRescheduleRequestToTermVariationMappings(); + for (LoanRescheduleRequestToTermVariationMapping loanRescheduleRequestToTermVariationMapping : loanRescheduleRequestToTermVariationMappings) { + loanRescheduleRequestToTermVariationMapping.getLoanTermVariations().markAsInactive(); + } } return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId) @@ -593,20 +595,4 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche "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 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; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java index dffd39c8d..18c905860 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java @@ -551,6 +551,22 @@ public class LoanEnumerations { optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(), LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount"); 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: optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(), LoanTermVariationType.INVALID.getCode(), "Invalid"); diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V313__multi_rescheduling_script.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V313__multi_rescheduling_script.sql new file mode 100644 index 000000000..1fca1d179 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V313__multi_rescheduling_script.sql @@ -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`; \ No newline at end of file