diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java index c4b4905b3..c7b6d6e67 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java @@ -359,4 +359,52 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { } + @Test + public void testCreateLoanRescheduleRequestWithMultpleInterestAppropriation() { + // create all required entities + this.createRequiredEntities(); + this.createAndApproveLoanRescheduleRequestForInterestAppropriation(); + + this.createAndApproveLoanRescheduleRequestForSecondInterestAppropriation(); + + } + + /** + * create new loan reschedule request + **/ + private void createAndApproveLoanRescheduleRequestForSecondInterestAppropriation() { + LOG.info( + "---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR INTEREST APPROPRIATTION-------------------------------------"); + + final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null) + .updateExtraTerms(null).updateRescheduleFromDate("04 December 2015").updateAdjustedDueDate("04 June 2016") + .updateRecalculateInterest(true).build(this.loanId.toString()); + + this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId); + + LOG.info("Successfully created loan reschedule request (ID: {} )", this.loanRescheduleRequestId); + + final String aproveRequestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON(); + this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, aproveRequestJSON); + final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum"); + assertTrue((Boolean) response.get("approved")); + + LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId); + + final Map repaymentSchedule = (Map) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId, + "repaymentSchedule"); + final ArrayList periods = (ArrayList) repaymentSchedule.get("periods"); + + HashMap period = (HashMap) periods.get(7); + Float totalDueForPeriod = (Float) period.get("totalDueForPeriod"); + + final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); + final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); + + assertEquals(12187, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT in Second Reschedule is NOK"); + assertEquals(130750, totalExpectedRepayment.intValue(), "TOTAL EXPECTED in Second Reschedule REPAYMENT is NOK"); + + LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId); + } } 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 b81757121..5c6f1b369 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 @@ -82,6 +82,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true) private BigDecimal interestAccrued; + @Column(name = "reschedule_interest_portion", scale = 6, precision = 19, nullable = true) + private BigDecimal rescheduleInterestPortion; + @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true) private BigDecimal feeChargesCharged; @@ -139,6 +142,24 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.obligationsMet = false; } + public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate, + final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges, + final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent, + final Set compoundingDetails, final BigDecimal rescheduleInterestPortion) { + this.loan = loan; + this.installmentNumber = installmentNumber; + this.fromDate = fromDate.toDateTimeAtStartOfDay().toDate(); + this.dueDate = dueDate.toDateTimeAtStartOfDay().toDate(); + this.principal = defaultToNullIfZero(principal); + this.interestCharged = defaultToNullIfZero(interest); + this.feeChargesCharged = defaultToNullIfZero(feeCharges); + this.penaltyCharges = defaultToNullIfZero(penaltyCharges); + this.obligationsMet = false; + this.recalculatedInterestComponent = recalculatedInterestComponent; + this.loanCompoundingDetails = compoundingDetails; + this.rescheduleInterestPortion = rescheduleInterestPortion; + } + public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges, final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent, @@ -807,4 +828,13 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return getPenaltyChargesPaid(currency).plus(getFeeChargesPaid(currency)).plus(getInterestPaid(currency)) .plus(getPrincipalCompleted(currency)); } + + public BigDecimal getRescheduleInterestPortion() { + return rescheduleInterestPortion; + } + + public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { + this.rescheduleInterestPortion = rescheduleInterestPortion; + } + } 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 fa7a57a7e..c0e4b5e32 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 @@ -397,6 +397,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener for (LoanScheduleModelPeriod installment : (List) periods) { if (!installment.isEMIFixedSpecificToInstallment()) { installment.addInterestAmount(interestFraction); + installment.setRescheduleInterestPortion(interestFraction.getAmount()); interestTobeApproppriated = interestTobeApproppriated.minus(interestFraction); } } @@ -404,6 +405,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (interestTobeApproppriated.isGreaterThanZero()) { lastInstallment.addInterestAmount(interestTobeApproppriated); + BigDecimal rescheduleInterestPortion = BigDecimal.ZERO; + if (lastInstallment.rescheduleInterestPortion() != null) { + rescheduleInterestPortion = lastInstallment.rescheduleInterestPortion(); + } + lastInstallment.setRescheduleInterestPortion(rescheduleInterestPortion.add(interestTobeApproppriated.getAmount())); } scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated()); scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated()); @@ -2385,6 +2391,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); periods.clear(); } + + BigDecimal rescheuleInterestPortionTobeRetained = BigDecimal.ZERO; + for (LoanRepaymentScheduleInstallment retainedInstallment : retainedInstallments) { + if (retainedInstallment.getRescheduleInterestPortion() != null) { + rescheuleInterestPortionTobeRetained = rescheuleInterestPortionTobeRetained + .add(retainedInstallment.getRescheduleInterestPortion()); + } + } + + BigDecimal rescheuleInterestPortionTotal = BigDecimal.ZERO; + for (LoanRepaymentScheduleInstallment inst : loan.getRepaymentScheduleInstallments()) { + if (inst.getRescheduleInterestPortion() != null) { + rescheuleInterestPortionTotal = rescheuleInterestPortionTotal.add(inst.getRescheduleInterestPortion()); + } + } + + BigDecimal rescheuleInterestPortionTobeAppropriated = rescheuleInterestPortionTotal.subtract(rescheuleInterestPortionTobeRetained); + loanApplicationTerms.setInterestTobeApproppriated(Money.of(loan.getCurrency(), rescheuleInterestPortionTobeAppropriated)); LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.charges(), holidayDetailDTO, loanScheduleParams); for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { @@ -2589,7 +2613,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), - scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails()); + scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(), + scheduledLoanInstallment.rescheduleInterestPortion()); installments.add(installment); } return installment; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index 2dbb4e1d2..8023200a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -138,4 +138,14 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public boolean isEMIFixedSpecificToInstallment() { return isEMIFixedSpecificToInstallment; } + + @Override + public BigDecimal rescheduleInterestPortion() { + return null; + } + + @Override + public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { + return; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java index 207e08b4e..2b71bd6b3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java @@ -58,4 +58,8 @@ public interface LoanScheduleModelPeriod { void setEMIFixedSpecificToInstallmentTrue(); boolean isEMIFixedSpecificToInstallment(); + + BigDecimal rescheduleInterestPortion(); + + void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java index a7c0edbfa..41b652f8a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java @@ -43,6 +43,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private final boolean recalculatedInterestComponent; private final Set loanCompoundingDetails = new HashSet<>(); private boolean isEMIFixedSpecificToInstallment = false; + BigDecimal rescheduleInterestPortion; public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, @@ -172,4 +173,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel public void setEMIFixedSpecificToInstallmentTrue() { this.isEMIFixedSpecificToInstallment = true; } + + @Override + public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) { + this.rescheduleInterestPortion = rescheduleInterestPortion; + } + + @Override + public BigDecimal rescheduleInterestPortion() { + return this.rescheduleInterestPortion; + } } diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V362__reschedule_interest_to_lrs.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V362__reschedule_interest_to_lrs.sql new file mode 100644 index 000000000..e37caf32f --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V362__reschedule_interest_to_lrs.sql @@ -0,0 +1,22 @@ +-- +-- 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. +-- + + +alter table m_loan_repayment_schedule + add column `reschedule_interest_portion` decimal(19,6) DEFAULT NULL after `accrual_interest_derived`;