FINERACT-2354: handle reamortization error code 500 issue on same disbursement day
This commit is contained in:
+39
@@ -105,6 +105,31 @@ public class LoanReAmortizationStepDef extends AbstractStepDef {
|
|||||||
testContext().set(TestContextKey.LOAN_REAMORTIZATION_UNDO_RESPONSE, response);
|
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")
|
@When("Admin creates a Loan re-amortization transaction on current business date is forbidden as loan was charged-off")
|
||||||
public void reAmortizationChargedOffLoanFailure() {
|
public void reAmortizationChargedOffLoanFailure() {
|
||||||
reAmortizationFailure(ErrorMessageHelper.reAmortizeChargedOffLoanFailure());
|
reAmortizationFailure(ErrorMessageHelper.reAmortizeChargedOffLoanFailure());
|
||||||
@@ -170,6 +195,20 @@ public class LoanReAmortizationStepDef extends AbstractStepDef {
|
|||||||
testContext().set(TestContextKey.LOAN_REAMORTIZATION_PREVIEW_RESPONSE, response);
|
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:")
|
@Then("Loan Re-Amortization Repayment schedule preview has the following data in Total row:")
|
||||||
public void loanRepaymentDScheduleAmountCheck(final DataTable table) {
|
public void loanRepaymentDScheduleAmountCheck(final DataTable table) {
|
||||||
final List<List<String>> data = table.asLists();
|
final List<List<String>> data = table.asLists();
|
||||||
|
|||||||
@@ -5101,9 +5101,8 @@ Feature: LoanReAmortization
|
|||||||
When Loan Pay-off is made on "02 April 2024"
|
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
|
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
|
||||||
|
|
||||||
@Skip
|
|
||||||
@TestRailId:C4510 @AdvancedPaymentAllocation
|
@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 sets the business date to "01 January 2024"
|
||||||
When Admin creates a client with random data
|
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
|
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:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
| 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 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
# --- re-amortization transaction --- 500 error!!
|
# --- 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
|
And Admin creates a Loan re-amortization transaction on current business date but fails with 403 error
|
||||||
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 |
|
|
||||||
# --- close the loan --- #
|
# --- close the loan --- #
|
||||||
When Loan Pay-off is made on "01 January 2024"
|
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
|
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
|
||||||
|
|
||||||
@Skip
|
|
||||||
@TestRailId:C4511 @AdvancedPaymentAllocation
|
@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 sets the business date to "01 January 2024"
|
||||||
When Admin creates a client with random data
|
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
|
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:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
| 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 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
# --- re-amortization transaction --- 500 error!!
|
# --- 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"
|
And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" but fails with 403 error
|
||||||
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 |
|
|
||||||
# --- close the loan --- #
|
# --- close the loan --- #
|
||||||
When Loan Pay-off is made on "01 January 2024"
|
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
|
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
|
||||||
|
|||||||
+2
-14
@@ -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 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 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 |
|
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false |
|
||||||
# --- Re-amortization repayment schedule preview ---
|
# --- 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:
|
And Admin creates a Loan re-amortization preview by Loan external ID with the following data, but fails with 403 error code:
|
||||||
| reAmortizationInterestHandling |
|
| reAmortizationInterestHandling |
|
||||||
| DEFAULT |
|
| 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"
|
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
|
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
|
||||||
+9
@@ -131,6 +131,15 @@ public class LoanReAmortizationValidator {
|
|||||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.not.allowed.on.contract.terminated",
|
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.not.allowed.on.contract.terminated",
|
||||||
"Loan re-amortization is not allowed on contract terminated loan.", loan.getId());
|
"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) {
|
public LoanTransaction findAndValidateReAmortizeTransactionForUndo(Loan loan) {
|
||||||
|
|||||||
+38
@@ -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.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
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.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
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");
|
.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
|
@Test
|
||||||
public void testValidateUndoReAmortize_ShouldThrowException_WhenLoanDoesntHaveReAmortization() {
|
public void testValidateUndoReAmortize_ShouldThrowException_WhenLoanDoesntHaveReAmortization() {
|
||||||
// given
|
// given
|
||||||
@@ -238,6 +254,10 @@ class LoanReAmortizationValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Loan loan() {
|
private Loan loan() {
|
||||||
|
return loan(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Loan loan(boolean withOverdueInstallments) {
|
||||||
Loan loan = mock(Loan.class);
|
Loan loan = mock(Loan.class);
|
||||||
given(loan.getStatus()).willReturn(LoanStatus.ACTIVE);
|
given(loan.getStatus()).willReturn(LoanStatus.ACTIVE);
|
||||||
given(loan.getMaturityDate()).willReturn(actualDate.plusDays(30));
|
given(loan.getMaturityDate()).willReturn(actualDate.plusDays(30));
|
||||||
@@ -248,6 +268,24 @@ class LoanReAmortizationValidatorTest {
|
|||||||
given(loanProductRelatedDetail.getLoanScheduleType()).willReturn(LoanScheduleType.PROGRESSIVE);
|
given(loanProductRelatedDetail.getLoanScheduleType()).willReturn(LoanScheduleType.PROGRESSIVE);
|
||||||
given(loan.isInterestBearing()).willReturn(false);
|
given(loan.isInterestBearing()).willReturn(false);
|
||||||
given(loan.getLoanTransactions()).willReturn(List.of());
|
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;
|
return loan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+125
@@ -18,7 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests.loan.reamortization;
|
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.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
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.PostLoanProductsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansRequest;
|
import org.apache.fineract.client.models.PostLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
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.BaseLoanIntegrationTest;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
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) {
|
private Long createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(int numberOfInstallments, int repaymentEvery) {
|
||||||
return createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(numberOfInstallments, repaymentEvery,
|
return createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(numberOfInstallments, repaymentEvery,
|
||||||
DOWN_PAYMENT_PERCENTAGE);
|
DOWN_PAYMENT_PERCENTAGE);
|
||||||
|
|||||||
Reference in New Issue
Block a user