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


The following commit(s) were added to refs/heads/develop by this push:
     new 442290ef92 FINERACT-2354: handle reamortization error code 500 issue 
on same disbursement day
442290ef92 is described below

commit 442290ef921270c2495b0245ae2ecd9c51d45dfb
Author: Attila Budai <[email protected]>
AuthorDate: Mon Jan 12 19:22:43 2026 +0100

    FINERACT-2354: handle reamortization error code 500 issue on same 
disbursement day
---
 .../stepdef/loan/LoanReAmortizationStepDef.java    |  39 +++++++
 .../resources/features/LoanReAmortization.feature  |  46 +-------
 .../features/LoanReAmortizationPreview.feature     |  16 +--
 .../LoanReAmortizationValidator.java               |   9 ++
 .../LoanReAmortizationValidatorTest.java           |  38 +++++++
 .../LoanReAmortizationIntegrationTest.java         | 125 +++++++++++++++++++++
 6 files changed, 219 insertions(+), 54 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java
index 833b43ff60..f28fc92686 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java
@@ -105,6 +105,31 @@ public class LoanReAmortizationStepDef extends 
AbstractStepDef {
         testContext().set(TestContextKey.LOAN_REAMORTIZATION_UNDO_RESPONSE, 
response);
     }
 
+    @When("Admin creates a Loan re-amortization transaction on current 
business date but fails with {int} error")
+    public void createLoanReAmortizationFailsWithError(int errorCode) {
+        final PostLoansResponse loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final Long loanId = loanResponse.getLoanId();
+
+        final PostLoansLoanIdTransactionsRequest reAmortizationRequest = 
LoanRequestFactory.defaultLoanReAmortizationRequest();
+
+        CallFailedRuntimeException exception = fail(() -> 
fineractClient.loanTransactions().executeLoanTransaction(loanId,
+                reAmortizationRequest, Map.of("command", "reAmortize")));
+        
assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(errorCode);
+    }
+
+    @When("Admin creates a Loan re-amortization transaction on current 
business date with reAmortizationInterestHandling {string} but fails with {int} 
error")
+    public void 
createLoanReAmortizationWithInterestHandlingFailsWithError(final String 
reAmortizationInterestHandling, int errorCode) {
+        final PostLoansResponse loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final Long loanId = loanResponse.getLoanId();
+
+        final PostLoansLoanIdTransactionsRequest reAmortizationRequest = 
LoanRequestFactory.defaultLoanReAmortizationRequest()
+                
.reAmortizationInterestHandling(reAmortizationInterestHandling);
+
+        CallFailedRuntimeException exception = fail(() -> 
fineractClient.loanTransactions().executeLoanTransaction(loanId,
+                reAmortizationRequest, Map.of("command", "reAmortize")));
+        
assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(errorCode);
+    }
+
     @When("Admin creates a Loan re-amortization transaction on current 
business date is forbidden as loan was charged-off")
     public void reAmortizationChargedOffLoanFailure() {
         
reAmortizationFailure(ErrorMessageHelper.reAmortizeChargedOffLoanFailure());
@@ -170,6 +195,20 @@ public class LoanReAmortizationStepDef extends 
AbstractStepDef {
         testContext().set(TestContextKey.LOAN_REAMORTIZATION_PREVIEW_RESPONSE, 
response);
     }
 
+    @When("Admin creates a Loan re-amortization preview by Loan external ID 
with the following data, but fails with {int} error code:")
+    public void createReAmortizedPreviewByLoanExternalIdFailsWithErrorCode(int 
errorCode, final DataTable table) {
+        final PostLoansResponse loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final String loanExternalId = loanResponse.getResourceExternalId();
+
+        final List<String> data = table.asLists().get(1);
+        final String reAmortizationInterestHandling = data.getFirst();
+
+        final Map<String, Object> queryParams = 
Map.of("reAmortizationInterestHandling", reAmortizationInterestHandling);
+        CallFailedRuntimeException exception = fail(
+                () -> 
fineractClient.loanTransactions().previewReAmortizationSchedule1(loanExternalId,
 queryParams));
+        assertThat(exception.getStatus()).isEqualTo(errorCode);
+    }
+
     @Then("Loan Re-Amortization Repayment schedule preview has the following 
data in Total row:")
     public void loanRepaymentDScheduleAmountCheck(final DataTable table) {
         final List<List<String>> data = table.asLists();
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature
index ae35ab5378..d8280e1ff1 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature
@@ -5101,9 +5101,8 @@ Feature: LoanReAmortization
     When Loan Pay-off is made on "02 April 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
 
-  @Skip
   @TestRailId:C4510 @AdvancedPaymentAllocation
-  Scenario: Verify allowing re-amortization on same day as disbursement - 
interest bearing loan with default interest handling - UC5.1
+  Scenario: Verify re-amortization on same day as disbursement is rejected 
when no overdue exists - interest bearing loan with default interest handling - 
UC5.1
     When Admin sets the business date to "01 January 2024"
     When Admin creates a client with random data
     When Admin set 
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product 
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation 
rule
@@ -5127,31 +5126,14 @@ Feature: LoanReAmortization
     Then Loan Transactions tab has the following data:
       | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted |
       | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    |
-#  --- re-amortization transaction --- 500 error!!
-    And Admin creates a Loan re-amortization transaction on current business 
date
-    Then Loan Repayment schedule has 6 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 2024  |           | 100.0           |           
    |          | 0.0  |           | 0.0   | 0.0  |            |      |          
   |
-      | 1  | 31   | 01 February 2024 |           | 83.57           | 16.43     
    | 0.58     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 2  | 29   | 01 March 2024    |           | 67.05           | 16.52     
    | 0.49     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 3  | 31   | 01 April 2024    |           | 50.43           | 16.62     
    | 0.39     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 4  | 30   | 01 May 2024      |           | 33.71           | 16.72     
    | 0.29     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 5  | 31   | 01 June 2024     |           | 16.9            | 16.81     
    | 0.2      | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 6  | 30   | 01 July 2024     |           | 0.0             | 16.9      
    | 0.1      | 0.0  | 0.0       | 17.0  | 0.0  | 0.0        | 0.0  | 17.0     
   |
-    Then Loan Repayment schedule has the following data in Total row:
-      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
-      | 100.0         | 2.05     | 0.0  | 0.0       | 102.05 | 0.0  | 0.0      
  | 0.0  | 102.05      |
-    Then Loan Transactions tab has the following data:
-      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
-      | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    | false    |
-      | 15 March 2024    | Re-amortize      | 17.01  | 16.52     | 0.49     | 
0.0  | 0.0       | 0.0          | false    | false    |
+#  --- re-amortization transaction - fails with 403 because no overdue 
installments exist on disbursement day ---
+    And Admin creates a Loan re-amortization transaction on current business 
date but fails with 403 error
 # --- close the loan --- #
     When Loan Pay-off is made on "01 January 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
 
-  @Skip
   @TestRailId:C4511 @AdvancedPaymentAllocation
-  Scenario: Verify allowing re-amortization on same day as disbursement - 
interest bearing loan with equal amortization + interest split - UC5.2
+  Scenario: Verify re-amortization on same day as disbursement is rejected 
when no overdue exists - interest bearing loan with equal amortization + 
interest split - UC5.2
     When Admin sets the business date to "01 January 2024"
     When Admin creates a client with random data
     When Admin set 
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product 
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation 
rule
@@ -5175,24 +5157,8 @@ Feature: LoanReAmortization
     Then Loan Transactions tab has the following data:
       | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted |
       | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    |
-# --- re-amortization transaction --- 500 error!!
-    And Admin creates a Loan re-amortization transaction on current business 
date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT"
-    Then Loan Repayment schedule has 6 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 2024  |           | 100.0           |           
    |          | 0.0  |           | 0.0   | 0.0  |            |      |          
   |
-      | 1  | 31   | 01 February 2024 |           | 83.57           | 16.43     
    | 0.58     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 2  | 29   | 01 March 2024    |           | 67.05           | 16.52     
    | 0.49     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 3  | 31   | 01 April 2024    |           | 50.43           | 16.62     
    | 0.39     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 4  | 30   | 01 May 2024      |           | 33.71           | 16.72     
    | 0.29     | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 5  | 31   | 01 June 2024     |           | 16.9            | 16.81     
    | 0.2      | 0.0  | 0.0       | 17.01 | 0.0  | 0.0        | 0.0  | 17.01    
   |
-      | 6  | 30   | 01 July 2024     |           | 0.0             | 16.9      
    | 0.1      | 0.0  | 0.0       | 17.0  | 0.0  | 0.0        | 0.0  | 17.0     
   |
-    Then Loan Repayment schedule has the following data in Total row:
-      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
-      | 100.0         | 2.05     | 0.0  | 0.0       | 102.05 | 0.0  | 0.0      
  | 0.0  | 102.05      |
-    Then Loan Transactions tab has the following data:
-      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
-      | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    | false    |
-      | 15 March 2024    | Re-amortize      | 17.01  | 16.52     | 0.49     | 
0.0  | 0.0       | 0.0          | false    | false    |
+# --- re-amortization transaction - fails with 403 because no overdue 
installments exist on disbursement day ---
+    And Admin creates a Loan re-amortization transaction on current business 
date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" 
but fails with 403 error
 # --- close the loan --- #
     When Loan Pay-off is made on "01 January 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationPreview.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationPreview.feature
index 8390143018..af53d1182f 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationPreview.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationPreview.feature
@@ -705,21 +705,9 @@ Feature: LoanReAmortizationPreview
       | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    | false    |
       | 01 February 2024 | Repayment        | 17.01  | 16.43     | 0.58     | 
0.0  | 0.0       | 83.57        | false    | false    |
       | 01 March 2024    | Repayment        | 17.01  | 16.52     | 0.49     | 
0.0  | 0.0       | 67.05        | false    | false    |
-    #    --- Re-amortization repayment schedule preview ---
-    And Admin creates a Loan re-amortization preview by Loan external ID with 
the following data:
+    #    --- Re-amortization repayment schedule preview - should fail because 
no overdue principal exists ---
+    And Admin creates a Loan re-amortization preview by Loan external ID with 
the following data, but fails with 403 error code:
       | reAmortizationInterestHandling |
       | DEFAULT                        |
-    Then Loan Re-Amortization Repayment schedule preview has 6 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 2024  |                  | 100.0           | 
0.0           | 0.0      | 0.0  | 0.0       | 0.0   | 0.0   |            |      
|             |
-      | 1  | 31   | 01 February 2024 | 01 February 2024 | 83.57           | 
16.43         | 0.58     | 0.0  | 0.0       | 17.01 | 17.01 | 0.0        | 0.0  
| 0.0         |
-      | 2  | 29   | 01 March 2024    |                  | 67.05           | 
16.52         | 0.49     | 0.0  | 10.0      | 27.01 | 17.01 | 0.0        | 0.0  
| 10.0        |
-      | 3  | 31   | 01 April 2024    |                  | 50.43           | 
16.62         | 0.39     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  
| 17.01       |
-      | 4  | 30   | 01 May 2024      |                  | 33.71           | 
16.72         | 0.29     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  
| 17.01       |
-      | 5  | 31   | 01 June 2024     |                  | 16.9            | 
16.81         | 0.2      | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  
| 17.01       |
-      | 6  | 30   | 01 July 2024     |                  | 0.0             | 
16.9          | 0.1      | 0.0  | 0.0       | 17.0  | 0.0   | 0.0        | 0.0  
| 17.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         | 2.05     | 0.0  | 10.0      | 112.05 | 34.02 | 0.0     
   | 0.0  | 78.03       |
     When Loan Pay-off is made on "15 March 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
\ No newline at end of file
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
index 940cd114d1..5faeed9141 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java
@@ -131,6 +131,15 @@ public class LoanReAmortizationValidator {
             throw new 
GeneralPlatformDomainRuleException("error.msg.loan.reamortize.not.allowed.on.contract.terminated",
                     "Loan re-amortization is not allowed on contract 
terminated loan.", loan.getId());
         }
+
+        // validate there are overdue installments to re-amortize
+        boolean hasOverdueInstallments = 
loan.getRepaymentScheduleInstallments().stream()
+                .anyMatch(installment -> 
installment.getDueDate().isBefore(getBusinessLocalDate())
+                        && 
installment.getPrincipalOutstanding(loan.getCurrency()).isGreaterThanZero());
+        if (!hasOverdueInstallments) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.loan.reamortize.no.overdue.amount",
+                    "Re-amortization cannot be executed: no overdue amount 
exists.", loan.getId());
+        }
     }
 
     public LoanTransaction findAndValidateReAmortizeTransactionForUndo(Loan 
loan) {
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java
index d5c1441adb..010923c8d0 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java
@@ -42,7 +42,10 @@ import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRu
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
@@ -179,6 +182,19 @@ class LoanReAmortizationValidatorTest {
                 
.isEqualTo("error.msg.loan.reamortize.reamortize.transaction.already.present.for.today");
     }
 
+    @Test
+    public void 
testValidateReAmortize_ShouldThrowException_WhenNoOverdueInstallmentsExist() {
+        // given
+        Loan loan = loan(false);
+        JsonCommand command = jsonCommand();
+        // when
+        GeneralPlatformDomainRuleException result = 
assertThrows(GeneralPlatformDomainRuleException.class,
+                () -> underTest.validateReAmortize(loan, command));
+        // then
+        assertThat(result).isNotNull();
+        
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.no.overdue.amount");
+    }
+
     @Test
     public void 
testValidateUndoReAmortize_ShouldThrowException_WhenLoanDoesntHaveReAmortization()
 {
         // given
@@ -238,6 +254,10 @@ class LoanReAmortizationValidatorTest {
     }
 
     private Loan loan() {
+        return loan(true);
+    }
+
+    private Loan loan(boolean withOverdueInstallments) {
         Loan loan = mock(Loan.class);
         given(loan.getStatus()).willReturn(LoanStatus.ACTIVE);
         given(loan.getMaturityDate()).willReturn(actualDate.plusDays(30));
@@ -248,6 +268,24 @@ class LoanReAmortizationValidatorTest {
         
given(loanProductRelatedDetail.getLoanScheduleType()).willReturn(LoanScheduleType.PROGRESSIVE);
         given(loan.isInterestBearing()).willReturn(false);
         given(loan.getLoanTransactions()).willReturn(List.of());
+
+        MonetaryCurrency currency = new MonetaryCurrency("USD", 2, null);
+        given(loan.getCurrency()).willReturn(currency);
+
+        Money principalOutstanding = mock(Money.class);
+        given(principalOutstanding.isGreaterThanZero()).willReturn(true);
+
+        if (withOverdueInstallments) {
+            LoanRepaymentScheduleInstallment overdueInstallment = 
mock(LoanRepaymentScheduleInstallment.class);
+            
given(overdueInstallment.getDueDate()).willReturn(actualDate.minusDays(5));
+            
given(overdueInstallment.getPrincipalOutstanding(currency)).willReturn(principalOutstanding);
+            
given(loan.getRepaymentScheduleInstallments()).willReturn(List.of(overdueInstallment));
+        } else {
+            LoanRepaymentScheduleInstallment futureInstallment = 
mock(LoanRepaymentScheduleInstallment.class);
+            
given(futureInstallment.getDueDate()).willReturn(actualDate.plusDays(10));
+            
given(futureInstallment.getPrincipalOutstanding(currency)).willReturn(principalOutstanding);
+            
given(loan.getRepaymentScheduleInstallments()).willReturn(List.of(futureInstallment));
+        }
         return loan;
     }
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
index b6ac4b30a8..5236d01842 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java
@@ -18,7 +18,10 @@
  */
 package org.apache.fineract.integrationtests.loan.reamortization;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
 import java.util.concurrent.atomic.AtomicLong;
@@ -27,6 +30,7 @@ import 
org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
@@ -894,6 +898,127 @@ public class LoanReAmortizationIntegrationTest extends 
BaseLoanIntegrationTest {
 
     }
 
+    @Test
+    public void reAmortizationOnDisbursementDayInterestBearingLoanTest() {
+        runAt("01 January 2024", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 6;
+            int repaymentEvery = 1;
+
+            // Create Interest-Bearing Loan Product with 7% interest 
(progressive schedule)
+            Long loanProductId = 
createInterestBearingProgressiveLoanProduct(numberOfRepayments, repaymentEvery);
+
+            // Apply for loan with 200 amount
+            double applyAmount = 200.0;
+            double approveAmount = 100.0;
+            double disburseAmount = 100.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductId, "01 January 2024", applyAmount,
+                    numberOfRepayments)//
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .interestRatePerPeriod(BigDecimal.valueOf(7.0))//
+                    
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            loanId.set(postLoansResponse.getLoanId());
+
+            // Approve with 100 (partial approval)
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(approveAmount, "01 January 2024"));
+
+            // Disburse 100 on Jan 1, 2024
+            disburseLoan(loanId.get(), BigDecimal.valueOf(disburseAmount), "01 
January 2024");
+
+            // Verify disbursement transaction exists
+            verifyTransactions(loanId.get(), //
+                    transaction(100.0, "Disbursement", "01 January 2024") //
+            );
+
+            // Re-amortize loan on the same day as disbursement should throw 
exception
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> reAmortizeLoan(loanId.get(), 
LoanReAmortizationInterestHandlingType.DEFAULT.name()));
+
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("error.msg.loan.reamortize.no.overdue.amount"));
+        });
+    }
+
+    @Test
+    public void reAmortizationOnDisbursementDayEqualInterestSplitTest() {
+        runAt("01 January 2024", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 6;
+            int repaymentEvery = 1;
+
+            // Create Interest-Bearing Loan Product with 7% interest 
(progressive schedule)
+            Long loanProductId = 
createInterestBearingProgressiveLoanProduct(numberOfRepayments, repaymentEvery);
+
+            // Apply for loan with 200 amount
+            double applyAmount = 200.0;
+            double approveAmount = 100.0;
+            double disburseAmount = 100.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductId, "01 January 2024", applyAmount,
+                    numberOfRepayments)//
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .interestRatePerPeriod(BigDecimal.valueOf(7.0))//
+                    
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            loanId.set(postLoansResponse.getLoanId());
+
+            // Approve with 100 (partial approval)
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(approveAmount, "01 January 2024"));
+
+            // Disburse 100 on Jan 1, 2024
+            disburseLoan(loanId.get(), BigDecimal.valueOf(disburseAmount), "01 
January 2024");
+
+            // Verify disbursement transaction exists
+            verifyTransactions(loanId.get(), //
+                    transaction(100.0, "Disbursement", "01 January 2024") //
+            );
+
+            // Re-amortize loan on the same day as disbursement with EQUAL 
interest split should throw exception
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> reAmortizeLoan(loanId.get(), 
LoanReAmortizationInterestHandlingType.EQUAL_AMORTIZATION_INTEREST_SPLIT.name()));
+
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("error.msg.loan.reamortize.no.overdue.amount"));
+        });
+    }
+
+    private Long createInterestBearingProgressiveLoanProduct(int 
numberOfRepayments, int repaymentEvery) {
+        PostLoanProductsRequest product = create4IProgressive()//
+                .numberOfRepayments(numberOfRepayments)//
+                .repaymentEvery(repaymentEvery)//
+                .repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L)//
+                .interestRatePerPeriod(7.0)// 7% annual interest
+                .multiDisburseLoan(true)//
+                .maxTrancheCount(10)//
+                .outstandingLoanBalance(10000.0)//
+                .disallowExpectedDisbursements(true)//
+                .allowApprovedDisbursedAmountsOverApplied(true)//
+                .overAppliedCalculationType("percentage")//
+                .overAppliedNumber(50);
+
+        PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+        GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = 
loanProductHelper
+                .retrieveLoanProductById(loanProductResponse.getResourceId());
+        assertNotNull(getLoanProductsProductIdResponse);
+        return loanProductResponse.getResourceId();
+    }
+
     private Long 
createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(int 
numberOfInstallments, int repaymentEvery) {
         return 
createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(numberOfInstallments,
 repaymentEvery,
                 DOWN_PAYMENT_PERCENTAGE);

Reply via email to