diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java index bb35365fc0b..4a37845a00e 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java @@ -21,6 +21,9 @@ public enum DefaultWorkingCapitalLoanProduct implements WorkingCapitalLoanProduct { WCLP, // + WCLP_DISCOUNT, // + WCLP_DISALLOW_ATTRIBUTES_OVERRIDE, // + WCLP_DISCOUNT_DISALLOW_ATTRIBUTES_OVERRIDE, /// WCLP_FOR_UPDATE; // @Override diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java index 8ec359833eb..5d6150df377 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java @@ -91,6 +91,18 @@ public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductReq List.of(PENALTY, FEE, PRINCIPAL))));// } + public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() { + String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10); + String shortName = loanProductsRequestFactory.generateShortNameSafely(); + + PostAllowAttributeOverrides allowAttributeOverrides = new PostAllowAttributeOverrides().delinquencyBucketClassification(true) + .discountDefault(true).periodPaymentFrequencyType(true).periodPaymentFrequency(true); + + return defaultWorkingCapitalLoanProductRequest().name(name)// + .shortName(shortName)// + .allowAttributeOverrides(allowAttributeOverrides); + } + public PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductRequestUpdate() { String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10); String shortName = loanProductsRequestFactory.generateShortNameSafely(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalProductLoanAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalProductLoanAccountStepDef.java index 79f87d844e0..7843fe66ea4 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalProductLoanAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalProductLoanAccountStepDef.java @@ -151,6 +151,7 @@ public void creatingWorkingCapitalLoanWithLpOverridablesDisabledWillResultAnErro assertValidationError(exception, "validation.msg.WORKINGCAPITALLOAN.delinquencyBucketId.override.not.allowed.by.product"); assertValidationError(exception, "validation.msg.WORKINGCAPITALLOAN.repaymentEvery.override.not.allowed.by.product"); assertValidationError(exception, "validation.msg.WORKINGCAPITALLOAN.repaymentFrequencyType.override.not.allowed.by.product"); + assertValidationError(exception, "validation.msg.WORKINGCAPITALLOAN.discount.override.not.allowed.by.product"); log.info("Verified working capital loan creation failed with expected validation errors for LP overridables disabled"); } 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 e549e918c4d..3ffab94e939 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 @@ -318,6 +318,9 @@ public abstract class TestContextKey { public static final String OFFICE_CREATE_RESPONSE = "officeCreateResponse"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC = "loanProductCreateResponseLP2DownPaymentAdvancedPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc"; public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP = "workingCapitalLoanProductCreateResponseWCLP"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISCOUNT = "workingCapitalLoanProductCreateResponseWCLPDiscount"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISALLOW_OVERRIDES = "workingCapitalLoanProductCreateResponseWCLPDisallowOverrides"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISCOUNT_DISALLOW_OVERRIDES = "workingCapitalLoanProductCreateResponseWCLPDiscountDisallowOverrides"; public static final String WC_LOAN_IDS = "wcLoanIds"; public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateRequestForUpdateWCLP"; public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateResponseForUpdateWCLP"; diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java index 931e739ad36..296166e3e32 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java @@ -20,12 +20,14 @@ import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.PostAllowAttributeOverrides; import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse; import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct; @@ -47,11 +49,46 @@ public void initialize() throws Exception { final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName(); final PostWorkingCapitalLoanProductsRequest defaultWCPLRequest = workingCapitalRequestFactory - .defaultWorkingCapitalLoanProductRequest() // + .defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() // .name(workingCapitalProductDefaultName); // final PostWorkingCapitalLoanProductsResponse responseDefaultWCPL = createWorkingCapitalLoanProductIdempotent(defaultWCPLRequest); TestContext.INSTANCE.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP, responseDefaultWCPL); + final String workingCapitalProductDiscountDefaultName = DefaultWorkingCapitalLoanProduct.WCLP_DISCOUNT.getName(); + final PostWorkingCapitalLoanProductsRequest defaultWCPLPDiscountRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() // + .name(workingCapitalProductDiscountDefaultName) // + .discount(new BigDecimal(50)); // + final PostWorkingCapitalLoanProductsResponse responseDefaultWCPLDiscount = createWorkingCapitalLoanProductIdempotent( + defaultWCPLPDiscountRequest); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISCOUNT, + responseDefaultWCPLDiscount); + + final String workingCapitalProductDisallowOverridesDefaultName = DefaultWorkingCapitalLoanProduct.WCLP_DISALLOW_ATTRIBUTES_OVERRIDE + .getName(); + final PostWorkingCapitalLoanProductsRequest defaultWCPLDisallowOverridesRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDisallowOverridesDefaultName); // + final PostWorkingCapitalLoanProductsResponse responseDefaultWCPLDisallowOverrides = createWorkingCapitalLoanProductIdempotent( + defaultWCPLDisallowOverridesRequest); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISALLOW_OVERRIDES, + responseDefaultWCPLDisallowOverrides); + + final String workingCapitalProductDiscountDisallowOverridesDefaultName = DefaultWorkingCapitalLoanProduct.WCLP_DISCOUNT_DISALLOW_ATTRIBUTES_OVERRIDE + .getName(); + PostAllowAttributeOverrides allowAttributeOverridesDisabled = new PostAllowAttributeOverrides() + .delinquencyBucketClassification(false).discountDefault(false).periodPaymentFrequencyType(false) + .periodPaymentFrequency(false); + final PostWorkingCapitalLoanProductsRequest defaultWCPLDiscountDisallowOverridesRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDiscountDisallowOverridesDefaultName) // + .discount(new BigDecimal(50)) // + .allowAttributeOverrides(allowAttributeOverridesDisabled); // + final PostWorkingCapitalLoanProductsResponse responseDefaultWCPLDiscountDisallowOverrides = createWorkingCapitalLoanProductIdempotent( + defaultWCPLDiscountDisallowOverridesRequest); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP_DISCOUNT_DISALLOW_OVERRIDES, + responseDefaultWCPLDiscountDisallowOverrides); + final String workingCapitalProductForUpdateName = DefaultWorkingCapitalLoanProduct.WCLP_FOR_UPDATE.getName(); final PostWorkingCapitalLoanProductsRequest defaultForUpdateWCPLRequest = workingCapitalRequestFactory .defaultWorkingCapitalLoanProductRequest() // diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalProductLoanAccount.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalProductLoanAccount.feature index 65bb32fa846..03eb36cebf8 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalProductLoanAccount.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalProductLoanAccount.feature @@ -1,5 +1,5 @@ -@WorkingCapitalProductFeature -Feature: WorkingCapitalProduct +@WorkingCapitalLoanAccountFeature +Feature: WorkingCapitalLoanAccount @TestRailId:C70250 Scenario: Create Working Capital Loan account - UC1: Create loan with all fields (LP overridables disabled) @@ -38,12 +38,36 @@ Feature: WorkingCapitalProduct | WCLP | 2026-01-01 | 2026-01-01 | Submitted and pending approval | 500.0 | 0.0 | 1000.0 | 2.0 | 5.0 | @TestRailId:C70253 - Scenario: Create Working Capital Loan account - UC4: With LP overridables disabled, loan creation will result an error when trying override values (Negative) + Scenario: Create Working Capital Loan account - UC4: With LP overridables disabled/disallowed, loan creation will result an error when trying override values (Negative) 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 LP overridables disabled and with the following data will result an error: - | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyBucketId | repaymentEvery | repaymentFrequencyType | - | WCLP | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | 1 | 30 | DAYS | + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyBucketId | repaymentEvery | repaymentFrequencyType | + | WCLP_DISALLOW_ATTRIBUTES_OVERRIDE | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | 1 | 30 | DAYS | + + @TestRailId:C74453 + Scenario: Create Working Capital Loan account - UC4.1: With LP overridables enabled/allowed, loan creation will override discount value + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyBucketId | repaymentEvery | repaymentFrequencyType | + | WCLP_DISCOUNT | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 2.0 | 60.0 | 1 | 1 | MONTHS | + Then Working capital loan creation was successful + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP_DISCOUNT | 2026-01-01 | 2026-01-01 | Submitted and pending approval | 100.0 | 0.0 | 100.0 | 2.0 | 60.0 | + + @TestRailId:C74479 + Scenario: Create Working Capital Loan account - UC4.2: With LP overridables disabled/disallowed, loan created with discount amount from loan product level + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyBucketId | repaymentEvery | repaymentFrequencyType | + | WCLP_DISCOUNT_DISALLOW_ATTRIBUTES_OVERRIDE | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | | 1 | 30 | DAYS | + Then Working capital loan creation was successful + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP_DISCOUNT_DISALLOW_ATTRIBUTES_OVERRIDE | 2026-01-01 | 2026-01-01 | Submitted and pending approval | 100.0 | 0.0 | 100.0 | 1.0 | 50.0 | @TestRailId:C70254 Scenario: Create Working Capital Loan account - UC5: Create with principal amount greater than WCLP max (Negative) @@ -53,7 +77,6 @@ Feature: WorkingCapitalProduct | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | | WCLP | 01 January 2026 | 01 January 2026 | 1000000.0 | 100.0 | 1.0 | 0.0 | - @TestRailId:C70255 Scenario: Create Working Capital Loan account - UC6: Create with principal amount smaller than WCLP min (Negative) When Admin sets the business date to "01 January 2026" @@ -638,7 +661,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 3 and delinquencyStartType "LOAN_CREATION" for loan test And Admin creates a working capital loan with the grace days product and the following data: | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | - | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | 0 | + | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | | Then Working capital loan creation was successful And Working capital loan account has delinquencyGraceDays 3 and delinquencyStartType "LOAN_CREATION" @@ -649,7 +672,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 3 and delinquencyStartType "LOAN_CREATION" for loan test And Admin creates a working capital loan with grace days override and the following data: | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyGraceDays | delinquencyStartType | - | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | 0 | 7 | DISBURSEMENT | + | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | | 7 | DISBURSEMENT | Then Working capital loan creation was successful And Working capital loan account has delinquencyGraceDays 7 and delinquencyStartType "DISBURSEMENT" @@ -660,7 +683,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 3 and delinquencyStartType "DISBURSEMENT" for loan test And Admin creates a working capital loan with grace days override and the following data: | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | delinquencyGraceDays | delinquencyStartType | - | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | 0 | 0 | LOAN_CREATION | + | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | | 0 | LOAN_CREATION | Then Working capital loan creation was successful And Working capital loan account has delinquencyGraceDays 0 and delinquencyStartType "LOAN_CREATION" @@ -671,7 +694,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 3 and delinquencyStartType "LOAN_CREATION" for loan test And Admin creates a working capital loan with the grace days product and the following data: | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | - | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | 0 | + | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | | Then Working capital loan creation was successful When Admin modifies the working capital loan with grace days: | delinquencyGraceDays | delinquencyStartType | @@ -685,7 +708,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 5 and delinquencyStartType "DISBURSEMENT" for loan test And Admin creates a working capital loan with the grace days product and the following data: | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | - | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | 0 | + | 01 January 2027 | 01 January 2027 | 100 | 100 | 1 | | Then Working capital loan creation was successful When Admin approves the working capital loan on "01 January 2027" Then Working capital loan account has delinquencyGraceDays 5 and delinquencyStartType "DISBURSEMENT" @@ -704,8 +727,7 @@ Feature: WorkingCapitalProduct And Admin creates a Working Capital Loan Product with delinquencyGraceDays 3 and delinquencyStartType "LOAN_CREATION" for loan test Then Creating a working capital loan with invalid delinquencyStartType "INVALID" will result with status code 400 - # TODO implement with disbursal testcases - @Skip @TestRailId:C72368 + @TestRailId:C72368 Scenario: Create Working Capital Loan account - UC13: Attempt to modify loan in DISBURSED state (Negative) When Admin sets the business date to "01 January 2026" And Admin creates a client with random data diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanApplicationDataValidator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanApplicationDataValidator.java index 9f74cd5fcf8..2db49226603 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanApplicationDataValidator.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanApplicationDataValidator.java @@ -540,9 +540,8 @@ private void validateSubmittedOnDate(final JsonElement element, final WorkingCap private void validateOverridables(final JsonElement element, final DataValidatorBuilder baseDataValidator, final WorkingCapitalLoanProductConfigurableAttributes config) { - // When overridable is false/null, reject override attempt if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element)) { - if (Boolean.TRUE.equals(config.getDelinquencyBucketClassification())) { + if (config.isDelinquencyBucketClassification()) { final Long bucketId = this.fromApiJsonHelper .extractLongNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element); baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName).value(bucketId) @@ -553,7 +552,7 @@ private void validateOverridables(final JsonElement element, final DataValidator } } if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.repaymentEveryParamName, element)) { - if (Boolean.TRUE.equals(config.getPeriodPaymentFrequency())) { + if (config.isPeriodPaymentFrequency()) { final Integer repaymentEvery = this.fromApiJsonHelper .extractIntegerWithLocaleNamed(WorkingCapitalLoanProductConstants.repaymentEveryParamName, element); baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.repaymentEveryParamName).value(repaymentEvery) @@ -564,7 +563,7 @@ private void validateOverridables(final JsonElement element, final DataValidator } } if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, element)) { - if (Boolean.TRUE.equals(config.getPeriodPaymentFrequencyType())) { + if (config.isPeriodPaymentFrequencyType()) { final String repaymentFrequencyTypeValue = this.fromApiJsonHelper .extractStringNamed(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, element); baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName) @@ -583,7 +582,12 @@ private void validateOverridables(final JsonElement element, final DataValidator } } if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.discountParamName, element)) { - if (Boolean.FALSE.equals(config.getDiscountDefault())) { + if (config.isDiscountDefault()) { + final BigDecimal discount = this.fromApiJsonHelper + .extractBigDecimalNamed(WorkingCapitalLoanProductConstants.discountParamName, element, new java.util.HashSet<>()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.discountParamName).value(discount).ignoreIfNull() + .zeroOrPositiveAmount(); + } else { baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.discountParamName) .failWithCode("override.not.allowed.by.product"); } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java index fd511b8b462..36a4a3e5e06 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java @@ -35,8 +35,8 @@ @AllArgsConstructor public class WorkingCapitalLoanProductConfigurableAttributesData implements Serializable { - private Boolean delinquencyBucketClassification; - private Boolean discountDefault; - private Boolean periodPaymentFrequency; - private Boolean periodPaymentFrequencyType; + private boolean delinquencyBucketClassification; + private boolean discountDefault; + private boolean periodPaymentFrequency; + private boolean periodPaymentFrequencyType; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java index 833da90f131..16a34900972 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java @@ -45,14 +45,14 @@ public class WorkingCapitalLoanProductConfigurableAttributes extends AbstractPer private WorkingCapitalLoanProduct wcProduct; @Column(name = "delinquency_bucket_classification_overridable") - private Boolean delinquencyBucketClassification; + private boolean delinquencyBucketClassification; @Column(name = "discount_default_overridable") - private Boolean discountDefault; + private boolean discountDefault; @Column(name = "period_payment_frequency_overridable") - private Boolean periodPaymentFrequency; + private boolean periodPaymentFrequency; @Column(name = "period_payment_frequency_type_overridable") - private Boolean periodPaymentFrequencyType; + private boolean periodPaymentFrequencyType; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java index 883123cdd3d..bcd1f08ce33 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java @@ -139,10 +139,10 @@ default WorkingCapitalLoanProductConfigurableAttributesData configurableAttribut return null; } return WorkingCapitalLoanProductConfigurableAttributesData.builder() // - .delinquencyBucketClassification(configurableAttributes.getDelinquencyBucketClassification()) // - .discountDefault(configurableAttributes.getDiscountDefault()) // - .periodPaymentFrequency(configurableAttributes.getPeriodPaymentFrequency()) // - .periodPaymentFrequencyType(configurableAttributes.getPeriodPaymentFrequencyType()) // + .delinquencyBucketClassification(configurableAttributes.isDelinquencyBucketClassification()) // + .discountDefault(configurableAttributes.isDiscountDefault()) // + .periodPaymentFrequency(configurableAttributes.isPeriodPaymentFrequency()) // + .periodPaymentFrequencyType(configurableAttributes.isPeriodPaymentFrequencyType()) // .build(); } } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java index 2b7c437717a..8a40f71a12f 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -186,13 +185,13 @@ public Map updateConfigurableAttributes(final WorkingCapitalLoan .getAsJsonObject(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName); if (allowOverrides != null && !allowOverrides.isJsonNull()) { updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName, - config::setDelinquencyBucketClassification, config::getDelinquencyBucketClassification, changes); + config::setDelinquencyBucketClassification, config::isDelinquencyBucketClassification, changes); updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName, - config::setDiscountDefault, config::getDiscountDefault, changes); + config::setDiscountDefault, config::isDiscountDefault, changes); updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName, - config::setPeriodPaymentFrequency, config::getPeriodPaymentFrequency, changes); + config::setPeriodPaymentFrequency, config::isPeriodPaymentFrequency, changes); updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName, - config::setPeriodPaymentFrequencyType, config::getPeriodPaymentFrequencyType, changes); + config::setPeriodPaymentFrequencyType, config::isPeriodPaymentFrequencyType, changes); } } return changes; @@ -200,9 +199,9 @@ public Map updateConfigurableAttributes(final WorkingCapitalLoan private static void updateBooleanField(final JsonObject allowOverrides, final String paramName, final Consumer setter, final Supplier getter, final Map changes) { - if (allowOverrides.has(paramName)) { - final Boolean newValue = allowOverrides.get(paramName).getAsBoolean(); - if (!Objects.equals(getter.get(), newValue)) { + if (allowOverrides.has(paramName) && !allowOverrides.get(paramName).isJsonNull()) { + final boolean newValue = allowOverrides.get(paramName).getAsBoolean(); + if (getter.get() != newValue) { changes.put(paramName, newValue); setter.accept(newValue); } 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 04835acc41a..c5025e9aa8f 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 @@ -341,10 +341,10 @@ private WorkingCapitalLoanProduct createProductFromCommand(final Fund fund, fina } private WorkingCapitalLoanProductConfigurableAttributes createConfigurableAttributesFromCommand(final JsonCommand command) { - Boolean delinquencyBucketClassification = null; - Boolean discountDefault = null; - Boolean periodPaymentFrequency = null; - Boolean periodPaymentFrequencyType = null; + boolean delinquencyBucketClassification = false; + boolean discountDefault = false; + boolean periodPaymentFrequency = false; + boolean periodPaymentFrequencyType = false; if (command.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)) { final JsonObject allowOverrides = command.parsedJson().getAsJsonObject() diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml index 317a91a0e6d..f6e53692e61 100644 --- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml @@ -36,4 +36,5 @@ + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0015_configurable_attributes_not_null.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0015_configurable_attributes_not_null.xml new file mode 100644 index 00000000000..8795443b3f6 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0015_configurable_attributes_not_null.xml @@ -0,0 +1,66 @@ + + + + + + + + + delinquency_bucket_classification_overridable IS NULL + + + + discount_default_overridable IS NULL + + + + period_payment_frequency_overridable IS NULL + + + + period_payment_frequency_type_overridable IS NULL + + + + + + + + + + + + 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 f37f67eeade..008c21836d4 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 @@ -407,6 +407,32 @@ public void testSubmitWithOverrideNotAllowedByProduct() { productHelper.deleteWorkingCapitalLoanProductById(productId); } + /** + * Product created without allowAttributeOverrides (all default to false). Discount override should be rejected. + */ + @Test + public void testSubmitWithDiscountOverrideWhenProductHasNoOverridesConfigured() { + final Long productId = createProductWithoutOverrides(); + final Long clientId = createClient(); + final String json = new WorkingCapitalLoanApplicationTestBuilder() // + .withClientId(clientId) // + .withProductId(productId) // + .withPrincipal(BigDecimal.valueOf(5000)) // + .withPeriodPaymentRate(BigDecimal.ONE) // + .withTotalPayment(BigDecimal.valueOf(5500)) // + .withDiscount(BigDecimal.ONE) // + .buildSubmitJson(); + + final CallFailedRuntimeException ex = applicationHelper.runSubmitExpectingFailure(json); + assertEquals(400, ex.getStatus()); + assertNotNull(ex.getDeveloperMessage()); + assertTrue(ex.getDeveloperMessage().contains("override.not.allowed.by.product"), + "Expected override.not.allowed.by.product in: " + ex.getDeveloperMessage()); + assertTrue(ex.getDeveloperMessage().contains("discount"), "Expected discount in: " + ex.getDeveloperMessage()); + + productHelper.deleteWorkingCapitalLoanProductById(productId); + } + @Test public void testSubmitWithDuplicateAccountNo() { final Long productId = createProduct(); @@ -977,6 +1003,16 @@ private Long createProductWithOverridableFalseForDiscountDefault() { .getResourceId(); } + private Long createProductWithoutOverrides() { + final String uniqueName = "WCL Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = UUID.randomUUID().toString().replace("-", "").substring(0, 4); + return productHelper.createWorkingCapitalLoanProduct(new WorkingCapitalLoanProductTestBuilder() // + .withName(uniqueName) // + .withShortName(uniqueShortName) // + .build()) // + .getResourceId(); + } + private Long createClient() { return ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java index 0159105fcbc..6fbf41fae9b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java @@ -31,6 +31,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.ZoneId; +import java.util.Map; import java.util.UUID; import org.apache.fineract.client.feign.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.common.ClientHelper; @@ -87,7 +88,7 @@ public void testApproveWorkingCapitalLoan() { @Test public void testApproveWithPrincipalAndDiscountOverride() { - final Long productId = createProduct(); + final Long productId = createProductWithDiscountOverride(); final Long clientId = createClient(); // Submit with discount = 100 @@ -148,7 +149,7 @@ public void testUndoApproval() { @Test public void testUndoApprovalResetsToCreatedState() { - final Long productId = createProduct(); + final Long productId = createProductWithDiscountOverride(); final Long clientId = createClient(); // Submit with discount = 100 @@ -337,7 +338,7 @@ public void testApproveWithoutExpectedDisbursementDateFails() { @Test public void testApproveWithDiscountExceedingCreatedValueFails() { - final Long productId = createProduct(); + final Long productId = createProductWithDiscountOverride(); final Long clientId = createClient(); // Submit with discount = 100 @@ -455,6 +456,16 @@ private Long createProduct() { .getResourceId(); } + private Long createProductWithDiscountOverride() { + final String uniqueName = "WCL Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = UUID.randomUUID().toString().replace("-", "").substring(0, 4); + return productHelper.createWorkingCapitalLoanProduct(new WorkingCapitalLoanProductTestBuilder() // + .withName(uniqueName) // + .withShortName(uniqueShortName) // + .withAllowAttributeOverrides(Map.of("discountDefault", true)) // + .build()).getResourceId(); + } + private Long createClient() { return ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); }