You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by av...@apache.org on 2020/09/14 14:26:26 UTC
[fineract] branch develop updated:
FINERACT-1109-rework-with-fixed-emi
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 <ma...@fynarfin.io>
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<LoanScheduleModelPeriod>) 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<LoanScheduleModelPeriod>) periods) {
- installment.addInterestAmount(interestFraction);
+ if (!installment.isEMIFixedSpecificToInstallment()) {
+ installment.addInterestAmount(interestFraction);
+ interestTobeApproppriated = interestTobeApproppriated.minus(interestFraction);
+ }
+ }
+ LoanScheduleModelPeriod lastInstallment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
+
+ if (interestTobeApproppriated.isGreaterThanZero()) {
+ lastInstallment.addInterestAmount(interestTobeApproppriated);
}
- LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) 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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> 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);