diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/delinquency/DelinquencyBucketResolver.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/delinquency/DelinquencyBucketResolver.java index ea238a556ce..369fb6777a7 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/delinquency/DelinquencyBucketResolver.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/delinquency/DelinquencyBucketResolver.java @@ -49,4 +49,15 @@ public long resolve(DelinquencyBucket delinquencyBucket) { return foundAtr.getId(); } + + public Long resolveBucketId(final String value) { + if (value == null || value.isEmpty()) { + return null; + } + try { + return Long.valueOf(value); + } catch (NumberFormatException ex) { + return resolve(DelinquencyBucket.valueOf(value)); + } + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 0df19c8e20d..84a96329b8c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -80,6 +80,7 @@ import org.apache.fineract.client.models.CapitalizedIncomeDetails; import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.DeleteLoansLoanIdResponse; +import org.apache.fineract.client.models.DelinquencyBucketData; import org.apache.fineract.client.models.DisbursementDetail; import org.apache.fineract.client.models.GetCodeValuesDataResponse; import org.apache.fineract.client.models.GetLoanProductsChargeOffReasonOptions; @@ -107,6 +108,8 @@ import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest; import org.apache.fineract.client.models.PostClientsResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansDisbursementData; import org.apache.fineract.client.models.PostLoansLoanIdOriginatorData; import org.apache.fineract.client.models.PostLoansLoanIdRequest; @@ -139,10 +142,12 @@ import org.apache.fineract.test.data.codevalue.CodeValue; import org.apache.fineract.test.data.codevalue.CodeValueResolver; import org.apache.fineract.test.data.codevalue.DefaultCodeValue; +import org.apache.fineract.test.data.delinquency.DelinquencyBucketResolver; import org.apache.fineract.test.data.loanproduct.DefaultLoanProduct; import org.apache.fineract.test.data.loanproduct.LoanProductResolver; import org.apache.fineract.test.data.paymenttype.DefaultPaymentType; import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver; +import org.apache.fineract.test.factory.LoanProductsRequestFactory; import org.apache.fineract.test.factory.LoanRequestFactory; import org.apache.fineract.test.helper.BusinessDateHelper; import org.apache.fineract.test.helper.CodeHelper; @@ -202,6 +207,8 @@ public class LoanStepDef extends AbstractStepDef { private final PaymentTypeResolver paymentTypeResolver; private final LoanProductResolver loanProductResolver; private final LoanRequestFactory loanRequestFactory; + private final LoanProductsRequestFactory loanProductsRequestFactory; + private final DelinquencyBucketResolver delinquencyBucketResolver; private final EventCheckHelper eventCheckHelper; private final EventStore eventStore; private final CodeValueResolver codeValueResolver; @@ -3713,6 +3720,80 @@ public void specificLoanProductChargeOffReasonOptionsCheck(final String loanProd }); } + @When("Admin creates a new Loan Product") + public void createLoanProduct() { + final PostLoanProductsRequest request = loanProductsRequestFactory.defaultLoanProductsRequestLP1(); + final PostLoanProductsResponse response = ok(() -> fineractClient.loanProducts().createLoanProduct(request)); + testContext().set(TestContextKey.LOAN_PRODUCT_CREATE_RESPONSE, response); + testContext().set(TestContextKey.LOAN_PRODUCT_CREATE_REQUEST, request); + } + + @When("Admin retrieves the Loan Product template") + public void retrieveLoanProductTemplate() { + final Long loanProductId = loanProductResolver.resolve(DefaultLoanProduct.LP1); + final GetLoanProductsProductIdResponse template = ok( + () -> fineractClient.loanProducts().retrieveOneLoanProductUniversal(loanProductId, Map.of("template", "true"))); + testContext().set(TestContextKey.LOAN_PRODUCT_TEMPLATE_RESPONSE, template); + } + + @Then("Loan Product template delinquencyBucketOptions all have bucketType {string}") + public void verifyTemplateDelinquencyBucketOptionsBucketType(final String expectedBucketType) { + final GetLoanProductsProductIdResponse template = testContext().get(TestContextKey.LOAN_PRODUCT_TEMPLATE_RESPONSE); + assertThat(template.getDelinquencyBucketOptions()).isNotNull().isNotEmpty(); + final DelinquencyBucketData.BucketTypeEnum expectedType = DelinquencyBucketData.BucketTypeEnum.fromValue(expectedBucketType); + assertThat(template.getDelinquencyBucketOptions()).allSatisfy(bucket -> assertThat(bucket.getBucketType()).isEqualTo(expectedType)); + } + + @Then("Loan Product template delinquencyBucketOptions do not contain:") + public void verifyTemplateDelinquencyBucketOptionsDoNotContain(final DataTable table) { + final GetLoanProductsProductIdResponse template = testContext().get(TestContextKey.LOAN_PRODUCT_TEMPLATE_RESPONSE); + assertThat(template.getDelinquencyBucketOptions()).isNotNull(); + final List bucketNames = template.getDelinquencyBucketOptions().stream().map(DelinquencyBucketData::getName).toList(); + assertThat(bucketNames).doesNotContainAnyElementsOf(table.asList()); + } + + @Then("Admin failed to create a new Loan Product with field {string} invalid data {string} and got an error {string}") + public void createLoanProductWithInvalidDataFailed(final String fieldName, final String value, final String errorMessage) { + final PostLoanProductsRequest request = setLoanProductCreateFieldValue(loanProductsRequestFactory.defaultLoanProductsRequestLP1(), + fieldName, value); + final CallFailedRuntimeException exception = fail(() -> fineractClient.loanProducts().createLoanProduct(request)); + assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(400); + assertThat(exception.getDeveloperMessage()).contains(errorMessage); + } + + @Then("Admin failed to update a new Loan Product field {string} with invalid data {string} and got an error {string}") + public void updateLoanProductWithInvalidDataFailed(final String fieldName, final String value, final String errorMessage) { + final PostLoanProductsResponse createResponse = testContext().get(TestContextKey.LOAN_PRODUCT_CREATE_RESPONSE); + final PutLoanProductsProductIdRequest updateRequest = setLoanProductUpdateFieldValue( + new PutLoanProductsProductIdRequest().locale(LOCALE_EN), fieldName, value); + final CallFailedRuntimeException exception = fail( + () -> fineractClient.loanProducts().updateLoanProduct(createResponse.getResourceId(), updateRequest)); + assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(400); + assertThat(exception.getDeveloperMessage()).contains(errorMessage); + } + + private PostLoanProductsRequest setLoanProductCreateFieldValue(final PostLoanProductsRequest request, final String fieldName, + String fieldValue) { + if ("null".equals(fieldValue)) { + fieldValue = null; + } + if ("delinquencyBucketId".equalsIgnoreCase(fieldName)) { + request.setDelinquencyBucketId(delinquencyBucketResolver.resolveBucketId(fieldValue)); + } + return request; + } + + private PutLoanProductsProductIdRequest setLoanProductUpdateFieldValue(final PutLoanProductsProductIdRequest request, + final String fieldName, String fieldValue) { + if ("null".equals(fieldValue)) { + fieldValue = null; + } + if ("delinquencyBucketId".equalsIgnoreCase(fieldName)) { + request.setDelinquencyBucketId(delinquencyBucketResolver.resolveBucketId(fieldValue)); + } + return request; + } + private void createCustomizedLoan(final List> loanData, final String emiStr) { final PostClientsResponse clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); final Long clientId = clientResponse.getClientId(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java index ffe8e9400cc..d3bc63c7d2a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java @@ -100,6 +100,7 @@ import org.apache.fineract.test.data.codevalue.CodeValue; import org.apache.fineract.test.data.codevalue.CodeValueResolver; import org.apache.fineract.test.data.codevalue.DefaultCodeValue; +import org.apache.fineract.test.data.delinquency.DelinquencyBucketResolver; import org.apache.fineract.test.data.paymenttype.DefaultPaymentType; import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver; import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct; @@ -153,6 +154,7 @@ public class WorkingCapitalLoanAccountStepDef extends AbstractStepDef { private final ClientRequestFactory clientRequestFactory; private final CodeValueResolver codeValueResolver; private final FineractClientConfiguration fineractClientConfiguration; + private final DelinquencyBucketResolver delinquencyBucketResolver; @Given("Admin creates a client with random data and creates-approves-disburses a working capital loan with the following data:") public void createClientAndDisburseWorkingCapitalLoanWithData(final DataTable table) { @@ -473,6 +475,31 @@ public void creatingWorkingCapitalLoanWithLpOverridablesDisabledWillResultAnErro log.info("Verified working capital loan creation failed with expected validation errors for LP overridables disabled"); } + @Then("Creating a working capital loan with the following data will result an error {string}:") + public void creatingWorkingCapitalLoanWithDataWillResultAnError(final String errorMessage, final DataTable table) { + final Map rawData = table.asMaps().getFirst(); + final Long clientId = extractClientId(); + final Long loanProductId = resolveLoanProductId(rawData.get("LoanProduct")); + + final PostWorkingCapitalLoansRequest loansRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoansRequest(clientId) + .productId(loanProductId).submittedOnDate(rawData.get("submittedOnDate")) + .expectedDisbursementDate(rawData.get("expectedDisbursementDate")) + .principalAmount(new BigDecimal(rawData.get("principalAmount"))) + .totalPaymentVolume(new BigDecimal(rawData.get("totalPaymentVolume"))) + .periodPaymentRate(new BigDecimal(rawData.get("periodPaymentRate"))) + .discount(rawData.get("discount") != null && !rawData.get("discount").isEmpty() ? new BigDecimal(rawData.get("discount")) + : null); + if (rawData.get("delinquencyBucketId") != null && !rawData.get("delinquencyBucketId").isEmpty()) { + loansRequest.delinquencyBucketId(delinquencyBucketResolver.resolveBucketId(rawData.get("delinquencyBucketId"))); + } + + final CallFailedRuntimeException exception = fail( + () -> fineractClient.workingCapitalLoans().submitWorkingCapitalLoanApplication(loansRequest)); + testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, exception); + assertHttpStatus(exception, 400); + assertValidationError(exception, errorMessage); + } + @Then("Creating a working capital loan with principal amount greater than Working Capital Loan Product max will result an error:") public void creatingAWorkingCapitalLoanWithPrincipalAmountGreaterThanWorkingCapitalLoanProductMaxWillResultAnError( final DataTable table) { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java index ba2f30a87d4..91a1d3c9bea 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java @@ -44,6 +44,7 @@ import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.DeleteWorkingCapitalLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetConfigurableAttributes; +import org.apache.fineract.client.models.GetDelinquencyBucket; import org.apache.fineract.client.models.GetPaymentAllocation; import org.apache.fineract.client.models.GetWorkingCapitalLoanDelinquencyRangeScheduleTagHistoryResponse; import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse; @@ -69,6 +70,7 @@ import org.apache.fineract.test.data.codevalue.CodeNames; import org.apache.fineract.test.data.codevalue.CodeValueResolver; import org.apache.fineract.test.data.codevalue.DefaultCodeValue; +import org.apache.fineract.test.data.delinquency.DelinquencyBucketResolver; import org.apache.fineract.test.data.paymenttype.DefaultPaymentType; import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver; import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct; @@ -95,6 +97,7 @@ public class WorkingCapitalStepDef extends AbstractStepDef { private final AccountTypeResolver accountTypeResolver; private final PaymentTypeResolver paymentTypeResolver; private final CodeValueResolver codeValueResolver; + private final DelinquencyBucketResolver delinquencyBucketResolver; private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getShared(); private static final String WC_ADVANCED_MAPPINGS_EXPECTED_CREATE = "wcAdvancedMappingsExpectedCreate"; private static final String WC_ADVANCED_MAPPINGS_EXPECTED_UPDATE = "wcAdvancedMappingsExpectedUpdate"; @@ -1622,10 +1625,12 @@ public PostWorkingCapitalLoanProductsRequest setWorkingCapitalLoanProductsCreate || fieldName.equalsIgnoreCase(MAX_PERIOD_PAYMENT_RATE_FIELD_NAME) || fieldName.equalsIgnoreCase(DISCOUNT_FIELD_NAME)) { valueBigDecimal = fieldValue != null ? new BigDecimal(fieldValue) : null; } - if (fieldName.equalsIgnoreCase(BREACH_ID_FIELD_NAME) || fieldName.equalsIgnoreCase(NEAR_BREACH_ID_FIELD_NAME) - || fieldName.equalsIgnoreCase(DELINQUENCY_BUCKET_ID_FIELD_NAME)) { + if (fieldName.equalsIgnoreCase(BREACH_ID_FIELD_NAME) || fieldName.equalsIgnoreCase(NEAR_BREACH_ID_FIELD_NAME)) { valueLong = fieldValue != null ? Long.valueOf(fieldValue) : null; } + if (fieldName.equalsIgnoreCase(DELINQUENCY_BUCKET_ID_FIELD_NAME)) { + valueLong = delinquencyBucketResolver.resolveBucketId(fieldValue); + } switch (fieldName) { case NAME_FIELD_NAME: @@ -1746,10 +1751,12 @@ public PutWorkingCapitalLoanProductsProductIdRequest setWorkingCapitalLoanProduc || fieldName.equalsIgnoreCase(MAX_PERIOD_PAYMENT_RATE_FIELD_NAME) || fieldName.equalsIgnoreCase(DISCOUNT_FIELD_NAME)) { valueBigDecimal = fieldValue != null ? new BigDecimal(fieldValue) : null; } - if (fieldName.equalsIgnoreCase(BREACH_ID_FIELD_NAME) || fieldName.equalsIgnoreCase(NEAR_BREACH_ID_FIELD_NAME) - || fieldName.equalsIgnoreCase(DELINQUENCY_BUCKET_ID_FIELD_NAME)) { + if (fieldName.equalsIgnoreCase(BREACH_ID_FIELD_NAME) || fieldName.equalsIgnoreCase(NEAR_BREACH_ID_FIELD_NAME)) { valueLong = fieldValue != null ? Long.valueOf(fieldValue) : null; } + if (fieldName.equalsIgnoreCase(DELINQUENCY_BUCKET_ID_FIELD_NAME)) { + valueLong = delinquencyBucketResolver.resolveBucketId(fieldValue); + } switch (fieldName) { case NAME_FIELD_NAME: @@ -1921,6 +1928,24 @@ public void retrieveProductTemplate() { testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_TEMPLATE_RESPONSE, template); } + @Then("Working Capital Loan Product template delinquencyBucketOptions all have bucketType {string}") + public void verifyTemplateDelinquencyBucketOptionsBucketType(final String expectedBucketType) { + final GetWorkingCapitalLoanProductsTemplateResponse template = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_TEMPLATE_RESPONSE); + assertThat(template.getDelinquencyBucketOptions()).isNotNull().isNotEmpty(); + assertThat(template.getDelinquencyBucketOptions()) + .allSatisfy(bucket -> assertThat(bucket.getBucketType()).isEqualTo(expectedBucketType)); + } + + @Then("Working Capital Loan Product template delinquencyBucketOptions do not contain:") + public void verifyTemplateDelinquencyBucketOptionsDoNotContain(final DataTable table) { + final GetWorkingCapitalLoanProductsTemplateResponse template = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_TEMPLATE_RESPONSE); + assertThat(template.getDelinquencyBucketOptions()).isNotNull(); + final List bucketNames = template.getDelinquencyBucketOptions().stream().map(GetDelinquencyBucket::getName).toList(); + assertThat(bucketNames).doesNotContainAnyElementsOf(table.asList()); + } + @When("Admin creates a new Working Capital Loan Product with Accrual with deferred revenue amortization accounting and advanced mappings") public void createWorkingCapitalLoanProductWithAdvancedMappings() { final String productName = DefaultWorkingCapitalLoanProduct.WCLP.getName() diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index cd9cf1fc993..8d486de1699 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -359,6 +359,9 @@ public abstract class TestContextKey { public static final String WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_RESPONSE = "workingCapitalLoanProductUpdateResponse"; public static final String WORKING_CAPITAL_LOAN_PRODUCT_TEMPLATE_RESPONSE = "workingCapitalLoanProductTemplateResponse"; public static final String WORKING_CAPITAL_LOAN_PRODUCT_FOR_LOAN_TEST = "workingCapitalLoanProductForLoanTest"; + public static final String LOAN_PRODUCT_CREATE_REQUEST = "loanProductCreateRequest"; + public static final String LOAN_PRODUCT_CREATE_RESPONSE = "loanProductCreateResponse"; + public static final String LOAN_PRODUCT_TEMPLATE_RESPONSE = "loanProductTemplateResponse"; public static final String DELINQUENCY_BUCKET_ID = "delinquencyBucketId"; public static final String DELINQUENCY_BUCKET_CREATE_REQUEST = "delinquencyBucketCreateRequest"; public static final String DELINQUENCY_BUCKET_UPDATE_REQUEST = "delinquencyBucketUpdateRequest"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature index 5938f70d778..d8286954334 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature @@ -261,3 +261,27 @@ Feature: LoanProduct And Admin successfully disburse the loan on "2 January 2024" with "300" EUR transaction amount And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "02 January 2024" with "200" EUR transaction amount + @TestRailId:C106739 + Scenario: Verify Loan Product template delinquencyBucketOptions are REGULAR only + When Admin retrieves the Loan Product template + Then Loan Product template delinquencyBucketOptions all have bucketType "REGULAR" + And Loan Product template delinquencyBucketOptions do not contain: + | WC_DELINQUENCY_BUCKET | + + @TestRailId:C106740 + Scenario Outline: Verify Loan Product create rejects non-REGULAR delinquency bucket + Then Admin failed to create a new Loan Product with field "" invalid data and got an error + + Examples: + | lp_field_name | lp_invalid_field_value | lp_error_message | + | delinquencyBucketId | "WC_DELINQUENCY_BUCKET" | "The parameter `delinquencyBucketId` must reference a REGULAR delinquency bucket." | + + @TestRailId:C106741 + Scenario Outline: Verify Loan Product update rejects non-REGULAR delinquency bucket + When Admin creates a new Loan Product + Then Admin failed to update a new Loan Product field "" with invalid data and got an error + + Examples: + | lp_field_name | lp_invalid_field_value | lp_error_message | + | delinquencyBucketId | "WC_DELINQUENCY_BUCKET" | "The parameter `delinquencyBucketId` must reference a REGULAR delinquency bucket." | + diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature index f4e8818a1c1..90f69967a50 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature @@ -1542,3 +1542,11 @@ Feature: WorkingCapitalLoanAccount | Type | Account code | Account name | Debit | Credit | | ASSET | 112601 | Loans Receivable | 9000.0 | | | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + + @TestRailId:C106745 + Scenario: Create Working Capital Loan account rejects non-WORKING_CAPITAL delinquency bucket + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + Then Creating a working capital loan with the following data will result an error "The parameter `delinquencyBucketId` must reference a WORKING_CAPITAL delinquency bucket.": + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | delinquencyBucketId | + | WCLP | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | BASIC_DELINQUENCY_BUCKET | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature index 49871ae1c65..674c230b617 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature @@ -497,3 +497,28 @@ Feature: WorkingCapitalLoanProduct @TestRailId:C80966 Scenario: Verify WC Loan Product create fails when payment allocation rules contain duplicates Then Admin failed to create a new Working Capital Loan Product with duplicate payment allocation rules + + @TestRailId:C106742 + Scenario: Verify WC Loan Product template delinquencyBucketOptions are WORKING_CAPITAL only + When Admin retrieves the Working Capital Loan Product template + Then Working Capital Loan Product template delinquencyBucketOptions all have bucketType "WORKING_CAPITAL" + And Working Capital Loan Product template delinquencyBucketOptions do not contain: + | BASIC_DELINQUENCY_BUCKET | + + @TestRailId:C106743 + Scenario Outline: Verify WC Loan Product create rejects non-WORKING_CAPITAL delinquency bucket + Then Admin failed to create a new Working Capital Loan Product with field "" invalid data and got an error + + Examples: + | wcp_field_name | wcp_invalid_field_value | wcp_error_message | + | delinquencyBucketId | "BASIC_DELINQUENCY_BUCKET" | "The parameter `delinquencyBucketId` must reference a WORKING_CAPITAL delinquency bucket." | + + @TestRailId:C106744 + Scenario Outline: Verify WC Loan Product update rejects non-WORKING_CAPITAL delinquency bucket + When Admin creates a new Working Capital Loan Product + Then Admin failed to update a new Working Capital Loan Product field "" with invalid data and got an error + Then Admin deletes a Working Capital Loan Product + + Examples: + | wcp_field_name | wcp_invalid_field_value | wcp_error_message | + | delinquencyBucketId | "BASIC_DELINQUENCY_BUCKET" | "The parameter `delinquencyBucketId` must reference a WORKING_CAPITAL delinquency bucket." | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketRepository.java index b28a99a1dcc..fc4e8a472ec 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketRepository.java @@ -32,4 +32,7 @@ public interface DelinquencyBucketRepository extends JpaRepository findAllBuckets(); + + @Query("SELECT b FROM DelinquencyBucket b WHERE b.bucketType = :bucketType") + List findByBucketType(@Param("bucketType") DelinquencyBucketType bucketType); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java index 61a28853aa5..4a69b516494 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformService.java @@ -25,6 +25,7 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData; import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction; import org.apache.fineract.portfolio.loanaccount.data.CollectionData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -38,6 +39,8 @@ public interface DelinquencyReadPlatformService { List retrieveAllDelinquencyBuckets(); + List retrieveDelinquencyBucketsByType(DelinquencyBucketType bucketType); + DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId); DelinquencyRangeData retrieveCurrentDelinquencyTag(Long loanId); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java index f85a1d47a44..58edd6da192 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java @@ -110,6 +110,14 @@ public List retrieveAllDelinquencyBuckets() { return result; } + @Override + public List retrieveDelinquencyBucketsByType(final DelinquencyBucketType bucketType) { + final List delinquencyBuckets = repositoryBucket.findByBucketType(bucketType); + final List result = mapperBucket.map(delinquencyBuckets); + result.forEach(this::enrichWorkingCapitalConfiguration); + return result; + } + @Override public DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId) { if (!repositoryBucket.existsById(delinquencyBucketId)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index 59e98ac85e0..7a0ae34a1a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -79,6 +79,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType; import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData; import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadService; @@ -422,7 +423,8 @@ private LoanProductData handleTemplate(final LoanProductData productData) { fundOptions = null; } - Collection delinquencyBucketOptions = this.delinquencyReadPlatformService.retrieveAllDelinquencyBuckets(); + Collection delinquencyBucketOptions = this.delinquencyReadPlatformService + .retrieveDelinquencyBucketsByType(DelinquencyBucketType.REGULAR); if (delinquencyBucketOptions.isEmpty()) { delinquencyBucketOptions = null; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 18e54b85c7f..96b02a6c3dd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -101,7 +101,7 @@ public LoanProductData retrieveLoanProduct(final Long loanProductId) { final Collection advancedPaymentData = retrieveAdvancedPaymentData(loanProductId); final Collection creditAllocationData = retrieveCreditAllocationData(loanProductId); final Collection delinquencyBucketOptions = this.delinquencyReadPlatformService - .retrieveAllDelinquencyBuckets(); + .retrieveDelinquencyBucketsByType(DelinquencyBucketType.REGULAR); final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates, delinquencyBucketOptions, advancedPaymentData, creditAllocationData); final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 3b78f94d0af..df3fe00ac96 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -30,9 +30,11 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType; @@ -45,6 +47,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType; import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate; import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; @@ -180,6 +183,12 @@ private DelinquencyBucket findDelinquencyBucketIdIfProvided(final Long delinquen if (delinquencyBucketId != null) { delinquencyBucket = delinquencyBucketRepository.findById(delinquencyBucketId) .orElseThrow(() -> DelinquencyBucketNotFoundException.notFound(delinquencyBucketId)); + if (!DelinquencyBucketType.REGULAR.equals(delinquencyBucket.getBucketType())) { + throw new PlatformApiDataValidationException( + List.of(ApiParameterError.parameterError("validation.msg.delinquencyBucketId.must.be.regular.type", + "The parameter `delinquencyBucketId` must reference a REGULAR delinquency bucket.", + LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, delinquencyBucketId))); + } } return delinquencyBucket; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalDelinquencyBucketResolver.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalDelinquencyBucketResolver.java new file mode 100644 index 00000000000..55621f549d8 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalDelinquencyBucketResolver.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloan.service; + +import java.util.Collection; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType; +import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.springframework.stereotype.Component; + +/** + * Resolves and validates delinquency buckets for Working Capital products and loans. Only + * {@link DelinquencyBucketType#WORKING_CAPITAL} buckets are allowed. + */ +@Component +@RequiredArgsConstructor +public class WorkingCapitalDelinquencyBucketResolver { + + private final DelinquencyBucketRepository delinquencyBucketRepository; + private final DelinquencyReadPlatformService delinquencyReadPlatformService; + + public Collection retrieveWorkingCapitalDelinquencyBucketOptions() { + final List workingCapitalBuckets = this.delinquencyReadPlatformService + .retrieveDelinquencyBucketsByType(DelinquencyBucketType.WORKING_CAPITAL); + return workingCapitalBuckets.isEmpty() ? null : workingCapitalBuckets; + } + + public DelinquencyBucket findWorkingCapitalBucketByIdIfProvided(final Long delinquencyBucketId) { + if (delinquencyBucketId == null) { + return null; + } + final DelinquencyBucket bucket = this.delinquencyBucketRepository.findById(delinquencyBucketId) + .orElseThrow(() -> DelinquencyBucketNotFoundException.notFound(delinquencyBucketId)); + validateIsWorkingCapitalType(bucket); + return bucket; + } + + public void validateIsWorkingCapitalType(final DelinquencyBucket bucket) { + if (bucket != null && !DelinquencyBucketType.WORKING_CAPITAL.equals(bucket.getBucketType())) { + throw new PlatformApiDataValidationException( + List.of(ApiParameterError.parameterError("validation.msg.delinquencyBucketId.must.be.working.capital.type", + "The parameter `delinquencyBucketId` must reference a WORKING_CAPITAL delinquency bucket.", + WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, bucket.getId()))); + } + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java index 09999329a45..ea1121d404e 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java @@ -21,7 +21,6 @@ import jakarta.persistence.criteria.Predicate; import java.math.MathContext; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -40,9 +39,7 @@ import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.accountdetails.data.WorkingCapitalLoanAccountSummaryData; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; -import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentType; -import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanCollectionData; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanData; @@ -80,7 +77,6 @@ public class WorkingCapitalLoanApplicationReadPlatformServiceImpl implements Wor private final WorkingCapitalLoanMapper mapper; private final WorkingCapitalLoanProductReadPlatformService productReadPlatformService; private final ClientReadPlatformService clientReadPlatformService; - private final DelinquencyReadPlatformService delinquencyReadPlatformService; private final WorkingCapitalLoanSummaryMapper workingCapitalLoanSummaryMapper; private final WorkingCapitalBreachReadPlatformService breachReadPlatformService; private final WorkingCapitalLoanDelinquencyReadPlatformService workingCapitalLoanDelinquencyReadPlatformService; @@ -98,8 +94,6 @@ public class WorkingCapitalLoanApplicationReadPlatformServiceImpl implements Wor public WorkingCapitalLoanTemplateData retrieveTemplate(final Long productId, final Long clientId) { final List productOptions = this.productReadPlatformService.retrieveAllWorkingCapitalLoanProducts(); final WorkingCapitalLoanProductData productTemplate = this.productReadPlatformService.retrieveNewWorkingCapitalLoanProductDetails(); - final Collection delinquencyBucketOptions = this.delinquencyReadPlatformService - .retrieveAllDelinquencyBuckets(); final List periodFrequencyTypeOptions = ApiFacingEnum .getValuesAsStringEnumOptionDataList(WorkingCapitalLoanPeriodFrequencyType.class); final List breachOptions = breachReadPlatformService.retrieveAll(); @@ -137,7 +131,7 @@ public WorkingCapitalLoanTemplateData retrieveTemplate(final Long productId, fin .loanData(loanData)// .productOptions(productOptions)// .fundOptions(productTemplate.getFundOptions())// - .delinquencyBucketOptions(delinquencyBucketOptions)// + .delinquencyBucketOptions(productTemplate.getDelinquencyBucketOptions())// .periodFrequencyTypeOptions(periodFrequencyTypeOptions)// .breachOptions(breachOptions)// .nearBreachOptions(nearBreachOptions)// diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAssemblerImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAssemblerImpl.java index 452b7a86289..02c3eab9c3b 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAssemblerImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAssemblerImpl.java @@ -43,7 +43,6 @@ import org.apache.fineract.portfolio.client.domain.ClientRepository; import org.apache.fineract.portfolio.client.exception.ClientNotFoundException; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; -import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; @@ -82,7 +81,7 @@ public class WorkingCapitalLoanAssemblerImpl implements WorkingCapitalLoanAssemb private final WorkingCapitalLoanProductRepository loanProductRepository; private final ClientRepository clientRepository; private final FundRepository fundRepository; - private final DelinquencyBucketRepository delinquencyBucketRepository; + private final WorkingCapitalDelinquencyBucketResolver workingCapitalDelinquencyBucketResolver; private final ExternalIdFactory externalIdFactory; private final WorkingCapitalAdvancedPaymentAllocationsJsonParser paymentAllocationParser; private final AccountNumberFormatLookup accountNumberFormatLookup; @@ -235,7 +234,7 @@ private WorkingCapitalLoanProductRelatedDetails buildLoanProductRelatedDetails(f if (fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element)) { final Long bucketId = fromApiJsonHelper.extractLongNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element); - detail.setDelinquencyBucket(bucketId != null ? delinquencyBucketRepository.findById(bucketId).orElse(null) : null); + detail.setDelinquencyBucket(workingCapitalDelinquencyBucketResolver.findWorkingCapitalBucketByIdIfProvided(bucketId)); } else { detail.setDelinquencyBucket(product.getDelinquencyBucket()); } @@ -421,7 +420,7 @@ public Map updateFrom(final JsonCommand command, final WorkingCa if (fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element)) { final Long bucketId = fromApiJsonHelper.extractLongNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element); - final DelinquencyBucket bucket = bucketId != null ? delinquencyBucketRepository.findById(bucketId).orElse(null) : null; + final DelinquencyBucket bucket = workingCapitalDelinquencyBucketResolver.findWorkingCapitalBucketByIdIfProvided(bucketId); final Long existingBucketId = detail.getDelinquencyBucket() != null ? detail.getDelinquencyBucket().getId() : null; if (!Objects.equals(bucketId, existingBucketId)) { detail.setDelinquencyBucket(bucket); diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java index 09121e1a459..72d7fcc7f78 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java @@ -35,7 +35,6 @@ import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentType; -import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; @@ -43,6 +42,7 @@ import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalDelinquencyBucketResolver; import org.apache.fineract.portfolio.workingcapitalloanbreach.data.WorkingCapitalBreachData; import org.apache.fineract.portfolio.workingcapitalloanbreach.service.WorkingCapitalBreachReadPlatformService; import org.apache.fineract.portfolio.workingcapitalloannearbreach.data.WorkingCapitalNearBreachData; @@ -69,7 +69,7 @@ public class WorkingCapitalLoanProductReadPlatformServiceImpl implements Working private final WorkingCapitalLoanProductMapper mapper; private final FundReadPlatformService fundReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService; - private final DelinquencyReadPlatformService delinquencyReadPlatformService; + private final WorkingCapitalDelinquencyBucketResolver workingCapitalDelinquencyBucketResolver; private final WorkingCapitalBreachReadPlatformService breachReadPlatformService; private final PaymentTypeReadService paymentTypeReadService; private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; @@ -129,8 +129,8 @@ public WorkingCapitalLoanProductData retrieveNewWorkingCapitalLoanProductDetails .getValuesAsStringEnumOptionDataList(DelinquencyMinimumPaymentType.class); final List advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType .getValuesAsEnumOptionDataList(); - final Collection delinquencyBucketOptions = this.delinquencyReadPlatformService - .retrieveAllDelinquencyBuckets(); + final Collection delinquencyBucketOptions = this.workingCapitalDelinquencyBucketResolver + .retrieveWorkingCapitalDelinquencyBucketOptions(); final List nearBreachOptions = nearBreachReadPlatformService.retrieveAll(); final List paymentTypeOptions = this.paymentTypeReadService.retrieveAllPaymentTypes(); @@ -153,8 +153,7 @@ public WorkingCapitalLoanProductData retrieveNewWorkingCapitalLoanProductDetails .delinquencyStartTypeOptions(delinquencyStartTypeOptions) // .breachStartTypeOptions(breachStartTypeOptions) // .delinquencyMinimumPaymentTypeOptions(delinquencyMinimumPaymentTypeOptions) // - .delinquencyBucketOptions( - delinquencyBucketOptions != null && !delinquencyBucketOptions.isEmpty() ? delinquencyBucketOptions : null) // + .delinquencyBucketOptions(delinquencyBucketOptions) // .paymentTypeOptions(paymentTypeOptions != null && !paymentTypeOptions.isEmpty() ? paymentTypeOptions : null) // // TODO: Populate WC-specific charge options when WC charges are introduced. .chargeOptions(List.of()) // diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java index 313a91d6849..75640d2045b 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java @@ -37,14 +37,13 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; -import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; -import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodFrequencyType; import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanRepository; +import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalDelinquencyBucketResolver; import org.apache.fineract.portfolio.workingcapitalloanbreach.domain.WorkingCapitalBreach; import org.apache.fineract.portfolio.workingcapitalloanbreach.repository.WorkingCapitalBreachRepository; import org.apache.fineract.portfolio.workingcapitalloannearbreach.domain.WorkingCapitalNearBreach; @@ -79,7 +78,7 @@ public class WorkingCapitalLoanProductWritePlatformServiceImpl implements Workin private final WorkingCapitalLoanRepository workingCapitalLoanRepository; private final WorkingCapitalLoanProductUpdateUtil updateUtil; private final FundRepository fundRepository; - private final DelinquencyBucketRepository delinquencyBucketRepository; + private final WorkingCapitalDelinquencyBucketResolver workingCapitalDelinquencyBucketResolver; private final WorkingCapitalAdvancedPaymentAllocationsJsonParser advancedPaymentAllocationsJsonParser; private final WorkingCapitalBreachRepository breachRepository; private final WorkingCapitalProductAccountingMappingService wcAccountingMappingService; @@ -93,7 +92,7 @@ public CommandProcessingResult createWorkingCapitalLoanProduct(final JsonCommand final Fund fund = findFundByIdIfProvided(command.parameterExists(WorkingCapitalLoanProductConstants.fundIdParamName) ? command.longValueOfParameterNamed(WorkingCapitalLoanProductConstants.fundIdParamName) : null); - final DelinquencyBucket delinquencyBucket = findDelinquencyBucketByIdIfProvided( + final DelinquencyBucket delinquencyBucket = this.workingCapitalDelinquencyBucketResolver.findWorkingCapitalBucketByIdIfProvided( command.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName) ? command.longValueOfParameterNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName) : null); @@ -278,7 +277,8 @@ private Map updateProductFields(final WorkingCapitalLoanProduct existingDelinquencyBucketId)) { final Long delinquencyBucketId = command .longValueOfParameterNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName); - final DelinquencyBucket delinquencyBucket = findDelinquencyBucketByIdIfProvided(delinquencyBucketId); + final DelinquencyBucket delinquencyBucket = this.workingCapitalDelinquencyBucketResolver + .findWorkingCapitalBucketByIdIfProvided(delinquencyBucketId); product.setDelinquencyBucket(delinquencyBucket); changes.put(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, delinquencyBucketId); } @@ -516,12 +516,4 @@ private Fund findFundByIdIfProvided(final Long fundId) { return this.fundRepository.findById(fundId).orElseThrow(() -> new FundNotFoundException(fundId)); } - private DelinquencyBucket findDelinquencyBucketByIdIfProvided(final Long delinquencyBucketId) { - if (delinquencyBucketId == null) { - return null; - } - return this.delinquencyBucketRepository.findById(delinquencyBucketId) - .orElseThrow(() -> DelinquencyBucketNotFoundException.notFound(delinquencyBucketId)); - } - } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java index d970656be65..275877ddec4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java @@ -74,7 +74,7 @@ public class WorkingCapitalLoanApplicationCRUDTest { @BeforeAll static void initDelinquency() { - delinquencyBucketId = DelinquencyBucketsHelper.createDefaultBucket(); + delinquencyBucketId = DelinquencyBucketsHelper.createDefaultWorkingCapitalBucket(); fundId = FundsResourceHandler.createFund().getResourceId(); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationValidationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationValidationTest.java index b57c2cac887..9551d13547c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationValidationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationValidationTest.java @@ -52,7 +52,7 @@ public class WorkingCapitalLoanApplicationValidationTest { @BeforeAll static void initDelinquency() { - delinquencyBucketId = DelinquencyBucketsHelper.createDefaultBucket(); + delinquencyBucketId = DelinquencyBucketsHelper.createDefaultWorkingCapitalBucket(); } @Test diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java index b77ef582299..5eac458dbee 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/products/DelinquencyBucketsHelper.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -32,6 +33,7 @@ import org.apache.fineract.client.models.DelinquencyRangeRequest; import org.apache.fineract.client.models.GetLoansLoanIdDelinquencySummary; import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.MinimumPaymentPeriodAndRule; import org.apache.fineract.client.models.PostDelinquencyBucketResponse; import org.apache.fineract.client.models.PutDelinquencyBucketResponse; import org.apache.fineract.client.util.Calls; @@ -52,18 +54,27 @@ public static DelinquencyBucketResponse getBucket(Long id) { } public static Long createDefaultBucket() { - Long range1Id = Calls + return createBucket(new DelinquencyBucketRequest().name(Utils.randomStringGenerator("DLQ_B_", 10)).ranges(createDefaultRanges())) + .getResourceId(); + } + + public static Long createDefaultWorkingCapitalBucket() { + final MinimumPaymentPeriodAndRule rule = new MinimumPaymentPeriodAndRule().frequency(30).frequencyType("DAYS") + .minimumPayment(BigDecimal.valueOf(3.0)).minimumPaymentType("PERCENTAGE"); + return createBucket(new DelinquencyBucketRequest().name(Utils.randomStringGenerator("WC_DLQ_B_", 10)).ranges(createDefaultRanges()) + .bucketType("WORKING_CAPITAL").minimumPaymentPeriodAndRule(rule)).getResourceId(); + } + + private static List createDefaultRanges() { + final Long range1Id = Calls .ok(FineractClientHelper.getFineractClient().delinquencyRangeAndBucketsManagement.createRange(new DelinquencyRangeRequest() .classification(Utils.randomStringGenerator("DLQ_R_", 10)).minimumAgeDays(1).maximumAgeDays(3).locale("en"))) .getResourceId(); - Long range2Id = Calls + final Long range2Id = Calls .ok(FineractClientHelper.getFineractClient().delinquencyRangeAndBucketsManagement.createRange(new DelinquencyRangeRequest() .classification(Utils.randomStringGenerator("DLQ_R_", 10)).minimumAgeDays(4).maximumAgeDays(60).locale("en"))) .getResourceId(); - return Calls - .ok(FineractClientHelper.getFineractClient().delinquencyRangeAndBucketsManagement.createBucket( - new DelinquencyBucketRequest().name(Utils.randomStringGenerator("DLQ_B_", 10)).ranges(List.of(range1Id, range2Id)))) - .getResourceId(); + return List.of(range1Id, range2Id); } public static PostDelinquencyBucketResponse createBucket(DelinquencyBucketRequest bucket) {