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);