This is an automated email from the ASF dual-hosted git repository.
arnold 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 ba1d0ce66 FINERACT-1748: Loan prepayment issue after advance payment
ba1d0ce66 is described below
commit ba1d0ce66cd309b76c82025a288e202cc92b9cfe
Author: logoutdhaval <[email protected]>
AuthorDate: Fri Sep 16 20:39:03 2022 +0530
FINERACT-1748: Loan prepayment issue after advance payment
---
.../domain/AbstractLoanScheduleGenerator.java | 4 +-
.../ClientLoanIntegrationTest.java | 207 +++++++++++++++++++++
.../common/loans/LoanProductTestBuilder.java | 10 +-
3 files changed, 219 insertions(+), 2 deletions(-)
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 1bb5793e7..b523a645d 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
@@ -2225,7 +2225,9 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
.adjustRepaymentDate(actualRepaymentDate,
loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
LocalDate modifiedLastInstDate = null;
LoanTermVariationsData variation1 = null;
+ boolean hasDueDateVariation = false;
while
(loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate))
{
+ hasDueDateVariation = true;
LoanTermVariationsData variation =
loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
modifiedLastInstDate =
variation.getDateValue();
@@ -2233,7 +2235,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
}
}
- if
(!lastInstallmentDate.isEqual(installment.getDueDate())
+ if (hasDueDateVariation &&
!lastInstallmentDate.isEqual(installment.getDueDate())
&&
!installment.getDueDate().equals(modifiedLastInstDate)) {
lastInstallmentDate = prevLastInstDate;
actualRepaymentDate = lastInstallmentDate;
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 0303cf825..69515a928 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -45,6 +45,7 @@ import java.util.Locale;
import java.util.Map;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
@@ -4712,6 +4713,82 @@ public class ClientLoanIntegrationTest {
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
+ private Integer
createLoanProductWithInterestRecalculationAndCompoundingDetails(final String
repaymentStrategy,
+ final String interestRecalculationCompoundingMethod, final String
rescheduleStrategyMethod,
+ final String recalculationRestFrequencyType, final String
preCloseInterestCalculationStrategy, final Account[] accounts,
+ final String installmentMultipleOf) {
+ final String recalculationCompoundingFrequencyType = null;
+ final String recalculationCompoundingFrequencyInterval = null;
+ final Integer recalculationCompoundingFrequencyOnDayType = null;
+ final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
+ return createLoanProductWithInterestRecalculation(repaymentStrategy,
interestRecalculationCompoundingMethod,
+ rescheduleStrategyMethod,
recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval,
+ preCloseInterestCalculationStrategy, accounts, null, false,
recalculationCompoundingFrequencyOnDayType,
+ recalculationCompoundingFrequencyDayOfWeekType,
installmentMultipleOf);
+ }
+
+ private Integer createLoanProductWithInterestRecalculation(final String
repaymentStrategy,
+ final String interestRecalculationCompoundingMethod, final String
rescheduleStrategyMethod,
+ final String recalculationCompoundingFrequencyType, final String
recalculationCompoundingFrequencyInterval,
+ final String preCloseInterestCalculationStrategy, final Account[]
accounts, final String chargeId,
+ boolean isArrearsBasedOnOriginalSchedule, final Integer
recalculationCompoundingFrequencyOnDayType,
+ final Integer recalculationCompoundingFrequencyDayOfWeekType,
final String installmentsMultiplesOf) {
+ LOG.info("------------------------------CREATING NEW LOAN PRODUCT
---------------------------------------");
+ LoanProductTestBuilder builder = new
LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12")
+
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9")
+
.withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments()
+
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod,
rescheduleStrategyMethod,
+ preCloseInterestCalculationStrategy)
+
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod,
rescheduleStrategyMethod,
+ preCloseInterestCalculationStrategy)
+
.withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+ recalculationCompoundingFrequencyInterval,
recalculationCompoundingFrequencyOnDayType,
+ recalculationCompoundingFrequencyDayOfWeekType)
+
.withDefineInstallmentAmount(true).withInstallmentAmountInMultiplesOf(installmentsMultiplesOf);
+ if (accounts != null) {
+ builder = builder.withAccountingRulePeriodicAccrual(accounts);
+ }
+
+ if (isArrearsBasedOnOriginalSchedule) {
+ builder = builder.withArrearsConfiguration();
+ }
+
+ final String loanProductJSON = builder.build(chargeId);
+ return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
+ private Integer applyForLoanApplicationForInterestRecalculation(final
Integer clientID, final Integer loanProductID,
+ final String disbursementDate, final String repaymentStrategy,
final String firstRepaymentDate) {
+ LOG.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ final Integer collateralId =
CollateralManagementHelper.createCollateralProduct(this.requestSpec,
this.responseSpec);
+ Assertions.assertNotNull(collateralId);
+ List<HashMap> collaterals = new ArrayList<>();
+
+ final Integer clientCollateralId =
CollateralManagementHelper.createClientCollateral(this.requestSpec,
this.responseSpec,
+ String.valueOf(clientID), collateralId);
+ Assertions.assertNotNull(clientCollateralId);
+ addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+ final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+ .withPrincipal("10000.00") //
+ .withLoanTermFrequency("12") //
+ .withLoanTermFrequencyAsMonths() //
+ .withNumberOfRepayments("12") //
+ .withRepaymentEveryAfter("1") //
+ .withLoanTermFrequencyAsMonths() //
+ .withInterestRatePerPeriod("19.9") //
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsDecliningBalance() //
+ .withInterestCalculationPeriodTypeAsDays() //
+ .withExpectedDisbursementDate(disbursementDate) //
+ .withSubmittedOnDate(disbursementDate) //
+
.withwithRepaymentStrategy(repaymentStrategy).withRepaymentFrequencyTypeAsMonths()//
+
.withFirstRepaymentDate(firstRepaymentDate).withCollaterals(collaterals)
+ .build(clientID.toString(), loanProductID.toString(), null);
+ return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ }
+
private Integer applyForLoanApplicationForInterestRecalculation(final
Integer clientID, final Integer loanProductID,
final String disbursementDate, final String repaymentStrategy,
final List<HashMap> charges) {
return applyForLoanApplicationForInterestRecalculation(clientID,
loanProductID, disbursementDate, repaymentStrategy, charges, null,
@@ -5721,6 +5798,136 @@ public class ClientLoanIntegrationTest {
}
+ @Test
+ public void
testLoanScheduleWithInterestRecalculationMakePrepaymentAfterRepayment() {
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
+ Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ Calendar currentDate =
Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ startDate.add(Calendar.MONTH, -8);
+
+ Calendar firstRepaymentDate = (Calendar) startDate.clone();
+ firstRepaymentDate.add(Calendar.MONTH, 1);
+ firstRepaymentDate.add(Calendar.DAY_OF_MONTH,
firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) -
Calendar.DAY_OF_MONTH);
+ String firstRepayment =
dateFormat.format(firstRepaymentDate.getTime());
+
+ final String loanDisbursementDate =
dateFormat.format(startDate.getTime());
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec,
this.responseSpec, clientID);
+ final Integer loanProductID =
createLoanProductWithInterestRecalculationAndCompoundingDetails(
+
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+ LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
"12");
+
+ final Integer loanID =
applyForLoanApplicationForInterestRecalculation(clientID, loanProductID,
loanDisbursementDate,
+
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
firstRepayment);
+
+ Assertions.assertNotNull(loanID);
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ LOG.info("-----------------------------------APPROVE
LOAN-----------------------------------------");
+ loanStatusHashMap =
this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ LOG.info("-------------------------------DISBURSE
LOAN-------------------------------------------");
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate,
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ ArrayList<HashMap> loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+ startDate.add(Calendar.DAY_OF_MONTH, 2);
+ String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+ //
+ Float earlyPayment = Float.parseFloat("3000");
+ this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate,
earlyPayment, loanID);
+
+ HashMap prepayDetail =
this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec,
loanID);
+ String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+ String loanPrepaymentDate = dateFormat.format(currentDate.getTime());
+ this.loanTransactionHelper.makeRepayment(loanPrepaymentDate,
Float.parseFloat(prepayAmount), loanID);
+ loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+ }
+
+ @Test
+ public void
testLoanScheduleWithInterestRecalculationMakeAdvancePaymentTillSettlement() {
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ final ResponseSpecification errorResponse = new
ResponseSpecBuilder().expectStatusCode(403).build();
+ final LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(this.requestSpec, errorResponse);
+ DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy",
Locale.US);
+ dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "42", true);
+ Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ Calendar currentDate =
Calendar.getInstance(Utils.getTimeZoneOfTenant());
+ startDate.add(Calendar.MONTH, -8);
+
+ Calendar firstRepaymentDate = (Calendar) startDate.clone();
+ firstRepaymentDate.add(Calendar.MONTH, 1);
+ firstRepaymentDate.add(Calendar.DAY_OF_MONTH,
firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) -
Calendar.DAY_OF_MONTH);
+ String firstRepayment =
dateFormat.format(firstRepaymentDate.getTime());
+
+ final String loanDisbursementDate =
dateFormat.format(startDate.getTime());
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ ClientHelper.verifyClientCreatedOnServer(this.requestSpec,
this.responseSpec, clientID);
+ final Integer loanProductID =
createLoanProductWithInterestRecalculationAndCompoundingDetails(
+
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+ LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null,
"12");
+
+ final Integer loanID =
applyForLoanApplicationForInterestRecalculation(clientID, loanProductID,
loanDisbursementDate,
+
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
firstRepayment);
+
+ Assertions.assertNotNull(loanID);
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ LOG.info("-----------------------------------APPROVE
LOAN-----------------------------------------");
+ loanStatusHashMap =
this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+ LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+ LOG.info("-------------------------------DISBURSE
LOAN-------------------------------------------");
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec,
loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate,
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ ArrayList<HashMap> loanSchedule =
this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
+ Assertions.assertNotNull(loanSchedule);
+ Calendar repaymentDate = (Calendar) firstRepaymentDate.clone();
+ startDate.add(Calendar.DAY_OF_MONTH, 2);
+ String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+ //
+ Float earlyPayment = Float.parseFloat("3000");
+ String retrieveDueDate = null;
+ Float amount = null;
+ this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate,
earlyPayment, loanID);
+ for (int i = 1; i < loanSchedule.size(); i++) {
+
+ retrieveDueDate = dateFormat.format(repaymentDate.getTime());
+ amount = (Float) loanSchedule.get(i).get("principalOriginalDue") +
(Float) loanSchedule.get(i).get("interestOriginalDue");
+ if (currentDate.after(repaymentDate)) {
+ this.loanTransactionHelper.makeRepayment(retrieveDueDate,
amount, loanID);
+ } else {
+ break;
+ }
+ repaymentDate.add(Calendar.MONTH, 1);
+ }
+ HashMap savingsAccountErrorData =
validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID);
+ ArrayList<HashMap> error = (ArrayList<HashMap>)
savingsAccountErrorData.get("errors");
+ assertEquals("error.msg.loan.transaction.cannot.be.a.future.date",
error.get(0).get("userMessageGlobalisationCode"));
+ }
+
@Test
public void testCollateralDataIsAvailableWhenRequested() {
// given
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index b11fa5f0b..39ec5f627 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -131,6 +131,7 @@ public class LoanProductTestBuilder {
private boolean syncExpectedWithDisbursementDate = false;
private String fixedPrincipalPercentagePerInstallment;
private String installmentAmountInMultiplesOf;
+ private boolean canDefineInstallmentAmount;
public String build(final String chargeId) {
final HashMap<String, Object> map = build(chargeId, null);
@@ -185,7 +186,9 @@ public class LoanProductTestBuilder {
map.put("overAppliedNumber", this.overAppliedNumber);
}
}
-
+ if (this.canDefineInstallmentAmount) {
+ map.put("canDefineInstallmentAmount",
this.canDefineInstallmentAmount);
+ }
if (multiDisburseLoan) {
map.put("multiDisburseLoan", this.multiDisburseLoan);
map.put("maxTrancheCount", this.maxTrancheCount);
@@ -473,6 +476,11 @@ public class LoanProductTestBuilder {
return this;
}
+ public LoanProductTestBuilder withDefineInstallmentAmount(final boolean
canDefineInstallmentAmount) {
+ this.canDefineInstallmentAmount = canDefineInstallmentAmount;
+ return this;
+ }
+
public LoanProductTestBuilder currencyDetails(final String
digitsAfterDecimal, final String inMultiplesOf) {
this.digitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;