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 <[email protected]>
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());
+        });
+    }
+
 }

Reply via email to