From commits-return-12089-archive-asf-public=cust-asf.ponee.io@fineract.apache.org Mon Sep 14 14:26:28 2020 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mailroute1-lw-us.apache.org (mailroute1-lw-us.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with ESMTPS id 9A25F18066D for ; Mon, 14 Sep 2020 16:26:28 +0200 (CEST) Received: from mail.apache.org (localhost [127.0.0.1]) by mailroute1-lw-us.apache.org (ASF Mail Server at mailroute1-lw-us.apache.org) with SMTP id B8796121F52 for ; Mon, 14 Sep 2020 14:26:27 +0000 (UTC) Received: (qmail 18861 invoked by uid 500); 14 Sep 2020 14:26:27 -0000 Mailing-List: contact commits-help@fineract.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@fineract.apache.org Delivered-To: mailing list commits@fineract.apache.org Received: (qmail 18842 invoked by uid 99); 14 Sep 2020 14:26:27 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 14 Sep 2020 14:26:27 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 01FBD8087B; Mon, 14 Sep 2020 14:26:26 +0000 (UTC) Date: Mon, 14 Sep 2020 14:26:26 +0000 To: "commits@fineract.apache.org" Subject: [fineract] branch develop updated: FINERACT-1109-rework-with-fixed-emi MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Message-ID: <160009358636.8683.13607323835593001218@gitbox.apache.org> From: avikg@apache.org X-Git-Host: gitbox.apache.org X-Git-Repo: fineract X-Git-Refname: refs/heads/develop X-Git-Reftype: branch X-Git-Oldrev: cdf3c7685603e0c2041636786ed882ec9816f861 X-Git-Newrev: 10dd1fa2b944c1311c8faae2c7df736dc733df1e X-Git-Rev: 10dd1fa2b944c1311c8faae2c7df736dc733df1e X-Git-NotificationType: ref_changed_plus_diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated This is an automated email from the ASF dual-hosted git repository. avikg pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git The following commit(s) were added to refs/heads/develop by this push: new 10dd1fa FINERACT-1109-rework-with-fixed-emi new 44bc6e6 Merge pull request #1327 from fynmanoj/FINERACT-1109-rework 10dd1fa is described below commit 10dd1fa2b944c1311c8faae2c7df736dc733df1e Author: Manoj AuthorDate: Sun Sep 13 22:18:20 2020 +0530 FINERACT-1109-rework-with-fixed-emi --- .../LoanRescheduleOnDecliningBalanceLoanTest.java | 150 ++++++++++++++++++++- .../domain/AbstractLoanScheduleGenerator.java | 31 ++++- .../LoanScheduleModelDisbursementPeriod.java | 11 ++ .../domain/LoanScheduleModelPeriod.java | 4 + .../domain/LoanScheduleModelRepaymentPeriod.java | 11 ++ .../data/LoanRescheduleRequestDataValidator.java | 5 - 6 files changed, 198 insertions(+), 14 deletions(-) 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 4a7ae3a..c4b4905 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 @@ -71,8 +71,6 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { this.generalResponseSpec = new ResponseSpecBuilder().build(); - // create all required entities - this.createRequiredEntities(); } @AfterEach @@ -91,6 +89,16 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { } /** + * Creates the client, loan product, and loan entities + **/ + private void createRequiredEntitiesWithRecalculationEnabled() { + this.createClientEntity(); + this.createLoanProductWithInterestRecalculation(); + this.createLoanEntity(); + this.enableConfig(); + } + + /** * create a new client **/ private void createClientEntity() { @@ -114,6 +122,38 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { LOG.info("Successfully created loan product (ID:{}) ", this.loanProductId); } + private void createLoanProductWithInterestRecalculation() { + LOG.info( + "---------------------------------CREATING LOAN PRODUCT WITH RECALULATION ENABLED ------------------------------------------"); + + final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE; + final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS; + final String recalculationRestFrequencyType = LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY; + final String recalculationRestFrequencyInterval = "0"; + final String preCloseInterestCalculationStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE; + final String recalculationCompoundingFrequencyType = null; + final String recalculationCompoundingFrequencyInterval = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; + final Integer recalculationRestFrequencyOnDayType = null; + final Integer recalculationRestFrequencyDayOfWeekType = null; + + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount) + .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod) + .withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays() + .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, + preCloseInterestCalculationStrategy) + .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType) + .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType) + .build(null); + + this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + LOG.info("Successfully created loan product (ID:{}) ", this.loanProductId); + } + /** * submit a new loan application, approve and disburse the loan **/ @@ -173,6 +213,8 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { @Test public void testCreateLoanRescheduleRequestWithInterestAppropriation() { + // create all required entities + this.createRequiredEntities(); this.createAndApproveLoanRescheduleRequestForInterestAppropriation(); } @@ -210,8 +252,108 @@ public class LoanRescheduleOnDecliningBalanceLoanTest { final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); - assertEquals(12186, totalDueForPeriod.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK"); - assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK"); + assertEquals(12186, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK"); + assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK"); + + LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId); + + } + + @Test + public void testCreateLoanRescheduleRequestWithRecalculationEnabled() { + // create all required entities + this.createRequiredEntitiesWithRecalculationEnabled(); + this.createAndApproveLoanRescheduleRequestWithRecalculationEnabled(); + } + + /** + * create new loan reschedule request with recalculation enabled in Loan product + **/ + + private void createAndApproveLoanRescheduleRequestWithRecalculationEnabled() { + LOG.info( + "---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR LOAN WITH RECALCULATION------------------------------------"); + + final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null) + .updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 October 2015") + .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(5); + Float totalDueForPeriod = (Float) period.get("totalDueForPeriod"); + + final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); + final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); + + assertEquals(12326, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK"); + assertEquals(131512, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK"); + + LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId); + + } + + @Test + public void testCreateLoanRescheduleRequestForInterestAppropriationAndFixedEMI() { + // create all required entities + this.createRequiredEntities(); + this.createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI(); + } + + /** + * create new loan reschedule request with combination of date change, interest appropriation and fixed emi + **/ + private void createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI() { + LOG.info( + "---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR INTEREST APPROPRIATTION-------------------------------------"); + + final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null) + .updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 July 2015").updateEMI("5000") + .updateEmiChangeEndDate("4 September 2015").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(5); + Float totalFixedDueForPeriod = (Float) period.get("totalDueForPeriod"); + + HashMap period2 = (HashMap) periods.get(8); + Float totalDueForPeriod = (Float) period2.get("totalDueForPeriod"); + + final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); + final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); + + assertEquals(5000, totalFixedDueForPeriod.intValue(), "EXPECTED FIXED REPAYMENT is NOK"); + + assertEquals(15316, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK"); + assertEquals(120806, totalExpectedRepayment.intValue(), "TOTAL EXPECTED 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/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 62f6158..5fd4380 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 @@ -337,6 +337,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); + + if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) { + installment.setEMIFixedSpecificToInstallmentTrue(); + } + periods.add(installment); // Updates principal paid map with efective date for reducing @@ -373,15 +378,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled() && loanApplicationTerms.getInterestTobeApproppriated() != null && loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero()) { - Money interestFraction = loanApplicationTerms.getInterestTobeApproppriated().dividedBy(periods.size(), mc.getRoundingMode()); + int emisTobeChanged = 1; + for (LoanScheduleModelPeriod installment : (List) periods) { + if (!installment.isEMIFixedSpecificToInstallment()) { + emisTobeChanged++; + } + } + if (emisTobeChanged > 1) { + emisTobeChanged--; + } + Money interestTobeApproppriated = loanApplicationTerms.getInterestTobeApproppriated(); + Money interestFraction = interestTobeApproppriated.dividedBy(emisTobeChanged, mc.getRoundingMode()); BigDecimal roundFraction = interestFraction.getAmount().remainder(BigDecimal.ONE); interestFraction = interestFraction.minus(roundFraction); - roundFraction = roundFraction.multiply(new BigDecimal(periods.size())); for (LoanScheduleModelPeriod installment : (List) periods) { - installment.addInterestAmount(interestFraction); + if (!installment.isEMIFixedSpecificToInstallment()) { + installment.addInterestAmount(interestFraction); + interestTobeApproppriated = interestTobeApproppriated.minus(interestFraction); + } + } + LoanScheduleModelPeriod lastInstallment = ((List) periods).get(periods.size() - 1); + + if (interestTobeApproppriated.isGreaterThanZero()) { + lastInstallment.addInterestAmount(interestTobeApproppriated); } - LoanScheduleModelPeriod installment = ((List) periods).get(periods.size() - 1); - installment.addInterestAmount(Money.of(currency, roundFraction)); scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated()); scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated()); loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency)); @@ -1079,6 +1099,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (loanTermVariationsData.isSpecificToInstallment()) { loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue()); recalculateAmounts = true; + } else { loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue()); } 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 68731b4..2dbb4e1 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 @@ -35,6 +35,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo private final LocalDate disbursementDate; private final Money principalDisbursed; private final BigDecimal chargesDueAtTimeOfDisbursement; + private boolean isEMIFixedSpecificToInstallment = false; public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) { @@ -127,4 +128,14 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public Set getLoanCompoundingDetails() { return null; } + + @Override + public void setEMIFixedSpecificToInstallmentTrue() { + this.isEMIFixedSpecificToInstallment = true; + } + + @Override + public boolean isEMIFixedSpecificToInstallment() { + return isEMIFixedSpecificToInstallment; + } } 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 0a7b8e9..207e08b 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 @@ -54,4 +54,8 @@ public interface LoanScheduleModelPeriod { void addInterestAmount(Money interestDue); Set getLoanCompoundingDetails(); + + void setEMIFixedSpecificToInstallmentTrue(); + + boolean isEMIFixedSpecificToInstallment(); } 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 b4720b6..a7c0edb 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 @@ -42,6 +42,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private Money totalDue; private final boolean recalculatedInterestComponent; private final Set loanCompoundingDetails = new HashSet<>(); + private boolean isEMIFixedSpecificToInstallment = false; public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, @@ -161,4 +162,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel public Set getLoanCompoundingDetails() { return this.loanCompoundingDetails; } + + @Override + public boolean isEMIFixedSpecificToInstallment() { + return this.isEMIFixedSpecificToInstallment; + } + + @Override + public void setEMIFixedSpecificToInstallmentTrue() { + this.isEMIFixedSpecificToInstallment = true; + } } 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 ee35f99..48de022 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 @@ -202,11 +202,6 @@ public class LoanRescheduleRequestDataValidator { "Loan rescheduling is not supported for multidisbursement loans"); } - if (loan.isInterestRecalculationEnabledForProduct()) { - dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( - RescheduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErrorCode, - "Loan rescheduling is not supported for the loan product with interest recalculation enabled"); - } validateForOverdueCharges(dataValidatorBuilder, loan, installment); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors);