You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/11/13 11:47:53 UTC
(fineract) branch develop updated (fdb9e5518c -> 1b3217be39)
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
from fdb9e5518c FINERACT-2354: Fourth step - Chargeback before re-age edge case for Interest bearing loans - updated e2e scenario tags
new a0b31bbb25 FINERACT-2326: Loan contract termination same disbursement date
new 1b3217be39 FINERACT-2326: Loan contract termination same disbursement date - E2E
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../test/data/loanproduct/DefaultLoanProduct.java | 1 +
.../global/LoanProductGlobalInitializerStep.java | 77 +++++++++++++++-------
.../fineract/test/stepdef/loan/LoanStepDef.java | 14 ++++
.../fineract/test/support/TestContextKey.java | 1 +
.../features/LoanContractTermination.feature | 45 +++++++++++++
.../portfolio/loanaccount/domain/Loan.java | 15 +++--
.../domain/LoanRepaymentScheduleInstallment.java | 5 +-
...dvancedPaymentScheduleTransactionProcessor.java | 9 ++-
.../LoanContractTerminationTest.java | 31 +++++++++
9 files changed, 164 insertions(+), 34 deletions(-)
(fineract) 02/02: FINERACT-2326: Loan contract termination same disbursement date - E2E
Posted by ad...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 1b3217be39cb645ade30cd715a7359c35ede53ae
Author: Peter Kovacs <pe...@dpc.hu>
AuthorDate: Wed Nov 12 13:31:50 2025 +0100
FINERACT-2326: Loan contract termination same disbursement date - E2E
---
.../org/apache/fineract/test/stepdef/loan/LoanStepDef.java | 14 ++++++++++++++
.../resources/features/LoanContractTermination.feature | 4 ++--
.../loanschedule/domain/LoanApplicationTerms.java | 1 +
3 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index b9272f355b..fd97188e11 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -5341,6 +5341,20 @@ public class LoanStepDef extends AbstractStepDef {
.isEqualTo(transactionId);
}
+ @And("Admin successfully terminates loan contract - no event check")
+ public void makeLoanContractTerminationNoEventCheck() throws IOException {
+ final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ assert loanResponse.body() != null;
+ final long loanId = loanResponse.body().getLoanId();
+
+ final PostLoansLoanIdRequest contractTerminationRequest = LoanRequestFactory.defaultLoanContractTerminationRequest();
+
+ final Response<PostLoansLoanIdResponse> loanContractTerminationResponse = loansApi
+ .stateTransitions(loanId, contractTerminationRequest, "contractTermination").execute();
+ testContext().set(TestContextKey.LOAN_CONTRACT_TERMINATION_RESPONSE, loanContractTerminationResponse);
+ ErrorHelper.checkSuccessfulApiCall(loanContractTerminationResponse);
+ }
+
@And("Admin successfully undoes loan contract termination")
public void undoLoanContractTermination() throws IOException {
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
index 641db7d926..920a71f49d 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
@@ -1327,7 +1327,7 @@ Feature: Contract Termination
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
- And Admin successfully terminates loan contract
+ And Admin successfully terminates loan contract - no event check
Then Loan Repayment schedule has 1 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
@@ -1349,7 +1349,7 @@ Feature: Contract Termination
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
- And Admin successfully terminates loan contract
+ And Admin successfully terminates loan contract - no event check
Then Loan Repayment schedule has 1 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 77a4e43865..560aa83d22 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -2255,4 +2255,5 @@ public final class LoanApplicationTerms {
public void updateVariationDays(final long daysToAdd) {
this.variationDays += daysToAdd;
}
+
}
(fineract) 01/02: FINERACT-2326: Loan contract termination same disbursement date
Posted by ad...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
commit a0b31bbb2508ab32517ff50fbb3526470957e83f
Author: Jose Alberto Hernandez <al...@black-box.local>
AuthorDate: Fri Oct 3 13:01:50 2025 -0500
FINERACT-2326: Loan contract termination same disbursement date
---
.../test/data/loanproduct/DefaultLoanProduct.java | 1 +
.../global/LoanProductGlobalInitializerStep.java | 77 +++++++++++++++-------
.../fineract/test/support/TestContextKey.java | 1 +
.../features/LoanContractTermination.feature | 45 +++++++++++++
.../portfolio/loanaccount/domain/Loan.java | 15 +++--
.../domain/LoanRepaymentScheduleInstallment.java | 5 +-
.../loanschedule/domain/LoanApplicationTerms.java | 1 -
...dvancedPaymentScheduleTransactionProcessor.java | 9 ++-
.../LoanContractTerminationTest.java | 31 +++++++++
9 files changed, 150 insertions(+), 35 deletions(-)
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
index f8b9b3afd2..bec004ff72 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
@@ -146,6 +146,7 @@ public enum DefaultLoanProduct implements LoanProduct {
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, //
LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION, //
+ LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION, //
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_FLAT_CAPITALIZED_INCOME, //
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
index 407448de53..0d4556ce6d 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
@@ -4058,6 +4058,39 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmi36030InterestRecalculationDaily);
+ // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement +
+ // contract termination with interest recognition
+ // (LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION)
+ final String name148 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION
+ .getName();
+
+ final PostLoanProductsRequest loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsRequestFactory
+ .defaultLoanProductsRequestLP2InterestDailyRecalculation()//
+ .interestRecognitionOnDisbursementDate(true) //
+ .name(name148)//
+ .paymentAllocation(List.of(//
+ createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT",
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL, //
+ LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST), //
+ createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
+ createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
+ createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
+ final Response<PostLoanProductsResponse> responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsApi
+ .createLoanProduct(loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog).execute();
+ TestContext.INSTANCE.set(
+ TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION,
+ responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog);
+
// (LP1_WITH_OVERRIDES) - Loan product with all attribute overrides ENABLED
final String nameWithOverrides = DefaultLoanProduct.LP1_WITH_OVERRIDES.getName();
final PostLoanProductsRequest loanProductsRequestWithOverrides = loanProductsRequestFactory.defaultLoanProductsRequestLP1() //
@@ -4106,12 +4139,13 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
.execute();
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES, responseNoOverrides);
- // LP2 advanced + progressive loan schedule + horizontal + interest recalculation
+ // LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
- String name148 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
- PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
+ String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
+ .getName();
+ PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory
.defaultLoanProductsRequestLP2Emi()//
- .name(name148)//
+ .name(name149)//
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
.daysInYearType(DaysInYearType.ACTUAL.value)//
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
@@ -4124,22 +4158,24 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
.enableAccrualActivityPosting(true) //
.chargeOffBehaviour(ZERO_INTEREST.value)//
.paymentAllocation(List.of(//
- createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
- Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
+ createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
+ createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
+ createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
+ createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); //
+ Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi
.createLoanProduct(
- loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
+ loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals)
.execute();
TestContext.INSTANCE.set(
- TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
- responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
+ TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
+ responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
- // LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
+ // LP2 advanced + progressive loan schedule + horizontal + interest recalculation
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
- String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
- .getName();
- PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory
+ String name150 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
+ PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
.defaultLoanProductsRequestLP2Emi()//
- .name(name149)//
+ .name(name150)//
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
.daysInYearType(DaysInYearType.ACTUAL.value)//
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
@@ -4152,17 +4188,14 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
.enableAccrualActivityPosting(true) //
.chargeOffBehaviour(ZERO_INTEREST.value)//
.paymentAllocation(List.of(//
- createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
- createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
- createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
- createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); //
- Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi
+ createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
+ Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
.createLoanProduct(
- loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals)
+ loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
.execute();
TestContext.INSTANCE.set(
- TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
- responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
+ TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
+ responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
}
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index 30db0f017b..f294b9e893 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -259,6 +259,7 @@ public abstract class TestContextKey {
public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse";
public static final String INTEREST_PAUSE_VARIATION_ID = "interestPauseVariationId";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
+ public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTerminationIntRecognition";
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
public static final String LOAN_BUY_DOWN_FEE_RESPONSE = "loanBuyDownFeeResponse";
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
index 65cded5e73..641db7d926 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature
@@ -1317,3 +1317,48 @@ Feature: Contract Termination
| 31 March 2024 | Accrual Adjustment | 0.15 | 0.0 | 0.15 | 0.0 | 0.0 | 0.0 | false | false |
| 31 March 2024 | Contract Termination | 57.37 | 57.05 | 0.32 | 0.0 | 0.0 | 0.0 | true | true |
And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled
+
+ @TestRailId:C4133
+ Scenario: Contract termination on disbursement date
+ When Admin sets the business date to "01 January 2025"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
+ | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
+ And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
+ And Admin successfully terminates loan contract
+ Then Loan Repayment schedule has 1 periods, with the following data for periods:
+ | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
+ | | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
+ | 1 | 0 | 01 January 2025 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
+ | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
+ | 01 January 2025 | Contract Termination | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
+
+ @TestRailId:C4134
+ Scenario: Contract termination on disbursement date with interest recognition
+ When Admin sets the business date to "01 January 2025"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
+ | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
+ And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
+ And Admin successfully terminates loan contract
+ Then Loan Repayment schedule has 1 periods, with the following data for periods:
+ | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
+ | | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
+ | 1 | 0 | 01 January 2025 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
+ | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
+ | 01 January 2025 | Contract Termination | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
+
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 31a6f971c0..601b8c6bba 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1410,10 +1410,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
* @param date
* @return a schedule installment is related to the provided date
**/
- public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) {
- return getRepaymentScheduleInstallment(
- e -> (e.isFirstNormalInstallment() && DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))
- || DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate()));
+ public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(final LocalDate date) {
+ return getRepaymentScheduleInstallment(e -> (DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())
+ || (e.isFirstNormalInstallment(getRepaymentScheduleInstallments())
+ && DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))));
+ }
+
+ public List<LoanRepaymentScheduleInstallment> getInstallmentsUpToTransactionDate(final LocalDate transactionDate) {
+ return getRepaymentScheduleInstallments().stream()
+ .filter(i -> (transactionDate.isAfter(i.getFromDate())
+ || (i.isFirstNormalInstallment(getRepaymentScheduleInstallments()) && !transactionDate.isBefore(i.getFromDate()))))
+ .collect(Collectors.toCollection(ArrayList::new));
}
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 52e8dd8982..fb55acbbdd 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -1216,8 +1216,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return value.setScale(6, MoneyHelper.getRoundingMode());
}
- public boolean isFirstNormalInstallment() {
- return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment()).findFirst().stream()
- .anyMatch(rp -> rp.equals(this));
+ public boolean isFirstNormalInstallment(List<LoanRepaymentScheduleInstallment> installments) {
+ return installments.stream().filter(rp -> !rp.isDownPayment()).findFirst().stream().anyMatch(rp -> rp.equals(this));
}
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 560aa83d22..77a4e43865 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -2255,5 +2255,4 @@ public final class LoanApplicationTerms {
public void updateVariationDays(final long daysToAdd) {
this.variationDays += daysToAdd;
}
-
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 57221a6a47..bf06feaa5c 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -1760,10 +1760,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
final Loan loan = loanTransaction.getLoan();
final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate);
- if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate())) {
+ if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate()) && currentInstallment != null) {
if (currentInstallment.isNotFullyPaidOff()) {
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
- && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
+ && loan.isInterestBearingAndInterestRecalculationEnabled()) {
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
currentInstallment.getDueDate(), transactionDate, true).getAmount();
@@ -1817,9 +1817,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance));
}
- final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = installments.stream()
- .filter(installment -> transactionDate.isAfter(installment.getFromDate()))
- .collect(Collectors.toCollection(ArrayList::new));
+ final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = loan
+ .getInstallmentsUpToTransactionDate(transactionDate);
final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream()
.filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate))
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java
index 80fc4b5de7..105fbf70d1 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java
@@ -18,13 +18,17 @@
*/
package org.apache.fineract.integrationtests;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -118,4 +122,31 @@ public class LoanContractTerminationTest extends BaseLoanIntegrationTest {
});
}
+ @Test
+ public void testLoanContractTerminationSameDisbursementDate() {
+ final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+ final GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper();
+
+ runAt("1 January 2024", () -> {
+
+ PostLoanProductsResponse loanProductsResponse = loanProductHelper
+ .createLoanProduct(create4IProgressive().interestRecognitionOnDisbursementDate(false));
+ Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
+ 500.0, 7.0, 6, (request) -> request.interestRecognitionOnDisbursementDate(false));
+
+ disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
+
+ loanTransactionHelper.moveLoanState(loanId,
+ new PostLoansLoanIdRequest().note("Contract Termination Test").externalId(Utils.randomStringGenerator("", 20)),
+ "contractTermination");
+
+ verifyTransactions(loanId, //
+ transaction(100.0, "Disbursement", "01 January 2024"), //
+ transaction(100.0, "Contract Termination", "01 January 2024"));
+
+ GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+ assertEquals(BigDecimal.ZERO.stripTrailingZeros(), loanDetails.getSummary().getInterestCharged().stripTrailingZeros());
+ });
+ }
+
}