From a58793207c48ecc3b5b8aad4372f158fbb364871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Thu, 14 May 2026 16:01:47 +0200 Subject: [PATCH 1/2] FINERACT-2455: Working Capital Loan Charge - Product Creation & Template --- .../charge/api/ChargesApiResource.java | 9 ++- .../portfolio/charge/domain/Charge.java | 15 ++++- .../charge/domain/ChargeAppliesTo.java | 12 +++- .../charge/domain/ChargeCalculationType.java | 14 +++++ ...initionCommandFromApiJsonDeserializer.java | 16 +++++ .../ChargeDropdownReadPlatformService.java | 9 ++- ...ChargeDropdownReadPlatformServiceImpl.java | 34 ++++++++++- .../charge/service/ChargeEnumerations.java | 4 ++ .../service/ChargeReadPlatformService.java | 2 + .../charge/domain/ChargeTimeType.java | 10 ++++ .../features/charges-template-filters.adoc | 51 ++++++++++++++++ .../src/docs/en/chapters/features/index.adoc | 2 + .../features/working-capital-charges.adoc | 59 +++++++++++++++++++ .../test/data/ChargeProductAppliesTo.java | 3 +- .../test/stepdef/loan/ChargeStepDef.java | 32 ++++++++++ .../features/WorkingCapitalLoanCharge.feature | 8 +++ .../ChargeReadPlatformServiceImpl.java | 26 ++++++-- 17 files changed, 293 insertions(+), 13 deletions(-) create mode 100644 fineract-doc/src/docs/en/chapters/features/charges-template-filters.adoc create mode 100644 fineract-doc/src/docs/en/chapters/features/working-capital-charges.adoc create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java index 039eed85a63..81baa76d424 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResource.java @@ -33,6 +33,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.UriInfo; @@ -101,7 +102,8 @@ public ChargeData retrieveCharge(@PathParam("chargeId") @Parameter(description = ChargeData charge = readPlatformService.retrieveCharge(chargeId); if (settings.isTemplate()) { - final ChargeData templateData = readPlatformService.retrieveNewChargeDetails(); + final ChargeData templateData = readPlatformService.retrieveNewChargeDetails(charge.getChargeAppliesTo().getId(), + charge.getChargeTimeType().getId()); charge = ChargeData.withTemplate(charge, templateData); } return charge; @@ -119,9 +121,10 @@ public ChargeData retrieveCharge(@PathParam("chargeId") @Parameter(description = charges/template """) - public ChargeData retrieveNewChargeDetails() { + public ChargeData retrieveNewChargeDetails(@QueryParam("chargeAppliesTo") Long chargeAppliesTo, + @QueryParam("chargeTimeType") Long chargeTimeType) { context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); - return readPlatformService.retrieveNewChargeDetails(); + return readPlatformService.retrieveNewChargeDetails(chargeAppliesTo, chargeTimeType); } @POST diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java index 2f8e909a958..4a6d513d351 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java @@ -174,7 +174,9 @@ public static Charge fromJson(final JsonCommand command, final GLAccount account .fromInt(command.integerValueOfParameterNamed(CHARGE_CALCULATION_TYPE_PARAM_NAME)); final Integer chargePaymentMode = command.integerValueOfParameterNamed("chargePaymentMode"); - final ChargePaymentMode paymentMode = chargePaymentMode == null ? null : ChargePaymentMode.fromInt(chargePaymentMode); + final ChargePaymentMode paymentMode = chargePaymentMode == null + ? (chargeAppliesTo.isWorkingCapitalLoanCharge() ? ChargePaymentMode.REGULAR : null) + : ChargePaymentMode.fromInt(chargePaymentMode); final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty"); final boolean active = command.booleanPrimitiveValueOfParameterNamed("active"); @@ -423,6 +425,17 @@ public Map update(final JsonCommand command) { baseDataValidator.reset().parameter(CHARGE_TIME_PARAM_NAME).value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan"); } + } else if (ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue().equals(this.chargeAppliesTo)) { + baseDataValidator.reset().parameter(CHARGE_TIME_PARAM_NAME).value(chargeTimeType).notNull() + .isOneOfTheseValues(ChargeTimeType.validWorkingCapitalLoanValues()); + + Object[] validCalculationTypeValues = new Object[] {}; + if (ChargeTimeType.SPECIFIED_DUE_DATE.getValue().equals(chargeTimeType)) { + validCalculationTypeValues = ChargeCalculationType.validValuesForWorkingCapitalLoanSpecifiedDueDate(); + } + baseDataValidator.reset().parameter(CHARGE_CALCULATION_TYPE_PARAM_NAME).value(chargeCalculation).notNull() + .isOneOfTheseValues(validCalculationTypeValues); + } else if (isClientCharge() && !isAllowedLoanChargeTime()) { baseDataValidator.reset().parameter(CHARGE_TIME_PARAM_NAME).value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.client"); diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java index 4f0defe346e..778fa122dd8 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeAppliesTo.java @@ -24,7 +24,8 @@ public enum ChargeAppliesTo { LOAN(1, "chargeAppliesTo.loan"), // SAVINGS(2, "chargeAppliesTo.savings"), // CLIENT(3, "chargeAppliesTo.client"), // - SHARES(4, "chargeAppliesTo.shares"); // + SHARES(4, "chargeAppliesTo.shares"), // + WORKING_CAPITAL_LOAN(5, "chargeAppliesTo.workingCapitalLoan"); // private final Integer value; private final String code; @@ -59,6 +60,9 @@ public static ChargeAppliesTo fromInt(final Integer chargeAppliesTo) { case 4: chargeAppliesToType = SHARES; break; + case 5: + chargeAppliesToType = WORKING_CAPITAL_LOAN; + break; default: chargeAppliesToType = INVALID; break; @@ -80,12 +84,16 @@ public boolean isClientCharge() { return this.value.equals(ChargeAppliesTo.CLIENT.getValue()); } + public boolean isWorkingCapitalLoanCharge() { + return ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue().equals(value); + } + public boolean isSharesCharge() { return this.value.equals(SHARES.getValue()); } public static Object[] validValues() { return new Object[] { ChargeAppliesTo.LOAN.getValue(), ChargeAppliesTo.SAVINGS.getValue(), ChargeAppliesTo.CLIENT.getValue(), - ChargeAppliesTo.SHARES.getValue() }; + ChargeAppliesTo.SHARES.getValue(), ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue() }; } } diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java index 83272248146..c17a36745db 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationType.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.charge.domain; +import java.util.List; + public enum ChargeCalculationType { INVALID(0, "chargeCalculationType.invalid"), // @@ -35,6 +37,18 @@ public enum ChargeCalculationType { this.code = code; } + public static List validEnumsForWorkingCapitalLoan() { + return List.of(ChargeCalculationType.FLAT); + } + + public static List validEnumsForWorkingCapitalLoanSpecifiedDueDate() { + return List.of(ChargeCalculationType.FLAT); + } + + public static Object[] validValuesForWorkingCapitalLoanSpecifiedDueDate() { + return validEnumsForWorkingCapitalLoanSpecifiedDueDate().stream().map(ChargeCalculationType::getValue).toArray(); + } + public Integer getValue() { return this.value; } diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java index 30e05c41f6e..b95e4da8eb7 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java @@ -255,6 +255,22 @@ public void validateForCreate(final String json) { baseDataValidator.reset().parameter(CHARGE_CALCULATION_TYPE).value(chargeCalculationType) .isOneOfTheseValues(ChargeCalculationType.validValuesForShareAccountActivation()); } + } else if (appliesTo.isWorkingCapitalLoanCharge()) { + final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CHARGE_TIME_TYPE, element); + baseDataValidator.reset().parameter(CHARGE_TIME_TYPE).value(chargeTimeType).notNull(); + + if (chargeTimeType != null) { + baseDataValidator.reset().parameter(CHARGE_TIME_TYPE).value(chargeTimeType) + .isOneOfTheseValues(ChargeTimeType.validWorkingCapitalLoanValues()); + } + + if (chargeCalculationType != null) { + Object[] validValues = new Object[] {}; + if (ChargeTimeType.SPECIFIED_DUE_DATE.getValue().equals(chargeTimeType)) { + validValues = ChargeCalculationType.validValuesForWorkingCapitalLoanSpecifiedDueDate(); + } + baseDataValidator.reset().parameter(CHARGE_CALCULATION_TYPE).value(chargeCalculationType).isOneOfTheseValues(validValues); + } } final String name = this.fromApiJsonHelper.extractStringNamed(NAME, element); diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java index abd3b7400ae..37de6b96197 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformService.java @@ -20,6 +20,8 @@ import java.util.List; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.charge.domain.ChargeAppliesTo; +import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; public interface ChargeDropdownReadPlatformService { @@ -29,7 +31,7 @@ public interface ChargeDropdownReadPlatformService { List retrieveCollectionTimeTypes(); - List retrivePaymentModes(); + List retrievePaymentModes(); List retrieveLoanCalculationTypes(); @@ -47,4 +49,9 @@ public interface ChargeDropdownReadPlatformService { List retrieveSharesCollectionTimeTypes(); + List retrieveCalculationTypes(ChargeAppliesTo chargeAppliesTo, ChargeTimeType chargeTimeType); + + List retrieveCollectionTimeTypes(ChargeAppliesTo chargeAppliesTo); + + List retrievePaymentModes(ChargeAppliesTo chargeAppliesTo); } diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java index f7706093cd4..b8e9ec36521 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java @@ -68,7 +68,7 @@ public List retrieveCollectionTimeTypes() { } @Override - public List retrivePaymentModes() { + public List retrievePaymentModes() { return Arrays.asList(chargePaymentMode(ChargePaymentMode.REGULAR), chargePaymentMode(ChargePaymentMode.ACCOUNT_TRANSFER)); } @@ -124,4 +124,36 @@ public List retrieveSharesCollectionTimeTypes() { return Arrays.asList(chargeTimeType(ChargeTimeType.SHAREACCOUNT_ACTIVATION), chargeTimeType(ChargeTimeType.SHARE_PURCHASE), chargeTimeType(ChargeTimeType.SHARE_REDEEM)); } + + @Override + public List retrieveCalculationTypes(ChargeAppliesTo chargeAppliesTo, ChargeTimeType chargeTimeType) { + if (ChargeAppliesTo.WORKING_CAPITAL_LOAN.equals(chargeAppliesTo)) { + if (chargeTimeType == null) { + return ChargeCalculationType.validEnumsForWorkingCapitalLoan().stream().map(ChargeEnumerations::chargeCalculationType) + .toList(); + } + if (ChargeTimeType.SPECIFIED_DUE_DATE.equals(chargeTimeType)) { + return ChargeCalculationType.validEnumsForWorkingCapitalLoanSpecifiedDueDate().stream() + .map(ChargeEnumerations::chargeCalculationType).toList(); + } + return List.of(); + } + return retrieveCalculationTypes(); + } + + @Override + public List retrieveCollectionTimeTypes(ChargeAppliesTo chargeAppliesTo) { + if (ChargeAppliesTo.WORKING_CAPITAL_LOAN.equals(chargeAppliesTo)) { + return ChargeTimeType.validWorkingCapitalLoan().stream().map(ChargeEnumerations::chargeTimeType).toList(); + } + return retrieveCollectionTimeTypes(); + } + + @Override + public List retrievePaymentModes(ChargeAppliesTo chargeAppliesTo) { + if (ChargeAppliesTo.WORKING_CAPITAL_LOAN.equals(chargeAppliesTo)) { + return List.of(chargePaymentMode(ChargePaymentMode.REGULAR)); + } + return retrievePaymentModes(); + } } diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java index 44a8d7f5a4b..226f3d08dba 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeEnumerations.java @@ -130,6 +130,10 @@ public static EnumOptionData chargeAppliesTo(final ChargeAppliesTo type) { case SHARES: optionData = new EnumOptionData(ChargeAppliesTo.SHARES.getValue().longValue(), ChargeAppliesTo.SHARES.getCode(), "Shares"); break; + case WORKING_CAPITAL_LOAN: + optionData = new EnumOptionData(ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue().longValue(), + ChargeAppliesTo.WORKING_CAPITAL_LOAN.getCode(), "Working Capital Loan"); + break; default: optionData = new EnumOptionData(ChargeAppliesTo.INVALID.getValue().longValue(), ChargeAppliesTo.INVALID.getCode(), "Invalid"); diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java index 137c42f0f63..8aba19bdd27 100644 --- a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java +++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformService.java @@ -32,6 +32,8 @@ public interface ChargeReadPlatformService { ChargeData retrieveNewChargeDetails(); + ChargeData retrieveNewChargeDetails(Long chargeAppliesTo, Long chargeTimeType); + /** * Returns all charges that can be applied to Cients * diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java index f713062c5b1..a1e693f61db 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeType.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.charge.domain; +import java.util.List; + public enum ChargeTimeType { INVALID(0, "chargeTimeType.invalid"), // @@ -60,6 +62,14 @@ public static Object[] validLoanValues() { ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() }; } + public static List validWorkingCapitalLoan() { + return List.of(ChargeTimeType.SPECIFIED_DUE_DATE); + } + + public static Object[] validWorkingCapitalLoanValues() { + return validWorkingCapitalLoan().stream().map(ChargeTimeType::getValue).toArray(); + } + public static Object[] validLoanChargeValues() { return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.INSTALMENT_FEE.getValue() }; diff --git a/fineract-doc/src/docs/en/chapters/features/charges-template-filters.adoc b/fineract-doc/src/docs/en/chapters/features/charges-template-filters.adoc new file mode 100644 index 00000000000..67412eb1595 --- /dev/null +++ b/fineract-doc/src/docs/en/chapters/features/charges-template-filters.adoc @@ -0,0 +1,51 @@ +== Template API Filtering + +The charge template API is enhanced to support filtered responses for charge creation. + +Previously, the template endpoint returned every possible configuration option regardless of applicability. This behavior remains unchanged when no filter parameters are provided. + +With this enhancement, the API supports hierarchical filtering of template options to reduce irrelevant configuration values for Working Capital Loan charge products. + +=== Filtering Levels + +Filtering is applied in the following order: + +1. `chargeAppliesTo` + - filters and returns only applicable `chargeTimeType`s +2. `chargeTimeType` + - filters and returns only applicable `chargeCalculationType`s + +Each filtering level depends on the previous level being provided. + +=== chargeAppliesTo Filter + +A new query parameter is introduced: + +[source,http] +---- +GET /charges/template?chargeAppliesTo= +---- + +When `chargeAppliesTo` is specified, the API returns only the template fields and dropdown options applicable to the selected entity type. + +Supported values include: +* `Working Capital Loan` + +=== chargeTimeType Filter + +An additional filtering level is available when `chargeAppliesTo` is also provided: + +[source,http] +---- +GET /charges/template?chargeAppliesTo=&chargeTimeType= +---- + +Supported values for WCP Loans: + +* `Specified due date` + +The API response filters the available `chargeCalculationType` options based on the selected charge time type. + +== Backward Compatibility + +If no query parameters are provided, the template API continues to return all available options to preserve legacy behavior. \ No newline at end of file diff --git a/fineract-doc/src/docs/en/chapters/features/index.adoc b/fineract-doc/src/docs/en/chapters/features/index.adoc index bac25720bea..ef659afed5b 100644 --- a/fineract-doc/src/docs/en/chapters/features/index.adoc +++ b/fineract-doc/src/docs/en/chapters/features/index.adoc @@ -17,7 +17,9 @@ include::re-ageing.adoc[leveloffset=+1] include::delayed-schedule-captures.adoc[leveloffset=+1] include::loan-origination-details.adoc[leveloffset=+1] include::taxes-on-loan-charges.adoc[leveloffset=+1] +include::charges-template-filters.adoc[leveloffset=+1] include::working-capital-amortization-schedule.adoc[leveloffset=+1] include::working-capital-discount-fee-txn.adoc[leveloffset=+1] +include::working-capital-charges.adoc[leveloffset=+1] include::working-capital-credit-balance-refund.adoc[leveloffset=+1] include::working-capital-goodwill-credit.adoc[leveloffset=+1] diff --git a/fineract-doc/src/docs/en/chapters/features/working-capital-charges.adoc b/fineract-doc/src/docs/en/chapters/features/working-capital-charges.adoc new file mode 100644 index 00000000000..ed325d64ccd --- /dev/null +++ b/fineract-doc/src/docs/en/chapters/features/working-capital-charges.adoc @@ -0,0 +1,59 @@ += Working Capital Loan Charges + +This documentation focuses on Working Capital Loan-related charge changes. + +== Create/Update Charge + +Creates or updates a charge product applicable to Working Capital Loans. + +=== Supported Fields + +==== Mandatory Fields + +* `chargeAppliesTo` +** Must be set to `5`, as the `WorkingCapitalLoan` enum identifier is `5`. + +* `chargeTimeType` + +* `chargeCalculationType` + +* `name` +** Must be unique across all charge products. + +* `amount` + +* `active` +** Boolean value. + +* `currencyCode` +** Refer to the template API for supported currency codes. + +* `locale` +** Refer to the available platform locales. + +* `penalty` +** Boolean value. + +==== Optional Fields + +* `chargePaymentMode` +** Defaults to `regular` when not provided. + +=== Supported Values + +==== chargeAppliesTo + +* `WorkingCapitalLoan` + +[NOTE] +==== +`Loan`, `Savings`, `Client`, and `Shares` charge entities are outside the scope of this documentation. +==== + +==== chargeTimeType + +* `specified due date` + +==== chargeCalculationType + +* `flat` \ No newline at end of file diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductAppliesTo.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductAppliesTo.java index ccfb78823ef..7541a0293d6 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductAppliesTo.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductAppliesTo.java @@ -23,7 +23,8 @@ public enum ChargeProductAppliesTo { LOAN(1), // SAVINGS(2), // CLIENT(3), // - SHARES(4); // + SHARES(4), // + WORKING_CAPITAL_LOAN(5); // public final Integer value; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java index 62f0557826c..e7bc384e108 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java @@ -23,9 +23,13 @@ import io.cucumber.java.en.When; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.models.ChargeRequest; +import org.apache.fineract.client.models.PostChargesResponse; import org.apache.fineract.test.data.ChargeCalculationType; +import org.apache.fineract.test.data.ChargeProductAppliesTo; import org.apache.fineract.test.data.ChargeProductResolver; import org.apache.fineract.test.data.ChargeProductType; +import org.apache.fineract.test.data.ChargeTimeType; +import org.apache.fineract.test.helper.Utils; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.springframework.beans.factory.annotation.Autowired; @@ -60,4 +64,32 @@ public void updateChargeWithFlatAmount(String chargeType, String chargeCalculati ok(() -> fineractClient.charges().updateCharge(chargeId, disbursementChargeUpdateRequest)); } + + @When("Admin creates working capital loan charge") + public void createWorkingCapitalLoanCharge() { + ChargeRequest request = new ChargeRequest().chargeAppliesTo(ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value) // + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.value) // + .chargeCalculationType(ChargeCalculationType.FLAT.value) // + .name(Utils.randomStringGenerator("WCL_CHARGE_", 10)) // + .amount(20.0D).active(true).currencyCode("EUR").locale("en").penalty(false); + PostChargesResponse response = ok(() -> fineractClient.charges().createCharge(request)); + testContext().set("WORKING_CAPITAL_LOAN_CHARGE_ID", response.getResourceId()); + } + + @When("Admin updates working capital loan charge") + public void updateWorkingCapitalLoanCharge() { + Long id = testContext().get("WORKING_CAPITAL_LOAN_CHARGE_ID"); + ChargeRequest request = new ChargeRequest().chargeAppliesTo(ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value) // + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.value) // + .chargeCalculationType(ChargeCalculationType.FLAT.value) // + .name(Utils.randomStringGenerator("WCL_CHARGE_", 10)) // + .active(true).currencyCode("EUR").locale("en").amount(30.0D).penalty(true); + ok(() -> fineractClient.charges().updateCharge(id, request)); + } + + @When("Admin deletes working capital loan charge") + public void deleteWorkingCapitalLoanCharge() { + Long id = testContext().get("WORKING_CAPITAL_LOAN_CHARGE_ID"); + ok(() -> fineractClient.charges().deleteCharge(id)); + } } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature new file mode 100644 index 00000000000..eda509b1bdd --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature @@ -0,0 +1,8 @@ +@WorkingCapitalLoanChargesFeature +Feature: WorkingCapitalLoanChargesFeature + + @TestRailId:Cxxxx1 + Scenario: Verify that charge can be created modified and deleted + When Admin creates working capital loan charge + When Admin updates working capital loan charge + When Admin deletes working capital loan charge \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index a5ef2101f01..60eb1deb18b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -112,13 +112,33 @@ public ChargeData retrieveCharge(final Long chargeId) { } @Override - public ChargeData retrieveNewChargeDetails() { + public ChargeData retrieveNewChargeDetails(Long chargeAppliesTo, Long chargeTimeType) { + if (chargeAppliesTo != null && ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue().longValue() == chargeAppliesTo) { + final List allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes(); + final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); + final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService + .retrieveCollectionTimeTypes(ChargeAppliesTo.WORKING_CAPITAL_LOAN); + final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService + .retrieveCalculationTypes(ChargeAppliesTo.WORKING_CAPITAL_LOAN, + chargeTimeType != null ? ChargeTimeType.fromInt(chargeTimeType.intValue()) : null); + final List chargePaymentOptions = this.chargeDropdownReadPlatformService + .retrievePaymentModes(ChargeAppliesTo.WORKING_CAPITAL_LOAN); + + return ChargeData.builder().currencyOptions(currencyOptions).chargeCalculationTypeOptions(allowedChargeCalculationTypeOptions) + .chargeAppliesToOptions(allowedChargeAppliesToOptions).chargeTimeTypeOptions(allowedChargeTimeOptions) + .chargePaymetModeOptions(chargePaymentOptions).build(); + + } + return retrieveNewChargeDetails(); + } + @Override + public ChargeData retrieveNewChargeDetails() { final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); final List allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes(); final List allowedChargeAppliesToOptions = this.chargeDropdownReadPlatformService.retrieveApplicableToTypes(); final List allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes(); - final List chargePaymentOptions = this.chargeDropdownReadPlatformService.retrivePaymentModes(); + final List chargePaymentOptions = this.chargeDropdownReadPlatformService.retrievePaymentModes(); final List loansChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService .retrieveLoanCalculationTypes(); final List loansChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveLoanCollectionTimeTypes(); @@ -207,8 +227,6 @@ public List retrieveLoanAccountApplicableCharges(final Long loanId, /** * @param excludeChargeTimes * @param excludeClause - * @param params - * @return */ private void processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause) { if (excludeChargeTimes != null && excludeChargeTimes.length > 0) { From 10a4349e3a7a0705b17826557fd88eca877eedd3 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Tue, 19 May 2026 16:50:20 +0200 Subject: [PATCH 2/2] FINERACT-2455: Working Capital Loan Charge - Product Creation & Template - E2E tests --- .../WorkingCapitalChargeRequestFactory.java | 49 +++++ .../test/stepdef/loan/ChargeStepDef.java | 31 --- .../loan/WorkingCapitalChargeStepDef.java | 204 ++++++++++++++++++ .../fineract/test/support/TestContextKey.java | 2 + .../features/WorkingCapitalLoanCharge.feature | 48 ++++- 5 files changed, 300 insertions(+), 34 deletions(-) create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalChargeRequestFactory.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalChargeStepDef.java diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalChargeRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalChargeRequestFactory.java new file mode 100644 index 00000000000..a35df096859 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalChargeRequestFactory.java @@ -0,0 +1,49 @@ +/** + * 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.test.factory; + +import org.apache.fineract.client.models.ChargeRequest; +import org.apache.fineract.test.data.ChargeCalculationType; +import org.apache.fineract.test.data.ChargeProductAppliesTo; +import org.apache.fineract.test.data.ChargeTimeType; +import org.apache.fineract.test.helper.Utils; +import org.springframework.stereotype.Component; + +@Component +public class WorkingCapitalChargeRequestFactory { + + public static final String DEFAULT_CURRENCY_CODE = "EUR"; + public static final String DEFAULT_LOCALE = "en"; + public static final Double DEFAULT_AMOUNT = 20.0D; + public static final String DEFAULT_NAME_PREFIX = "WCL_CHARGE_"; + public static final int DEFAULT_NAME_RANDOM_LENGTH = 10; + + public ChargeRequest defaultWorkingCapitalChargeRequest() { + return new ChargeRequest() // + .chargeAppliesTo(ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value) // + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.value) // + .chargeCalculationType(ChargeCalculationType.FLAT.value) // + .name(Utils.randomStringGenerator(DEFAULT_NAME_PREFIX, DEFAULT_NAME_RANDOM_LENGTH)) // + .amount(DEFAULT_AMOUNT) // + .active(true) // + .currencyCode(DEFAULT_CURRENCY_CODE) // + .locale(DEFAULT_LOCALE) // + .penalty(false); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java index e7bc384e108..2ee0eed361d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/ChargeStepDef.java @@ -23,13 +23,9 @@ import io.cucumber.java.en.When; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.models.ChargeRequest; -import org.apache.fineract.client.models.PostChargesResponse; import org.apache.fineract.test.data.ChargeCalculationType; -import org.apache.fineract.test.data.ChargeProductAppliesTo; import org.apache.fineract.test.data.ChargeProductResolver; import org.apache.fineract.test.data.ChargeProductType; -import org.apache.fineract.test.data.ChargeTimeType; -import org.apache.fineract.test.helper.Utils; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.springframework.beans.factory.annotation.Autowired; @@ -65,31 +61,4 @@ public void updateChargeWithFlatAmount(String chargeType, String chargeCalculati ok(() -> fineractClient.charges().updateCharge(chargeId, disbursementChargeUpdateRequest)); } - @When("Admin creates working capital loan charge") - public void createWorkingCapitalLoanCharge() { - ChargeRequest request = new ChargeRequest().chargeAppliesTo(ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value) // - .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.value) // - .chargeCalculationType(ChargeCalculationType.FLAT.value) // - .name(Utils.randomStringGenerator("WCL_CHARGE_", 10)) // - .amount(20.0D).active(true).currencyCode("EUR").locale("en").penalty(false); - PostChargesResponse response = ok(() -> fineractClient.charges().createCharge(request)); - testContext().set("WORKING_CAPITAL_LOAN_CHARGE_ID", response.getResourceId()); - } - - @When("Admin updates working capital loan charge") - public void updateWorkingCapitalLoanCharge() { - Long id = testContext().get("WORKING_CAPITAL_LOAN_CHARGE_ID"); - ChargeRequest request = new ChargeRequest().chargeAppliesTo(ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value) // - .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.value) // - .chargeCalculationType(ChargeCalculationType.FLAT.value) // - .name(Utils.randomStringGenerator("WCL_CHARGE_", 10)) // - .active(true).currencyCode("EUR").locale("en").amount(30.0D).penalty(true); - ok(() -> fineractClient.charges().updateCharge(id, request)); - } - - @When("Admin deletes working capital loan charge") - public void deleteWorkingCapitalLoanCharge() { - Long id = testContext().get("WORKING_CAPITAL_LOAN_CHARGE_ID"); - ok(() -> fineractClient.charges().deleteCharge(id)); - } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalChargeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalChargeStepDef.java new file mode 100644 index 00000000000..b9ce2a92f8d --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalChargeStepDef.java @@ -0,0 +1,204 @@ +/** + * 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.test.stepdef.loan; + +import static org.apache.fineract.client.feign.util.FeignCalls.fail; +import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +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.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.ChargeData; +import org.apache.fineract.client.models.ChargeRequest; +import org.apache.fineract.client.models.EnumOptionData; +import org.apache.fineract.client.models.GetChargesResponse; +import org.apache.fineract.client.models.PostChargesResponse; +import org.apache.fineract.test.data.ChargeCalculationType; +import org.apache.fineract.test.data.ChargeProductAppliesTo; +import org.apache.fineract.test.data.ChargeTimeType; +import org.apache.fineract.test.factory.WorkingCapitalChargeRequestFactory; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; + +@Slf4j +@RequiredArgsConstructor +public class WorkingCapitalChargeStepDef extends AbstractStepDef { + + private static final Long REGULAR_PAYMENT_MODE_ID = 0L; + private static final Long SPECIFIED_DUE_DATE_ID = 2L; + private static final Long FLAT_CALCULATION_TYPE_ID = 1L; + + private final FineractFeignClient fineractClient; + private final WorkingCapitalChargeRequestFactory chargeRequestFactory; + + @When("Admin creates working capital loan charge") + public void createWorkingCapitalLoanCharge() { + createChargeAndStore(chargeRequestFactory.defaultWorkingCapitalChargeRequest()); + } + + @When("Admin creates working capital loan charge as penalty") + public void createWorkingCapitalLoanChargeAsPenalty() { + createChargeAndStore(chargeRequestFactory.defaultWorkingCapitalChargeRequest().penalty(true).amount(15.0D)); + } + + @When("Admin creates working capital loan charge without payment mode") + public void createWorkingCapitalLoanChargeWithoutPaymentMode() { + createChargeAndStore(chargeRequestFactory.defaultWorkingCapitalChargeRequest().amount(25.0D).chargePaymentMode(null)); + } + + @When("Admin creates working capital loan charge with {string} charge time type and {string} calculation type") + public void createWorkingCapitalLoanChargeWithParams(String chargeTimeTypeName, String chargeCalcTypeName) { + final ChargeTimeType timeType = ChargeTimeType.valueOf(chargeTimeTypeName); + final ChargeCalculationType calcType = ChargeCalculationType.valueOf(chargeCalcTypeName); + createChargeAndStore(chargeRequestFactory.defaultWorkingCapitalChargeRequest() // + .chargeTimeType(timeType.value) // + .chargeCalculationType(calcType.value)); + } + + @When("Admin updates working capital loan charge") + public void updateWorkingCapitalLoanCharge() { + final Long id = getChargeId(); + final ChargeRequest request = chargeRequestFactory.defaultWorkingCapitalChargeRequest().amount(30.0D).penalty(true); + ok(() -> fineractClient.charges().updateCharge(id, request)); + } + + @When("Admin deletes working capital loan charge") + public void deleteWorkingCapitalLoanCharge() { + ok(() -> fineractClient.charges().deleteCharge(getChargeId())); + } + + @Then("Admin retrieves working capital loan charge and verifies it is a penalty") + public void retrieveAndVerifyPenalty() { + final Long id = getChargeId(); + final GetChargesResponse chargeData = retrieveCharge(id); + assertThat(chargeData.getPenalty()).as("Charge should be a penalty").isTrue(); + assertThat(chargeData.getActive()).as("Charge should be active").isTrue(); + log.info("Verified WCL charge ID {} is a penalty", id); + } + + @Then("Admin retrieves working capital loan charge and verifies payment mode is Regular") + public void retrieveAndVerifyPaymentModeRegular() { + final Long id = getChargeId(); + final GetChargesResponse chargeData = retrieveCharge(id); + assertThat(chargeData.getChargePaymentMode()).as("Charge payment mode should not be null").isNotNull(); + assertThat(chargeData.getChargePaymentMode().getId()).as("Payment mode should be Regular (0)").isEqualTo(REGULAR_PAYMENT_MODE_ID); + log.info("Verified WCL charge ID {} has Regular payment mode", id); + } + + @Then("Admin retrieves the charge template for Working Capital Loan") + public void retrieveChargeTemplateForWcl() { + final ChargeData templateData = ok(() -> fineractClient.charges() + .retrieveTemplateCharge(Map.of("chargeAppliesTo", ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value))); + testContext().set(TestContextKey.WORKING_CAPITAL_CHARGE_TEMPLATE, templateData); + log.info("Retrieved charge template for Working Capital Loan"); + } + + @Then("Admin retrieves the charge template for Working Capital Loan with charge time type {string}") + public void retrieveChargeTemplateForWclWithTimeType(String chargeTimeTypeName) { + final ChargeTimeType timeType = ChargeTimeType.valueOf(chargeTimeTypeName); + final ChargeData templateData = ok(() -> fineractClient.charges().retrieveTemplateCharge( + Map.of("chargeAppliesTo", ChargeProductAppliesTo.WORKING_CAPITAL_LOAN.value, "chargeTimeType", timeType.value))); + testContext().set(TestContextKey.WORKING_CAPITAL_CHARGE_TEMPLATE, templateData); + log.info("Retrieved charge template for Working Capital Loan with chargeTimeType={}", chargeTimeTypeName); + } + + @Then("The charge template chargeTimeTypeOptions contains only Specified due date") + public void verifyTemplateChargeTimeTypeOptions() { + assertSingleOption(getChargeTemplate().getChargeTimeTypeOptions(), "chargeTimeTypeOptions", SPECIFIED_DUE_DATE_ID); + log.info("Verified charge template chargeTimeTypeOptions contains only Specified due date"); + } + + @Then("The charge template chargeCalculationTypeOptions contains only Flat") + public void verifyTemplateChargeCalculationTypeOptions() { + assertSingleOption(getChargeTemplate().getChargeCalculationTypeOptions(), "chargeCalculationTypeOptions", FLAT_CALCULATION_TYPE_ID); + log.info("Verified charge template chargeCalculationTypeOptions contains only Flat"); + } + + @Then("The charge template chargePaymentModeOptions contains only Regular") + public void verifyTemplateChargePaymentModeOptions() { + assertSingleOption(getChargeTemplate().getChargePaymetModeOptions(), "chargePaymentModeOptions", REGULAR_PAYMENT_MODE_ID); + log.info("Verified charge template chargePaymentModeOptions contains only Regular"); + } + + @Then("Creating working capital loan charge with {string} chargeTimeType and {string} chargeCalculationType results an error with the following data:") + public void createWclChargeWithInvalidParamsFails(String chargeTimeTypeName, String chargeCalcTypeName, DataTable table) { + final ChargeTimeType timeType = ChargeTimeType.valueOf(chargeTimeTypeName); + final ChargeCalculationType calcType = ChargeCalculationType.valueOf(chargeCalcTypeName); + final ChargeRequest request = chargeRequestFactory.defaultWorkingCapitalChargeRequest() // + .chargeTimeType(timeType.value) // + .chargeCalculationType(calcType.value); + + final Map expectedData = table.asMaps().get(0); + final int expectedHttpCode = Integer.parseInt(expectedData.get("httpCode")); + final String expectedErrorMessage = expectedData.get("errorMessage").trim(); + + final CallFailedRuntimeException exception = fail(() -> fineractClient.charges().createCharge(request)); + assertHttpStatus(exception, expectedHttpCode); + assertErrorMessage(exception, expectedErrorMessage); + log.info("Verified creating WCL charge with chargeTimeType={} and calcType={} failed with status {} and message: {}", + chargeTimeTypeName, chargeCalcTypeName, exception.getStatus(), expectedErrorMessage); + } + + // Charge API Helpers + private void createChargeAndStore(final ChargeRequest request) { + final PostChargesResponse response = ok(() -> fineractClient.charges().createCharge(request)); + testContext().set(TestContextKey.WORKING_CAPITAL_CHARGE_ID, response.getResourceId()); + log.info("Created WCL charge with ID: {}", response.getResourceId()); + } + + private GetChargesResponse retrieveCharge(final Long chargeId) { + final GetChargesResponse chargeData = ok(() -> fineractClient.charges().retrieveOneCharge(chargeId)); + assertThat(chargeData).as("Charge data should not be null").isNotNull(); + return chargeData; + } + + // Data Extraction Helpers + private Long getChargeId() { + return testContext().get(TestContextKey.WORKING_CAPITAL_CHARGE_ID); + } + + private ChargeData getChargeTemplate() { + final ChargeData templateData = testContext().get(TestContextKey.WORKING_CAPITAL_CHARGE_TEMPLATE); + assertThat(templateData).as("Charge template should not be null").isNotNull(); + return templateData; + } + + // Assertion Helpers + private void assertHttpStatus(final CallFailedRuntimeException exception, final int expectedStatus) { + assertThat(exception.getStatus()).as("HTTP status code should be " + expectedStatus).isEqualTo(expectedStatus); + } + + private void assertErrorMessage(final CallFailedRuntimeException exception, final String expectedMessage) { + assertThat(exception.getMessage()).as("Error message should contain: " + expectedMessage).contains(expectedMessage); + } + + private void assertSingleOption(final List options, final String optionName, final Long expectedId) { + assertThat(options).as(optionName + " should not be null or empty").isNotNull().isNotEmpty(); + assertThat(options).hasSize(1); + assertThat(options.get(0).getId()).as("Only " + optionName + " with ID " + expectedId + " should be available") + .isEqualTo(expectedId); + } +} 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 34238403625..9038510e9cf 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 @@ -366,5 +366,7 @@ public abstract class TestContextKey { public static final String WORKING_CAPITAL_NEAR_BREACH_CREATE_REQUEST_FOR_UPDATE = "workingCapitalNearBreachCreateRequestForUpdate"; public static final String WC_LOAN_ACTION_TEMPLATE_RESPONSE = "wcLoanActionTemplateResponse"; public static final String WORKING_CAPITAL_LOAN_RATE_CHANGE_ID = "wcLoanRateChangeId"; + public static final String WORKING_CAPITAL_CHARGE_ID = "workingCapitalChargeId"; + public static final String WORKING_CAPITAL_CHARGE_TEMPLATE = "workingCapitalChargeTemplate"; public static final String LAST_SAVINGS_ACCOUNT_ID = "lastSavingsAccountId"; } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature index eda509b1bdd..074c07e005d 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature @@ -1,8 +1,50 @@ @WorkingCapitalLoanChargesFeature Feature: WorkingCapitalLoanChargesFeature - @TestRailId:Cxxxx1 - Scenario: Verify that charge can be created modified and deleted + @TestRailId:C80954 + Scenario: Verify Working Capital Charge product - UC1: charge can be created modified and deleted When Admin creates working capital loan charge When Admin updates working capital loan charge - When Admin deletes working capital loan charge \ No newline at end of file + When Admin deletes working capital loan charge + + @TestRailId:C80955 + Scenario: Verify Working Capital Charge product - UC2: template API returns filtered options + Then Admin retrieves the charge template for Working Capital Loan + Then The charge template chargeTimeTypeOptions contains only Specified due date + Then The charge template chargeCalculationTypeOptions contains only Flat + Then The charge template chargePaymentModeOptions contains only Regular + + @TestRailId:C80956 + Scenario: Verify Working Capital Charge product - UC3: template API with Specified due date returns Flat calculation type only + Then Admin retrieves the charge template for Working Capital Loan with charge time type "SPECIFIED_DUE_DATE" + Then The charge template chargeCalculationTypeOptions contains only Flat + + @TestRailId:C80957 + Scenario: Verify Working Capital Charge product - UC4: charge can be created as penalty + When Admin creates working capital loan charge as penalty + Then Admin retrieves working capital loan charge and verifies it is a penalty + When Admin deletes working capital loan charge + + @TestRailId:C80958 + Scenario: Verify Working Capital Charge product - UC5: creating charge without payment mode will result defaults to payment mode Regular + When Admin creates working capital loan charge without payment mode + Then Admin retrieves working capital loan charge and verifies payment mode is Regular + When Admin deletes working capital loan charge + + @TestRailId:C80959 + Scenario: Verify Working Capital Charge product - UC6: invalid chargeTimeType Disbursement fails (Negative) + Then Creating working capital loan charge with "DISBURSEMENT" chargeTimeType and "FLAT" chargeCalculationType results an error with the following data: + | httpCode | errorMessage | + | 400 | The parameter `chargeTimeType` must be one of [ 2 ] . | + + @TestRailId:C80960 + Scenario: Verify Working Capital Charge product - UC7: invalid chargeCalculationType Percentage Amount fails (Negative) + Then Creating working capital loan charge with "SPECIFIED_DUE_DATE" chargeTimeType and "PERCENTAGE_AMOUNT" chargeCalculationType results an error with the following data: + | httpCode | errorMessage | + | 400 | The parameter `chargeCalculationType` must be one of [ 1 ] . | + + @TestRailId:C80961 + Scenario: Verify Working Capital Charge product - UC8: invalid chargeTimeType Instalment Fee fails (Negative) + Then Creating working capital loan charge with "INSTALLMENT_FEE" chargeTimeType and "FLAT" chargeCalculationType results an error with the following data: + | httpCode | errorMessage | + | 400 | The parameter `chargeTimeType` must be one of [ 2 ] . | \ No newline at end of file