From 7e3aadb84309aa66d123fd5b74505764ee26b9a9 Mon Sep 17 00:00:00 2001 From: mariiaKraievska Date: Fri, 18 Sep 2026 12:31:18 +0300 Subject: [PATCH] FINERACT-2455: WC - delinquency bucket for regular loan shouldn't be available on WC loan product creation --- .../DelinquencyBucketResolver.java | 11 +++ .../WorkingCapitalLoanAccountStepDef.java | 27 +++++++ .../stepdef/loan/WorkingCapitalStepDef.java | 33 +++++++-- .../WorkingCapitalLoanAccount.feature | 7 ++ .../WorkingCapitalLoanProduct.feature | 22 ++++++ .../domain/DelinquencyBucketRepository.java | 3 + .../DelinquencyReadPlatformService.java | 3 + .../DelinquencyReadPlatformServiceImpl.java | 8 +++ ...rkingCapitalDelinquencyBucketResolver.java | 70 +++++++++++++++++++ ...oanApplicationReadPlatformServiceImpl.java | 8 +-- .../WorkingCapitalLoanAssemblerImpl.java | 7 +- ...talLoanProductReadPlatformServiceImpl.java | 11 ++- ...alLoanProductWritePlatformServiceImpl.java | 18 ++--- ...WorkingCapitalLoanApplicationCRUDTest.java | 2 +- ...gCapitalLoanApplicationValidationTest.java | 2 +- .../products/DelinquencyBucketsHelper.java | 23 ++++-- 16 files changed, 213 insertions(+), 42 deletions(-) create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalDelinquencyBucketResolver.java 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/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-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature index f4e8818a1c1..e1d00f035ea 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,10 @@ Feature: WorkingCapitalLoanAccount | Type | Account code | Account name | Debit | Credit | | ASSET | 112601 | Loans Receivable | 9000.0 | | | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + + 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..daf75dca900 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,25 @@ 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 + + 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 | + + 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." | + + 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-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) {