From 97fc7c956cf17f7b91f8cdf19ddb60a38c4cac7c Mon Sep 17 00:00:00 2001 From: DeathGun44 Date: Sat, 20 Jun 2026 11:15:12 +0530 Subject: [PATCH] FINERACT-2649: Migrate Tier 1 loan integration tests to Feign client Signed-off-by: DeathGun44 --- ...FixedLengthLoanProductIntegrationTest.java | 59 ++- .../LoanDueCalculationTest.java | 177 ++++---- .../LoanInterestRateFrequencyTest.java | 49 +-- .../LoanPrepayAmountTest.java | 79 ++-- ...LoanProductWithChargeOffBehaviourTest.java | 27 +- .../client/feign/FeignLoanTestBase.java | 392 ++++++++++++++++++ .../helpers/FeignBusinessDateHelper.java | 2 +- .../feign/helpers/FeignClientHelper.java | 2 +- .../client/feign/helpers/FeignLoanHelper.java | 11 + .../feign/modules/LoanProductTemplates.java | 41 ++ .../feign/modules/LoanRequestBuilders.java | 67 +++ .../feign/modules/LoanTestValidators.java | 163 ++++++++ 12 files changed, 857 insertions(+), 212 deletions(-) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java index 82bcf8b8dd3..b4eae30da9b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java @@ -21,45 +21,40 @@ import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse; 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.models.PutLoanProductsProductIdRequest; import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; -import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase; +import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class FixedLengthLoanProductIntegrationTest extends BaseLoanIntegrationTest { +public class FixedLengthLoanProductIntegrationTest extends FeignLoanTestBase { @Test public void testCreateReadUpdateReadLoanProductWithFixedLength() { // create with 4 PostLoanProductsRequest loanProductsRequest = fixedLengthLoanProduct(4); - PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(loanProductsRequest); - Assertions.assertNotNull(loanProduct.getResourceId()); + Long loanProductId = createLoanProduct(loanProductsRequest); + Assertions.assertNotNull(loanProductId); // read - GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper - .retrieveLoanProductById(loanProduct.getResourceId()); + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = retrieveLoanProduct(loanProductId); Assertions.assertEquals(4, getLoanProductsProductIdResponse.getFixedLength()); // update to 5 PutLoanProductsProductIdRequest updateRequest = new PutLoanProductsProductIdRequest().fixedLength(5).locale("en"); - PutLoanProductsProductIdResponse putLoanProductsProductIdResponse = loanProductHelper - .updateLoanProductById(loanProduct.getResourceId(), updateRequest); + PutLoanProductsProductIdResponse putLoanProductsProductIdResponse = updateLoanProduct(loanProductId, updateRequest); Assertions.assertNotNull(putLoanProductsProductIdResponse.getResourceId()); // read again - getLoanProductsProductIdResponse = loanProductHelper.retrieveLoanProductById(loanProduct.getResourceId()); + getLoanProductsProductIdResponse = retrieveLoanProduct(loanProductId); Assertions.assertEquals(5, getLoanProductsProductIdResponse.getFixedLength()); - // update to null - loanTransactionHelper.updateLoanProduct(putLoanProductsProductIdResponse.getResourceId(), """ + // update to null (raw JSON required to send explicit null through NON_NULL ObjectMapper) + updateLoanProduct(loanProductId, """ { "fixedLength": null, "locale": "en" @@ -67,26 +62,25 @@ public void testCreateReadUpdateReadLoanProductWithFixedLength() { """); // read again - getLoanProductsProductIdResponse = loanProductHelper.retrieveLoanProductById(loanProduct.getResourceId()); + getLoanProductsProductIdResponse = retrieveLoanProduct(loanProductId); Assertions.assertNull(getLoanProductsProductIdResponse.getFixedLength()); } @Test public void testLoanApplicationWithFixedLengthInheritedFromLoanProduct() { runAt("01 January 2023", () -> { - Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + Long clientId = createClient(); - PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(fixedLengthLoanProduct(4)); - Assertions.assertNotNull(loanProduct.getResourceId()); + Long loanProductId = createLoanProduct(fixedLengthLoanProduct(4)); + Assertions.assertNotNull(loanProductId); - PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProduct.getResourceId(), "01 January 2023", 1000.0, 4); + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", 1000.0, 4); applicationRequest = applicationRequest .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY); - PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest); - Long loanId = loanResponse.getLoanId(); + Long loanId = applyForLoan(applicationRequest); - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals(4, loanDetails.getFixedLength()); }); } @@ -94,19 +88,18 @@ public void testLoanApplicationWithFixedLengthInheritedFromLoanProduct() { @Test public void testLoanApplicationWithFixedLengthOverriddenByLoanApplication() { runAt("01 January 2023", () -> { - Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + Long clientId = createClient(); - PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(fixedLengthLoanProduct(4)); - Assertions.assertNotNull(loanProduct.getResourceId()); + Long loanProductId = createLoanProduct(fixedLengthLoanProduct(4)); + Assertions.assertNotNull(loanProductId); - PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProduct.getResourceId(), "01 January 2023", 1000.0, 4); + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", 1000.0, 4); applicationRequest = applicationRequest.fixedLength(5).repaymentEvery(1).repaymentFrequencyType(2).loanTermFrequencyType(2) .loanTermFrequency(4).transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY); - PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest); - Long loanId = loanResponse.getLoanId(); + Long loanId = applyForLoan(applicationRequest); - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals(5, loanDetails.getFixedLength()); }); } @@ -114,10 +107,10 @@ public void testLoanApplicationWithFixedLengthOverriddenByLoanApplication() { private PostLoanProductsRequest fixedLengthLoanProduct(Integer fixedLength) { return createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() // .numberOfRepayments(4).repaymentEvery(1) // - .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()) // - .loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) // + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS.longValue()) // + .loanScheduleType("PROGRESSIVE") // .transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) // - .loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()) // + .loanScheduleProcessingType("HORIZONTAL") // .interestRatePerPeriod(0.0).fixedLength(fixedLength); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java index bb1b9284a5d..f08f26e2b38 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java @@ -24,10 +24,8 @@ import java.time.LocalDate; import java.util.stream.Stream; 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.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.junit.jupiter.api.Named; @@ -35,7 +33,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -public class LoanDueCalculationTest extends BaseLoanIntegrationTest { +public class LoanDueCalculationTest extends FeignLoanTestBase { private static Stream processingStrategy() { return Stream.of( @@ -51,37 +49,35 @@ private static Stream processingStrategy() { @MethodSource("processingStrategy") public void dueDateBasedOnFirstRepaymentDate(String repaymentProcessor) { runAt("2 February 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd") - .repaymentsStartingFromDate(LocalDate.of(2024, 2, 29)); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-01-31", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd") + .repaymentsStartingFromDate(LocalDate.of(2024, 2, 29)); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "29 March 2024"), // installment(250.0, false, "29 April 2024"), // installment(250.0, false, "29 May 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "31 January 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "29 March 2024"), // installment(250.0, false, "29 April 2024"), // installment(250.0, false, "29 May 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "31 January 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "29 March 2024"), // installment(250.0, false, "29 April 2024"), // @@ -100,37 +96,35 @@ public void dueDateBasedOnFirstRepaymentDate(String repaymentProcessor) { @MethodSource("processingStrategy") public void dueDateBasedOnExpectedDisbursementDate(String repaymentProcessor) { runAt("31 March 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor) .repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue()); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-30", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd"); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "30 January 2024"), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-01-30", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd"); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, installment(1000.0, null, "30 January 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "30 March 2024"), // installment(250.0, false, "30 April 2024"), // installment(250.0, false, "30 May 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "30 January 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "30 January 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "30 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "30 January 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "30 March 2024"), // installment(250.0, false, "30 April 2024"), // installment(250.0, false, "30 May 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 March 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "31 March 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 March 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 March 2024"), // installment(250.0, false, "30 April 2024"), // installment(250.0, false, "31 May 2024"), // installment(250.0, false, "30 June 2024"), // @@ -148,19 +142,17 @@ public void dueDateBasedOnExpectedDisbursementDate(String repaymentProcessor) { @MethodSource("processingStrategy") public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) { runAt("03 February 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor) .repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue()); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-02-01", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-02-01", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "01 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // @@ -168,9 +160,9 @@ public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) { installment(250.0, false, "31 May 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "31 January 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "01 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // @@ -178,9 +170,9 @@ public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) { installment(250.0, false, "31 May 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "03 February 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "03 February 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "03 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // @@ -198,38 +190,36 @@ public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) { @MethodSource("processingStrategy") public void dueDateBasedOnSubmittedOnDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment(String repaymentProcessor) { runAt("31 January 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor) .repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue()) .minimumDaysBetweenDisbursalAndFirstRepayment(10); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-02-26", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "26 February 2024"), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-02-26", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, installment(1000.0, null, "26 February 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // installment(250.0, false, "07 June 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "31 January 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "26 February 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "26 February 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // installment(250.0, false, "07 June 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "31 January 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // @@ -247,38 +237,36 @@ public void dueDateBasedOnSubmittedOnDateButThereShallBeMinimumDaysBetweenDisbur public void dueDateBasedOnExpectedDisbursalDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment( String repaymentProcessor) { runAt("31 January 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor) .repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue()) .minimumDaysBetweenDisbursalAndFirstRepayment(36); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-01-31", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // installment(250.0, false, "07 June 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "31 January 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // installment(250.0, false, "07 June 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "31 January 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), // + verifyRepaymentSchedule(loanId, installment(1000.0, null, "31 January 2024"), // installment(250.0, false, "07 March 2024"), // installment(250.0, false, "07 April 2024"), // installment(250.0, false, "07 May 2024"), // @@ -297,19 +285,17 @@ public void dueDateBasedOnExpectedDisbursalDateButThereShallBeMinimumDaysBetween @MethodSource("processingStrategy") public void dueDateBasedOnSubmittedOnDateButChangingExpectedDisbursementAtApproval(String repaymentProcessor) { runAt("03 February 2024", () -> { - // Client and Loan account creation - final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long clientId = createClient(); PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor) .repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue()); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); - - PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-02-01", 1000.0, 4, - (postLoansRequest) -> { - postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) - .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); - }); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + Long loanProductId = createLoanProduct(loanProductsRequest); + + PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductId, "2024-02-01", 1000.0, 4, (postLoansRequest) -> { + postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2) + .loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd"); + }); + Long loanId = applyForLoan(loanRequest); + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "01 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // @@ -317,10 +303,9 @@ public void dueDateBasedOnSubmittedOnDateButChangingExpectedDisbursementAtApprov installment(250.0, false, "31 May 2024")) // ; - loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(1000.0, "31 January 2024", "02 February 2024")); + approveLoan(loanId, approveLoanRequest(1000.0, "31 January 2024", "02 February 2024")); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "02 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // @@ -328,9 +313,9 @@ public void dueDateBasedOnSubmittedOnDateButChangingExpectedDisbursementAtApprov installment(250.0, false, "31 May 2024")) // ; - disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "03 February 2024"); + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "03 February 2024"); - verifyRepaymentSchedule(postLoansResponse.getLoanId(), // + verifyRepaymentSchedule(loanId, // installment(1000.0, null, "03 February 2024"), // installment(250.0, false, "29 February 2024"), // installment(250.0, false, "31 March 2024"), // diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRateFrequencyTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRateFrequencyTest.java index 356b59e32da..68b5a7fee1e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRateFrequencyTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRateFrequencyTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.DATETIME_PATTERN; import static org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY; import java.math.BigDecimal; @@ -27,38 +28,31 @@ import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostLoanProductsRequest; -import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoansRequest; -import org.apache.fineract.client.models.PostLoansResponse; -import org.apache.fineract.integrationtests.common.ClientHelper; -import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase; +import org.apache.fineract.integrationtests.client.feign.modules.LoanRequestBuilders; +import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; @Slf4j -@ExtendWith({ LoanTestLifecycleExtension.class }) -public class LoanInterestRateFrequencyTest extends BaseLoanIntegrationTest { +public class LoanInterestRateFrequencyTest extends FeignLoanTestBase { @Test public void testProgressiveInterestRateTypeWholeTerm() { runAt("15 April 2024", () -> { - // Create Client - Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + Long clientId = createClient(); - // Create Loan Product PostLoanProductsRequest loanProductsRequest = createLoanProductWithInterestCalculation(); - PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest); + Long loanProductId = createLoanProduct(loanProductsRequest); - // Apply and Approve Loan - Long loanId = applyAndApproveLoanApplication(clientId, loanProductResponse.getResourceId(), "15 April 2024", 1000.0, 6); + Long loanId = applyAndApproveLoanApplication(clientId, loanProductId, "15 April 2024", 1000.0, 6); - // Disburse Loan disburseLoan(loanId, BigDecimal.valueOf(1000), "15 April 2024"); - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals(loanDetails.getInterestRateFrequencyType().getCode(), "interestRateFrequency.periodFrequencyType.whole_term"); Assertions.assertEquals(loanDetails.getAnnualInterestRate(), new BigDecimal("20.000000")); @@ -73,21 +67,20 @@ private Long applyAndApproveLoanApplication(Long clientId, Long productId, Strin .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY) // .locale("en") // .submittedOnDate(disbursementDate) // - .amortizationType(AmortizationType.EQUAL_INSTALLMENTS) // + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS) // .interestRatePerPeriod(new BigDecimal(10.0)) // - .interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD) // - .interestType(InterestType.DECLINING_BALANCE) // + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD) // + .interestType(LoanTestData.InterestType.DECLINING_BALANCE) // .repaymentEvery(1) // - .repaymentFrequencyType(RepaymentFrequencyType.MONTHS) // + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS) // .numberOfRepayments(numberOfRepayments) // .loanTermFrequency(numberOfRepayments) // .loanTermFrequencyType(2) // .maxOutstandingLoanBalance(BigDecimal.valueOf(amount)) // .principal(BigDecimal.valueOf(amount)) // .loanType("individual"); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(postLoansRequest); - PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount, disbursementDate)); + Long loanId = applyForLoan(postLoansRequest); + PostLoansLoanIdResponse approvedLoanResult = approveLoan(loanId, LoanRequestBuilders.approveLoan(amount, disbursementDate)); return approvedLoanResult.getLoanId(); } @@ -98,18 +91,18 @@ private PostLoanProductsRequest createLoanProductWithInterestCalculation() { .overAppliedCalculationType(null)// .overAppliedNumber(null)// .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY) // - .paymentAllocation(List.of(createDefaultPaymentAllocation(), createRepaymentPaymentAllocation())) // + .paymentAllocation(List.of(createDefaultPaymentAllocation("NEXT_INSTALLMENT"), createRepaymentPaymentAllocation())) // .loanScheduleType("PROGRESSIVE") // .loanScheduleProcessingType("HORIZONTAL") // .principal(1000.0)// .numberOfRepayments(6)// .repaymentEvery(1)// - .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// - .interestType(BaseLoanIntegrationTest.InterestType.DECLINING_BALANCE)// - .amortizationType(BaseLoanIntegrationTest.AmortizationType.EQUAL_INSTALLMENTS)// - .interestCalculationPeriodType(BaseLoanIntegrationTest.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS.longValue())// + .interestType(LoanTestData.InterestType.DECLINING_BALANCE)// + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)// + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)// .interestRatePerPeriod(10.0) // - .interestRateFrequencyType(InterestRateFrequencyType.WHOLE_TERM)// + .interestRateFrequencyType(LoanTestData.InterestRateFrequencyType.WHOLE_TERM)// .isInterestRecalculationEnabled(false); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java index 3c81b5da593..c86c56fbf47 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java @@ -18,33 +18,39 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.integrationtests.client.feign.modules.LoanTestData.DATETIME_PATTERN; + import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; -import org.apache.fineract.client.models.PostLoanProductsResponse; -import org.apache.fineract.client.models.PostLoansResponse; -import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase; +import org.apache.fineract.integrationtests.client.feign.modules.LoanRequestBuilders; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @Slf4j -public class LoanPrepayAmountTest extends BaseLoanIntegrationTest { +public class LoanPrepayAmountTest extends FeignLoanTestBase { - Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + Long clientId; Long loanId; + @BeforeEach + public void setup() { + clientId = createClient(); + } + @Test public void testLoanPrepayAmountProgressive() { runAt("1 January 2024", () -> { - final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId, - loanProductsResponse.getResourceId(), "01 January 2024", 1000.0, 9.99, 6, null)); - loanId = postLoansResponse.getLoanId(); - loanTransactionHelper.approveLoan(loanId, approveLoanRequest(1000.0, "01 January 2024")); + final Long loanProductId = createLoanProduct(create4IProgressive()); + Long loanIdLocal = applyForLoan( + applyLP2ProgressiveLoanRequest(clientId, loanProductId, "01 January 2024", 1000.0, 9.99, 6, null)); + loanId = loanIdLocal; + approveLoan(loanId, LoanRequestBuilders.approveLoan(1000.0, "01 January 2024")); disburseLoan(loanId, BigDecimal.valueOf(250.0), "01 January 2024"); }); runAt("7 january 2024", () -> { @@ -53,9 +59,8 @@ public void testLoanPrepayAmountProgressive() { }); for (int i = 7; i <= 31; i++) { runAt(i + " January 2024", () -> { - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); - GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId, null, - DATETIME_PATTERN); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); + GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = getPrepaymentAmount(loanId, null, DATETIME_PATTERN); Assertions.assertEquals(BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros(), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros()); }); @@ -65,32 +70,32 @@ public void testLoanPrepayAmountProgressive() { @Test public void testLoanPrepayAmountProgressivePartialRepayment() { runAt("15 March 2025", () -> { - final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct( + final Long loanProductId = createLoanProduct( create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true)); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId, - loanProductsResponse.getResourceId(), "15 March 2025", 296.79, 35.99, 12, null)); - loanId = postLoansResponse.getLoanId(); - loanTransactionHelper.approveLoan(loanId, approveLoanRequest(296.79, "15 March 2025")); + Long loanIdLocal = applyForLoan( + applyLP2ProgressiveLoanRequest(clientId, loanProductId, "15 March 2025", 296.79, 35.99, 12, null)); + loanId = loanIdLocal; + approveLoan(loanId, LoanRequestBuilders.approveLoan(296.79, "15 March 2025")); disburseLoan(loanId, BigDecimal.valueOf(296.79), "15 March 2025"); }); runAt("16 March 2025", () -> { - loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "16 March 2025", 59.0); - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + addRepaymentForLoan(loanId, 59.0, "16 March 2025"); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros()); Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros()); - GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId, - "16 March 2025", DATETIME_PATTERN); + GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = getPrepaymentAmount(loanId, "16 March 2025", + DATETIME_PATTERN); Assertions.assertEquals(BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros(), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest()); }); for (int i = 1; i < 4; i++) { - inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); + executeInlineCOB(loanId); LocalDate date = LocalDate.of(2025, 3, 17).plusDays(i * 11); String formattedDate = DateTimeFormatter.ofPattern(DATETIME_PATTERN).format(date); runAt(formattedDate, () -> { - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); - GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId, - formattedDate, DATETIME_PATTERN); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); + GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = getPrepaymentAmount(loanId, formattedDate, + DATETIME_PATTERN); Assertions.assertEquals(loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros(), BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros()); }); @@ -100,22 +105,22 @@ public void testLoanPrepayAmountProgressivePartialRepayment() { @Test public void testLoanPrepayAmountProgressivePartialRepaymentNoInterestRecalculation() { runAt("15 March 2025", () -> { - final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct( + final Long loanProductId = createLoanProduct( create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(false)); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId, - loanProductsResponse.getResourceId(), "15 March 2025", 296.79, 35.99, 12, null)); - loanId = postLoansResponse.getLoanId(); - loanTransactionHelper.approveLoan(loanId, approveLoanRequest(296.79, "15 March 2025")); + Long loanIdLocal = applyForLoan( + applyLP2ProgressiveLoanRequest(clientId, loanProductId, "15 March 2025", 296.79, 35.99, 12, null)); + loanId = loanIdLocal; + approveLoan(loanId, LoanRequestBuilders.approveLoan(296.79, "15 March 2025")); disburseLoan(loanId, BigDecimal.valueOf(296.79), "15 March 2025"); - inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); + executeInlineCOB(loanId); }); runAt("16 March 2025", () -> { - loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "16 March 2025", 59.0); - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + addRepaymentForLoan(loanId, 59.0, "16 March 2025"); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros()); Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros()); - GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId, - "16 March 2025", DATETIME_PATTERN); + GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = getPrepaymentAmount(loanId, "16 March 2025", + DATETIME_PATTERN); Assertions.assertEquals(BigDecimal.valueOf(44.43).stripTrailingZeros(), BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros()); }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithChargeOffBehaviourTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithChargeOffBehaviourTest.java index 363a3107372..f3937c637e0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithChargeOffBehaviourTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithChargeOffBehaviourTest.java @@ -21,15 +21,14 @@ import java.math.BigDecimal; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse; -import org.apache.fineract.client.models.PostLoanProductsResponse; -import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; -import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase; +import org.apache.fineract.integrationtests.client.feign.modules.LoanRequestBuilders; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class LoanProductWithChargeOffBehaviourTest extends BaseLoanIntegrationTest { +public class LoanProductWithChargeOffBehaviourTest extends FeignLoanTestBase { private Long clientId; private Long loanProductId; @@ -41,13 +40,10 @@ public class LoanProductWithChargeOffBehaviourTest extends BaseLoanIntegrationTe @BeforeEach public void beforeEach() { runAt("01 June 2024", () -> { - clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); - final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()); - loanProductId = loanProductsResponse.getResourceId(); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan( - applyLP2ProgressiveLoanRequest(clientId, loanProductsResponse.getResourceId(), "01 June 2024", 1000.0, 10.0, 4, null)); - loanId = postLoansResponse.getLoanId(); - loanTransactionHelper.approveLoan(loanId, approveLoanRequest(1000.0, "01 June 2024")); + clientId = createClient(); + loanProductId = createLoanProduct(create4IProgressive()); + loanId = applyForLoan(applyLP2ProgressiveLoanRequest(clientId, loanProductId, "01 June 2024", 1000.0, 10.0, 4, null)); + approveLoan(loanId, LoanRequestBuilders.approveLoan(1000.0, "01 June 2024")); disburseLoan(loanId, BigDecimal.valueOf(250.0), "01 June 2024"); }); } @@ -55,15 +51,14 @@ public void beforeEach() { @Test public void testSavedToLoanNotChangingWithProduct() { runAt("01 June 2024", () -> { - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); Assertions.assertEquals("REGULAR", loanDetails.getChargeOffBehaviour().getId()); - loanProductHelper.updateLoanProductById(loanProductId, - new PutLoanProductsProductIdRequest().chargeOffBehaviour("ZERO_INTEREST")); - final GetLoanProductsProductIdResponse loanProduct = loanTransactionHelper.getLoanProduct(loanProductId.intValue()); + updateLoanProduct(loanProductId, new PutLoanProductsProductIdRequest().chargeOffBehaviour("ZERO_INTEREST")); + final GetLoanProductsProductIdResponse loanProduct = retrieveLoanProduct(loanProductId); Assertions.assertEquals("ZERO_INTEREST", loanProduct.getChargeOffBehaviour().getId()); - loanDetails = loanTransactionHelper.getLoanDetails(loanId); + loanDetails = getLoanDetails(loanId); Assertions.assertEquals("REGULAR", loanDetails.getChargeOffBehaviour().getId()); }); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/FeignLoanTestBase.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/FeignLoanTestBase.java index cee6b179ed9..1f2f6620fb6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/FeignLoanTestBase.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/FeignLoanTestBase.java @@ -18,15 +18,27 @@ */ package org.apache.fineract.integrationtests.client.feign; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.math.BigDecimal; import java.time.LocalDate; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Function; import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.DeleteLoansLoanIdChargesChargeIdResponse; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdChargesChargeIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdStatus; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; +import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest; @@ -39,6 +51,8 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; import org.apache.fineract.integrationtests.client.FeignIntegrationTest; import org.apache.fineract.integrationtests.client.feign.helpers.FeignAccountHelper; import org.apache.fineract.integrationtests.client.feign.helpers.FeignBusinessDateHelper; @@ -53,6 +67,7 @@ import org.apache.fineract.integrationtests.client.feign.modules.LoanTestData; import org.apache.fineract.integrationtests.client.feign.modules.LoanTestValidators; import org.apache.fineract.integrationtests.common.FineractFeignClientHelper; +import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.ExtendWith; @@ -60,6 +75,8 @@ @ExtendWith(LoanTestLifecycleExtension.class) public abstract class FeignLoanTestBase extends FeignIntegrationTest implements LoanProductTemplates { + private static final String LOAN_PRODUCTS_URL = "/fineract-provider/api/v1/loanproducts"; + protected static FeignAccountHelper accountHelper; protected static FeignLoanHelper loanHelper; protected static FeignTransactionHelper transactionHelper; @@ -68,6 +85,12 @@ public abstract class FeignLoanTestBase extends FeignIntegrationTest implements protected static FeignClientHelper clientHelper; protected static FeignChargesHelper chargesHelper; protected static LoanTestAccounts accounts; + protected static RequestSpecification requestSpec; + protected static ResponseSpecification responseSpec; + + static { + Utils.initializeRESTAssured(); + } @BeforeAll public static void setupHelpers() { @@ -79,6 +102,16 @@ public static void setupHelpers() { businessDateHelper = new FeignBusinessDateHelper(client); clientHelper = new FeignClientHelper(client); chargesHelper = new FeignChargesHelper(client); + requestSpec = createRequestSpec(); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + } + + private static RequestSpecification createRequestSpec() { + String authKey = Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(); + RequestSpecification spec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + spec.header("Authorization", "Basic " + authKey); + spec.header("Fineract-Platform-TenantId", "default"); + return spec; } protected LoanTestAccounts getAccounts() { @@ -120,6 +153,23 @@ protected Long createLoanProduct(PostLoanProductsRequest request) { return loanHelper.createLoanProduct(request); } + protected GetLoanProductsProductIdResponse retrieveLoanProduct(Long productId) { + return loanHelper.retrieveLoanProduct(productId); + } + + protected PutLoanProductsProductIdResponse updateLoanProduct(Long productId, PutLoanProductsProductIdRequest request) { + return loanHelper.updateLoanProduct(productId, request); + } + + /** + * Updates a loan product using raw JSON. Use this overload when you need to send explicit null values in the + * request body, which the typed Feign client cannot express due to the global NON_NULL ObjectMapper configuration. + */ + protected void updateLoanProduct(Long productId, String rawJsonBody) { + final String url = LOAN_PRODUCTS_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER; + Utils.performServerPut(requestSpec, responseSpec, url, rawJsonBody); + } + protected Long applyForLoan(PostLoansRequest request) { return loanHelper.applyForLoan(request); } @@ -128,6 +178,14 @@ protected PostLoansLoanIdResponse approveLoan(Long loanId, PostLoansLoanIdReques return loanHelper.approveLoan(loanId, request); } + protected PostLoansLoanIdRequest approveLoanRequest(Double amount, String approvalDate) { + return LoanRequestBuilders.approveLoan(amount, approvalDate); + } + + protected PostLoansLoanIdRequest approveLoanRequest(Double amount, String approvalDate, String expectedDisbursementDate) { + return LoanRequestBuilders.approveLoan(amount, approvalDate, expectedDisbursementDate); + } + protected PostLoansLoanIdResponse disburseLoan(Long loanId, PostLoansLoanIdRequest request) { return loanHelper.disburseLoan(loanId, request); } @@ -337,4 +395,338 @@ protected PostLoansLoanIdTransactionsRequest reAge(String startDate, String freq Integer numberOfInstallments) { return LoanRequestBuilders.reAge(startDate, frequencyType, frequencyNumber, numberOfInstallments); } + + protected LoanTestData.TransactionExt transaction(double amount, String type, String date, double outstandingPrincipal, + double principalPortion, double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, + double overpaymentPortion) { + return new LoanTestData.TransactionExt(amount, type, date, outstandingPrincipal, principalPortion, interestPortion, feePortion, + penaltyPortion, unrecognizedIncomePortion, overpaymentPortion, false); + } + + protected LoanTestData.TransactionExt transaction(double amount, String type, String date, double outstandingPrincipal, + double principalPortion, double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, + double overpaymentPortion, boolean reversed) { + return new LoanTestData.TransactionExt(amount, type, date, outstandingPrincipal, principalPortion, interestPortion, feePortion, + penaltyPortion, unrecognizedIncomePortion, overpaymentPortion, reversed); + } + + protected LoanTestData.Installment installment(double principalAmount, Boolean completed, String dueDate) { + return new LoanTestData.Installment(principalAmount, null, null, null, null, completed, dueDate, null, null); + } + + protected LoanTestData.Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, + Boolean completed, String dueDate) { + return new LoanTestData.Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate, null, + null); + } + + protected LoanTestData.Installment installment(double principalAmount, double interestAmount, double feeAmount, + double totalOutstandingAmount, Boolean completed, String dueDate) { + return new LoanTestData.Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, + null, null); + } + + protected LoanTestData.Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount, + double totalOutstandingAmount, Boolean completed, String dueDate) { + return new LoanTestData.Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, + dueDate, null, null); + } + + protected LoanTestData.Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount, + LoanTestData.OutstandingAmounts outstandingAmounts, Boolean completed, String dueDate) { + return new LoanTestData.Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, null, completed, dueDate, + outstandingAmounts, null); + } + + protected LoanTestData.Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount, + double totalOutstanding, Boolean completed, String dueDate, double loanBalance) { + return new LoanTestData.Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstanding, completed, dueDate, + null, loanBalance); + } + + protected LoanTestData.OutstandingAmounts outstanding(double principal, double interestOutstanding, double fee, double penalty, + double total) { + return new LoanTestData.OutstandingAmounts(principal, interestOutstanding, fee, penalty, total); + } + + protected LoanTestData.TransactionExt reversedTransaction(double principalAmount, String type, String date) { + return new LoanTestData.TransactionExt(principalAmount, type, date, null, null, null, null, null, null, null, true); + } + + protected LoanTestData.TransactionExt transaction(double amount, String type, String date) { + return new LoanTestData.TransactionExt(amount, type, date, null, null, null, null, null, null, null, false); + } + + protected void verifyTransactions(Long loanId, LoanTestData.TransactionExt... transactions) { + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); + LoanTestValidators.verifyTransactions(loanDetails, transactions); + } + + protected void verifyRepaymentSchedule(Long loanId, LoanTestData.Installment... installments) { + GetLoansLoanIdResponse loanDetails = getLoanDetails(loanId); + LoanTestValidators.verifyRepaymentSchedule(loanDetails, installments); + } + + protected PostLoanProductsRequest create4IProgressive() { + final Long delinquencyBucketId = org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper + .createDefaultBucket(); + return new PostLoanProductsRequest() + .name(org.apache.fineract.integrationtests.common.Utils.uniqueRandomStringGenerator("4I_PROGRESSIVE_", 6))// + .shortName(org.apache.fineract.integrationtests.common.Utils.uniqueRandomStringGenerator("", 4))// + .description("4 installment product - progressive")// + .includeInBorrowerCycle(false)// + .useBorrowerCycle(false)// + .currencyCode("EUR")// + .digitsAfterDecimal(2)// + .principal(1000.0)// + .minPrincipal(100.0)// + .maxPrincipal(10000.0)// + .numberOfRepayments(4)// + .repaymentEvery(1)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS_L)// + .interestRatePerPeriod(10D)// + .minInterestRatePerPeriod(0D)// + .maxInterestRatePerPeriod(120D)// + .interestRateFrequencyType(LoanTestData.InterestRateFrequencyType.YEARS)// + .isLinkedToFloatingInterestRates(false)// + .allowVariableInstallments(false)// + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)// + .interestType(LoanTestData.InterestType.DECLINING_BALANCE)// + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.DAILY)// + .allowPartialPeriodInterestCalculation(false)// + .transactionProcessingStrategyCode(LoanTestData.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .paymentAllocation(List.of(createDefaultPaymentAllocation("NEXT_INSTALLMENT")))// + .creditAllocation(List.of())// + .overdueDaysForNPA(179)// + .daysInMonthType(LoanTestData.DaysInMonthType.DAYS_30)// + .daysInYearType(LoanTestData.DaysInYearType.DAYS_360)// + .isInterestRecalculationEnabled(true)// + .interestRecalculationCompoundingMethod(LoanTestData.InterestRecalculationCompoundingMethod.NONE)// + .rescheduleStrategyMethod(LoanTestData.RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)// + .recalculationRestFrequencyType(LoanTestData.RecalculationRestFrequencyType.DAILY)// + .recalculationRestFrequencyInterval(1)// + .isArrearsBasedOnOriginalSchedule(false)// + .isCompoundingToBePostedAsTransaction(false)// + .preClosureInterestCalculationStrategy(1)// + .allowCompoundingOnEod(false)// + .canDefineInstallmentAmount(true)// + .repaymentStartDateType(1)// + .charges(List.of())// + .principalVariationsForBorrowerCycle(List.of())// + .interestRateVariationsForBorrowerCycle(List.of())// + .numberOfRepaymentVariationsForBorrowerCycle(List.of())// + .accountingRule(1)// + .canUseForTopup(false)// + .dateFormat(LoanTestData.DATETIME_PATTERN)// + .locale("en")// + .multiDisburseLoan(true)// + .maxTrancheCount(10)// + .outstandingLoanBalance(10000.0)// + .disallowExpectedDisbursements(true)// + .allowApprovedDisbursedAmountsOverApplied(true)// + .overAppliedCalculationType("percentage")// + .overAppliedNumber(50)// + .principalThresholdForLastInstallment(50)// + .holdGuaranteeFunds(false)// + .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)// + .isEqualAmortization(false)// + .delinquencyBucketId(delinquencyBucketId)// + .enableDownPayment(false)// + .enableInstallmentLevelDelinquency(false)// + .loanScheduleType("PROGRESSIVE")// + .loanScheduleProcessingType("HORIZONTAL"); + } + + protected AdvancedPaymentData createDefaultPaymentAllocation(String futureInstallmentAllocationRule) { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType("DEFAULT"); + advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule); + + List paymentAllocationOrders = getPaymentAllocationOrder( + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.PAST_DUE_PENALTY, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.PAST_DUE_FEE, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.PAST_DUE_PRINCIPAL, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.PAST_DUE_INTEREST, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.DUE_PENALTY, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.DUE_FEE, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.DUE_PRINCIPAL, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.DUE_INTEREST, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.IN_ADVANCE_PENALTY, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.IN_ADVANCE_FEE, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.IN_ADVANCE_PRINCIPAL, + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.IN_ADVANCE_INTEREST); + + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders); + return advancedPaymentData; + } + + protected static List getPaymentAllocationOrder( + org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType... paymentAllocationTypes) { + AtomicInteger integer = new AtomicInteger(1); + return Arrays.stream(paymentAllocationTypes).map(pat -> { + PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder(); + paymentAllocationOrder.setPaymentAllocationRule(pat.name()); + paymentAllocationOrder.setOrder(integer.getAndIncrement()); + return paymentAllocationOrder; + }).toList(); + } + + protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() { + AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation("NEXT_INSTALLMENT"); + return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() + .transactionProcessingStrategyCode("advanced-payment-allocation-strategy").loanScheduleType("PROGRESSIVE") + .loanScheduleProcessingType("HORIZONTAL").addPaymentAllocationItem(defaultAllocation); + } + + protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() { + return createOnePeriod30DaysPeriodicAccrualProduct(0); + } + + protected PostLoanProductsRequest createOnePeriod30DaysPeriodicAccrualProduct(double interestRatePerPeriod) { + return new PostLoanProductsRequest() + .name(org.apache.fineract.integrationtests.common.Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))// + .shortName(org.apache.fineract.integrationtests.common.Utils.uniqueRandomStringGenerator("", 4))// + .description("Loan Product Description")// + .includeInBorrowerCycle(false)// + .currencyCode("USD")// + .digitsAfterDecimal(2)// + .inMultiplesOf(0)// + .installmentAmountInMultiplesOf(1)// + .useBorrowerCycle(false)// + .minPrincipal(100.0)// + .principal(1000.0)// + .maxPrincipal(100000.0)// + .minNumberOfRepayments(1)// + .numberOfRepayments(1)// + .maxNumberOfRepayments(30)// + .isLinkedToFloatingInterestRates(false)// + .minInterestRatePerPeriod(0.0)// + .interestRatePerPeriod(interestRatePerPeriod)// + .maxInterestRatePerPeriod(100.0)// + .interestRateFrequencyType(LoanTestData.InterestRateFrequencyType.MONTHS)// + .repaymentEvery(30)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.DAYS_L)// + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)// + .interestType(LoanTestData.InterestType.DECLINING_BALANCE)// + .isEqualAmortization(false)// + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)// + .transactionProcessingStrategyCode("due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy")// + .loanScheduleType("CUMULATIVE")// + .daysInYearType(LoanTestData.DaysInYearType.ACTUAL)// + .daysInMonthType(LoanTestData.DaysInMonthType.ACTUAL)// + .canDefineInstallmentAmount(true)// + .graceOnArrearsAgeing(3)// + .overdueDaysForNPA(179)// + .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)// + .principalThresholdForLastInstallment(50)// + .allowVariableInstallments(false)// + .canUseForTopup(false)// + .isInterestRecalculationEnabled(false)// + .holdGuaranteeFunds(false)// + .multiDisburseLoan(true)// + .allowAttributeOverrides(new org.apache.fineract.client.models.AllowAttributeOverrides()// + .amortizationType(true)// + .interestType(true)// + .transactionProcessingStrategyCode(true)// + .interestCalculationPeriodType(true)// + .inArrearsTolerance(true)// + .repaymentEvery(true)// + .graceOnPrincipalAndInterestPayment(true)// + .graceOnArrearsAgeing(true))// + .allowPartialPeriodInterestCalculation(true)// + .maxTrancheCount(10)// + .outstandingLoanBalance(10000.0)// + .charges(java.util.Collections.emptyList())// + .accountingRule(1)// + .dateFormat(LoanTestData.DATETIME_PATTERN)// + .locale("en_GB")// + .disallowExpectedDisbursements(true)// + .allowApprovedDisbursedAmountsOverApplied(true)// + .overAppliedCalculationType("percentage")// + .overAppliedNumber(50); + } + + protected PostLoanProductsRequest create4Period1MonthLongWithoutInterestProduct(String repaymentStrategy) { + PostLoanProductsRequest productRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)// + .disallowExpectedDisbursements(false)// + .allowApprovedDisbursedAmountsOverApplied(false)// + .overAppliedCalculationType(null)// + .overAppliedNumber(null)// + .principal(1000.0)// + .numberOfRepayments(4)// + .repaymentEvery(1)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS.longValue())// + .transactionProcessingStrategyCode(repaymentStrategy); + if (org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + .equals(repaymentStrategy)) { + productRequest.loanScheduleType("PROGRESSIVE").loanScheduleProcessingType("HORIZONTAL") + .addPaymentAllocationItem(createDefaultPaymentAllocation("NEXT_INSTALLMENT")); + } else { + productRequest.loanScheduleType("CUMULATIVE").loanScheduleProcessingType(null).paymentAllocation(null); + } + return productRequest; + } + + protected PostLoansRequest applyLoanRequest(Long clientId, Long productId, String submittedOnDate, Double principal, + Integer numberOfRepayments) { + return LoanRequestBuilders.applyLoanRequest(clientId, productId, submittedOnDate, principal, numberOfRepayments); + } + + protected PostLoansRequest applyLoanRequest(Long clientId, Long productId, String submittedOnDate, Double principal, + Integer numberOfRepayments, Consumer customizer) { + return LoanRequestBuilders.applyLoanRequest(clientId, productId, submittedOnDate, principal, numberOfRepayments, customizer); + } + + protected PostLoansRequest applyLP2ProgressiveLoanRequest(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, + Double interestRate, Integer numberOfRepayments, java.util.function.Consumer customizer) { + return LoanRequestBuilders.applyLP2ProgressiveLoanRequest(clientId, loanProductId, loanDisbursementDate, amount, interestRate, + numberOfRepayments, customizer); + } + + protected Long applyAndApproveLoan(Long clientId, Long productId, String date, Double amount) { + return applyAndApproveLoan(clientId, productId, date, amount, 1, null); + } + + protected Long applyAndApproveLoan(Long clientId, Long productId, String date, Double amount, int numberOfRepayments) { + return applyAndApproveLoan(clientId, productId, date, amount, numberOfRepayments, null); + } + + protected Long applyAndApproveLoan(Long clientId, Long productId, String date, Double amount, int numberOfRepayments, + Consumer customizer) { + PostLoansRequest request = LoanRequestBuilders.applyLoanRequest(clientId, productId, date, amount, numberOfRepayments, customizer); + Long loanId = applyForLoan(request); + approveLoan(loanId, LoanRequestBuilders.approveLoan(amount, date)); + return loanId; + } + + protected Long applyAndApproveProgressiveLoan(Long clientId, Long productId, String date, Double amount, Double interestRate, + int numberOfRepayments, Consumer customizer) { + PostLoansRequest request = LoanRequestBuilders.applyLP2ProgressiveLoanRequest(clientId, productId, date, amount, interestRate, + numberOfRepayments, customizer); + Long loanId = applyForLoan(request); + approveLoan(loanId, LoanRequestBuilders.approveLoan(amount, date)); + return loanId; + } + + protected void disburseLoan(Long loanId, BigDecimal amount, String date) { + disburseLoan(loanId, LoanRequestBuilders.disburseLoan(amount.doubleValue(), date)); + } + + protected Long addRepaymentForLoan(Long loanId, Double amount, String date) { + return addRepayment(loanId, LoanRequestBuilders.repayLoan(amount, date)); + } + + protected void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment, + Double principalOutstanding, Double principalPaid, Double totalOverpaid) { + org.junit.jupiter.api.Assertions.assertEquals(totalOutstanding, + org.apache.fineract.integrationtests.common.Utils.getDoubleValue(loanDetails.getSummary().getTotalOutstanding())); + org.junit.jupiter.api.Assertions.assertEquals(totalRepayment, + org.apache.fineract.integrationtests.common.Utils.getDoubleValue(loanDetails.getSummary().getTotalRepayment())); + org.junit.jupiter.api.Assertions.assertEquals(principalOutstanding, + org.apache.fineract.integrationtests.common.Utils.getDoubleValue(loanDetails.getSummary().getPrincipalOutstanding())); + org.junit.jupiter.api.Assertions.assertEquals(principalPaid, + org.apache.fineract.integrationtests.common.Utils.getDoubleValue(loanDetails.getSummary().getPrincipalPaid())); + org.junit.jupiter.api.Assertions.assertEquals(totalOverpaid, + org.apache.fineract.integrationtests.common.Utils.getDoubleValue(loanDetails.getTotalOverpaid())); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignBusinessDateHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignBusinessDateHelper.java index 9e8e74edd35..579ac154ec5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignBusinessDateHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignBusinessDateHelper.java @@ -46,7 +46,7 @@ public void updateBusinessDate(String type, String date) { BusinessDateUpdateRequest request = new BusinessDateUpdateRequest()// .type(BusinessDateUpdateRequest.TypeEnum.fromValue(type))// .date(date)// - .dateFormat("yyyy-MM-dd")// + .dateFormat(LoanTestData.DATETIME_PATTERN)// .locale(LoanTestData.LOCALE); ok(() -> fineractClient.businessDateManagement().updateBusinessDate(request, Collections.emptyMap())); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java index 5e50d1f87d6..eae90b2659d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java @@ -57,7 +57,7 @@ public FeignClientHelper(FineractFeignClient fineractClient) { } public Long createClient() { - return createClient(Utils.dateFormatter.format(Utils.getLocalDateOfTenant())); + return createClient("04 March 2011"); } public Long createClient(String activationDate) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanHelper.java index 6bbaa1705f2..8ebfecea5c3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanHelper.java @@ -28,6 +28,7 @@ import org.apache.fineract.client.feign.util.CallFailedRuntimeException; import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.DeleteLoansLoanIdChargesChargeIdResponse; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdChargesChargeIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdChargesTemplateResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse; @@ -49,6 +50,8 @@ import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest; import org.apache.fineract.client.models.PostUpdateRescheduleLoansResponse; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest; import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResponse; import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdRequest; @@ -94,6 +97,14 @@ public Long createLoanProduct(PostLoanProductsRequest request) { return response.getResourceId(); } + public GetLoanProductsProductIdResponse retrieveLoanProduct(Long productId) { + return ok(() -> fineractClient.loanProducts().retrieveOneLoanProduct(productId)); + } + + public PutLoanProductsProductIdResponse updateLoanProduct(Long productId, PutLoanProductsProductIdRequest request) { + return ok(() -> fineractClient.loanProducts().updateLoanProduct(productId, request)); + } + public Long applyForLoan(PostLoansRequest request) { PostLoansResponse response = ok(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, (String) null)); return response.getLoanId(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanProductTemplates.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanProductTemplates.java index b34ab562e29..243dc2259f7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanProductTemplates.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanProductTemplates.java @@ -261,4 +261,45 @@ default PostLoanProductsRequest customizeProduct(PostLoanProductsRequest templat Function customizer) { return customizer.apply(template); } + + default PostLoanProductsRequest onePeriod30DaysPeriodicAccrual(double interestRatePerPeriod) { + return onePeriod30DaysNoInterest()// + .interestRatePerPeriod(interestRatePerPeriod); + } + + default PostLoanProductsRequest onePeriod30DaysPeriodicAccrual() { + return onePeriod30DaysPeriodicAccrual(0.0); + } + + default PostLoanProductsRequest onePeriod30DaysPeriodicAccrualWithAdvancedAllocation() { + return onePeriod30DaysPeriodicAccrual()// + .transactionProcessingStrategyCode(TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .loanScheduleType("PROGRESSIVE")// + .loanScheduleProcessingType("HORIZONTAL")// + .addPaymentAllocationItem(LoanRequestBuilders.defaultPaymentAllocation()); + } + + default PostLoanProductsRequest fourPeriod1MonthWithoutInterest(String repaymentStrategy) { + PostLoanProductsRequest request = onePeriod30DaysPeriodicAccrual()// + .multiDisburseLoan(false)// + .disallowExpectedDisbursements(false)// + .allowApprovedDisbursedAmountsOverApplied(false)// + .overAppliedCalculationType(null)// + .overAppliedNumber(null)// + .principal(1000.0)// + .numberOfRepayments(4)// + .repaymentEvery(1)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L)// + .transactionProcessingStrategyCode(repaymentStrategy); + if (TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(repaymentStrategy)) { + request.loanScheduleType("PROGRESSIVE")// + .loanScheduleProcessingType("HORIZONTAL")// + .addPaymentAllocationItem(LoanRequestBuilders.defaultPaymentAllocation()); + } else { + request.loanScheduleType("CUMULATIVE")// + .loanScheduleProcessingType(null)// + .paymentAllocation(null); + } + return request; + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanRequestBuilders.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanRequestBuilders.java index f3cc4e4a302..ae9f9bf27d4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanRequestBuilders.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanRequestBuilders.java @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.stream.Stream; import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.PaymentAllocationOrder; @@ -102,6 +103,11 @@ public static PostLoansLoanIdRequest approveLoan(Double approvedAmount, String a .dateFormat(LoanTestData.DATETIME_PATTERN); } + public static PostLoansLoanIdRequest approveLoan(Double approvedAmount, String approvedOnDate, String expectedDisbursementDate) { + return approveLoan(approvedAmount, approvedOnDate)// + .expectedDisbursementDate(expectedDisbursementDate); + } + public static PostLoansLoanIdRequest disburseLoan(Double disbursedAmount, String disbursedOnDate) { return new PostLoansLoanIdRequest()// .actualDisbursementDate(disbursedOnDate)// @@ -318,4 +324,65 @@ public static PostLoansLoanIdChargesChargeIdRequest adjustLoanCharge(double amou request.setLocale(LoanTestData.LOCALE); return request; } + + public static PostLoansRequest applyLoanRequest(Long clientId, Long productId, String submittedOnDate, Double principal, + int numberOfRepayments, Consumer customizer) { + PostLoansRequest request = new PostLoansRequest()// + .clientId(clientId)// + .productId(productId)// + .expectedDisbursementDate(submittedOnDate)// + .dateFormat(LoanTestData.DATETIME_PATTERN)// + .transactionProcessingStrategyCode("due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee-strategy")// + .locale(LoanTestData.LOCALE)// + .submittedOnDate(submittedOnDate)// + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)// + .interestRatePerPeriod(BigDecimal.ZERO)// + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)// + .interestType(LoanTestData.InterestType.DECLINING_BALANCE)// + .repaymentEvery(30)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.DAYS)// + .numberOfRepayments(numberOfRepayments)// + .loanTermFrequency(numberOfRepayments * 30)// + .loanTermFrequencyType(LoanTestData.RepaymentFrequencyType.DAYS)// + .maxOutstandingLoanBalance(BigDecimal.valueOf(principal))// + .principal(BigDecimal.valueOf(principal))// + .loanType("individual")// + .graceOnArrearsAgeing(0); + if (customizer != null) { + customizer.accept(request); + } + return request; + } + + public static PostLoansRequest applyLoanRequest(Long clientId, Long productId, String submittedOnDate, Double principal, + int numberOfRepayments) { + return applyLoanRequest(clientId, productId, submittedOnDate, principal, numberOfRepayments, null); + } + + public static PostLoansRequest applyLP2ProgressiveLoanRequest(Long clientId, Long productId, String submittedOnDate, Double principal, + Double interestRate, int numberOfRepayments, Consumer customizer) { + PostLoansRequest request = new PostLoansRequest()// + .clientId(clientId)// + .transactionProcessingStrategyCode(LoanTestData.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .productId(productId)// + .expectedDisbursementDate(submittedOnDate)// + .dateFormat(LoanTestData.DATETIME_PATTERN)// + .locale(LoanTestData.LOCALE)// + .submittedOnDate(submittedOnDate)// + .amortizationType(LoanTestData.AmortizationType.EQUAL_INSTALLMENTS)// + .interestRatePerPeriod(BigDecimal.valueOf(interestRate))// + .numberOfRepayments(numberOfRepayments)// + .principal(BigDecimal.valueOf(principal))// + .loanTermFrequency(numberOfRepayments)// + .repaymentEvery(1)// + .repaymentFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(LoanTestData.RepaymentFrequencyType.MONTHS)// + .interestType(LoanTestData.InterestType.DECLINING_BALANCE)// + .interestCalculationPeriodType(LoanTestData.InterestCalculationPeriodType.DAILY)// + .loanType("individual"); + if (customizer != null) { + customizer.accept(request); + } + return request; + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanTestValidators.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanTestValidators.java index 211661b2cc8..6d3f8642460 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanTestValidators.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/modules/LoanTestValidators.java @@ -20,22 +20,185 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.function.Function; import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdStatus; +import org.apache.fineract.client.models.GetLoansLoanIdTransactions; import org.apache.fineract.integrationtests.common.Utils; +import org.junit.jupiter.api.Assertions; public final class LoanTestValidators { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(LoanTestData.DATETIME_PATTERN, Locale.ENGLISH); + private LoanTestValidators() {} + public static void verifyTransactions(GetLoansLoanIdResponse loanDetails, LoanTestData.TransactionExt... transactions) { + if (transactions == null || transactions.length == 0) { + assertNull(loanDetails.getTransactions(), "No transaction is expected"); + return; + } + assertNotNull(loanDetails.getTransactions()); + assertEquals(transactions.length, loanDetails.getTransactions().size(), "Number of transactions"); + + Arrays.stream(transactions).forEach(tr -> { + List transactionsByDate = loanDetails.getTransactions().stream() + .filter(item -> Objects.equals(item.getDate(), LocalDate.parse(tr.date, DATE_FORMATTER))).toList(); + + if (transactionsByDate.isEmpty()) { + Assertions.fail("No transactions found for date " + tr.date); + return; + } + + boolean found = transactionsByDate.stream() + .anyMatch(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount) + && Objects.equals(item.getType().getValue(), tr.type) + && Objects.equals(Utils.getDoubleValue(item.getOutstandingLoanBalance()), tr.outstandingPrincipal) + && Objects.equals(Utils.getDoubleValue(item.getPrincipalPortion()), tr.principalPortion) + && Objects.equals(Utils.getDoubleValue(item.getInterestPortion()), tr.interestPortion) + && Objects.equals(Utils.getDoubleValue(item.getFeeChargesPortion()), tr.feePortion) + && Objects.equals(Utils.getDoubleValue(item.getPenaltyChargesPortion()), tr.penaltyPortion) + && Objects.equals(Utils.getDoubleValue(item.getOverpaymentPortion()), tr.overpaymentPortion) + && Objects.equals(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()), tr.unrecognizedPortion)); + + if (!found) { + StringBuilder errorMessage = new StringBuilder(); + errorMessage.append("Required transaction not found: ").append(tr); + errorMessage.append("\nTransactions found for date ").append(tr.date).append(":"); + for (int i = 0; i < transactionsByDate.size(); i++) { + GetLoansLoanIdTransactions item = transactionsByDate.get(i); + errorMessage.append("\n Transaction ").append(i + 1).append(": "); + errorMessage.append("amount=").append(Utils.getDoubleValue(item.getAmount())); + errorMessage.append(", type=").append(item.getType().getValue()); + errorMessage.append(", outstandingPrincipal=").append(Utils.getDoubleValue(item.getOutstandingLoanBalance())); + errorMessage.append(", principalPortion=").append(Utils.getDoubleValue(item.getPrincipalPortion())); + errorMessage.append(", interestPortion=").append(Utils.getDoubleValue(item.getInterestPortion())); + errorMessage.append(", feePortion=").append(Utils.getDoubleValue(item.getFeeChargesPortion())); + errorMessage.append(", penaltyPortion=").append(Utils.getDoubleValue(item.getPenaltyChargesPortion())); + errorMessage.append(", unrecognizedPortion=").append(Utils.getDoubleValue(item.getUnrecognizedIncomePortion())); + errorMessage.append(", overpaymentPortion=").append(Utils.getDoubleValue(item.getOverpaymentPortion())); + errorMessage.append(", reversed=").append(item.getManuallyReversed() != null ? item.getManuallyReversed() : false); + } + Assertions.fail(errorMessage.toString()); + } + }); + } + + public static void verifyRepaymentSchedule(GetLoansLoanIdResponse loanResponse, LoanTestData.Installment... installments) { + assertNotNull(loanResponse.getRepaymentSchedule()); + assertNotNull(loanResponse.getRepaymentSchedule().getPeriods()); + assertEquals(installments.length, loanResponse.getRepaymentSchedule().getPeriods().size(), + "Expected installments are not matching with the installments configured on the loan"); + + int installmentNumber = 0; + for (int i = 0; i < installments.length; i++) { + GetLoansLoanIdRepaymentPeriod period = loanResponse.getRepaymentSchedule().getPeriods().get(i); + Double principalDue = Utils.getDoubleValue(period.getPrincipalDue()); + Double amount = installments[i].principalAmount; + + if (installments[i].completed == null) { + assertEquals(amount, Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding()), + "%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount, + Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding()))); + } else { + assertEquals(amount, principalDue, + "%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount, principalDue)); + + Double interestAmount = installments[i].interestAmount; + Double interestDue = Utils.getDoubleValue(period.getInterestDue()); + if (interestAmount != null) { + assertEquals(interestAmount, interestDue, "%d. installment's interest due is different, expected: %.2f, actual: %.2f" + .formatted(i, interestAmount, interestDue)); + } + + Double feeAmount = installments[i].feeAmount; + Double feeDue = Utils.getDoubleValue(period.getFeeChargesDue()); + if (feeAmount != null) { + assertEquals(feeAmount, feeDue, + "%d. installment's fee charges due is different, expected: %.2f, actual: %.2f".formatted(i, feeAmount, feeDue)); + } + + Double penaltyAmount = installments[i].penaltyAmount; + Double penaltyDue = Utils.getDoubleValue(period.getPenaltyChargesDue()); + if (penaltyAmount != null) { + assertEquals(penaltyAmount, penaltyDue, + "%d. installment's penalty charges due is different, expected: %.2f, actual: %.2f".formatted(i, penaltyAmount, + penaltyDue)); + } + + Double outstandingAmount = installments[i].totalOutstandingAmount; + Double totalOutstanding = Utils.getDoubleValue(period.getTotalOutstandingForPeriod()); + if (outstandingAmount != null) { + assertEquals(outstandingAmount, totalOutstanding, + "%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i, outstandingAmount, + totalOutstanding)); + } + + Double outstandingPrincipalExpected = installments[i].outstandingAmounts != null + ? installments[i].outstandingAmounts.principalOutstanding + : null; + Double outstandingPrincipal = Utils.getDoubleValue(period.getPrincipalOutstanding()); + if (outstandingPrincipalExpected != null) { + assertEquals(outstandingPrincipalExpected, outstandingPrincipal, + "%d. installment's outstanding principal is different, expected: %.2f, actual: %.2f".formatted(i, + outstandingPrincipalExpected, outstandingPrincipal)); + } + + Double outstandingFeeExpected = installments[i].outstandingAmounts != null + ? installments[i].outstandingAmounts.feeOutstanding + : null; + Double outstandingFee = Utils.getDoubleValue(period.getFeeChargesOutstanding()); + if (outstandingFeeExpected != null) { + assertEquals(outstandingFeeExpected, outstandingFee, + "%d. installment's outstanding fee is different, expected: %.2f, actual: %.2f".formatted(i, + outstandingFeeExpected, outstandingFee)); + } + + Double outstandingPenaltyExpected = installments[i].outstandingAmounts != null + ? installments[i].outstandingAmounts.penaltyOutstanding + : null; + Double outstandingPenalty = Utils.getDoubleValue(period.getPenaltyChargesOutstanding()); + if (outstandingPenaltyExpected != null) { + assertEquals(outstandingPenaltyExpected, outstandingPenalty, + "%d. installment's outstanding penalty is different, expected: %.2f, actual: %.2f".formatted(i, + outstandingPenaltyExpected, outstandingPenalty)); + } + + Double outstandingTotalExpected = installments[i].outstandingAmounts != null + ? installments[i].outstandingAmounts.totalOutstanding + : null; + Double outstandingTotal = Utils.getDoubleValue(period.getTotalOutstandingForPeriod()); + if (outstandingTotalExpected != null) { + assertEquals(outstandingTotalExpected, outstandingTotal, + "%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i, + outstandingTotalExpected, outstandingTotal)); + } + + Double loanBalanceExpected = installments[i].loanBalance; + Double loanBalance = Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding()); + if (loanBalanceExpected != null) { + assertEquals(loanBalanceExpected, loanBalance, + "%d. installment's loan balance is different, expected: %.2f, actual: %.2f".formatted(i, loanBalanceExpected, + loanBalance)); + } + installmentNumber++; + assertEquals(installmentNumber, period.getPeriod()); + } + assertEquals(installments[i].completed, period.getComplete()); + assertEquals(LocalDate.parse(installments[i].dueDate, DATE_FORMATTER), period.getDueDate()); + } + } + public static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue, double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) { GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()