diff --git a/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml b/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
index eed32406779..abe1b937aef 100644
--- a/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
+++ b/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.accounting.glaccount.domain.GLAccount
@@ -83,6 +85,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.tax.domain.TaxComponent
diff --git a/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml b/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
index af19d5faf24..04267b02439 100644
--- a/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
+++ b/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
@@ -48,6 +48,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.accounting.glaccount.domain.GLAccount
@@ -89,6 +91,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.tax.domain.TaxComponent
@@ -98,7 +101,7 @@
false
-
+
diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationTypeConverter.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationTypeConverter.java
new file mode 100644
index 00000000000..a942da3b3bf
--- /dev/null
+++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeCalculationTypeConverter.java
@@ -0,0 +1,37 @@
+/**
+ * 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.charge.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+@Converter
+public class ChargeCalculationTypeConverter implements AttributeConverter {
+
+ @Override
+ public Integer convertToDatabaseColumn(ChargeCalculationType attribute) {
+ return attribute == null ? null : attribute.getValue();
+ }
+
+ @Override
+ public ChargeCalculationType convertToEntityAttribute(Integer dbData) {
+ return dbData == null ? null : ChargeCalculationType.fromInt(dbData);
+ }
+}
diff --git a/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentModeConverter.java b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentModeConverter.java
new file mode 100644
index 00000000000..96bce140cf8
--- /dev/null
+++ b/fineract-charge/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargePaymentModeConverter.java
@@ -0,0 +1,37 @@
+/**
+ * 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.charge.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+@Converter
+public class ChargePaymentModeConverter implements AttributeConverter {
+
+ @Override
+ public Integer convertToDatabaseColumn(ChargePaymentMode attribute) {
+ return attribute == null ? null : attribute.getValue();
+ }
+
+ @Override
+ public ChargePaymentMode convertToEntityAttribute(Integer dbData) {
+ return dbData == null ? null : ChargePaymentMode.fromInt(dbData);
+ }
+}
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 8aba19bdd27..61110348565 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
@@ -123,4 +123,6 @@ public interface ChargeReadPlatformService {
List retrieveSharesApplicableCharges();
List retrieveShareProductCharges(Long shareProductId);
+
+ List retrieveWorkingCapitalLoanAccountApplicableCharges(Long resolvedLoanId);
}
diff --git a/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml b/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
index 3230acc9561..09eb577c071 100644
--- a/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
+++ b/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
@@ -33,6 +33,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.accounting.glaccount.domain.GLAccount
@@ -74,6 +76,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.tax.domain.TaxComponent
@@ -83,7 +86,7 @@
false
-
+
diff --git a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
index 06b1fad4cfe..8aae2f6e65b 100644
--- a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
+++ b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
@@ -157,6 +157,7 @@
import org.apache.fineract.client.feign.services.WorkingCapitalBreachApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanAccountLockApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanBreachScheduleApi;
+import org.apache.fineract.client.feign.services.WorkingCapitalLoanChargesApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanCobCatchUpApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyActionsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyRangeScheduleApi;
@@ -785,6 +786,10 @@ public WorkingCapitalLoansApi workingCapitalLoans() {
return create(WorkingCapitalLoansApi.class);
}
+ public WorkingCapitalLoanChargesApi workingCapitalLoanCharges() {
+ return create(WorkingCapitalLoanChargesApi.class);
+ }
+
public WorkingCapitalLoanTransactionsApi workingCapitalLoanTransactions() {
return create(WorkingCapitalLoanTransactionsApi.class);
}
diff --git a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
index 0925d1bfc8f..076bdf71fe2 100644
--- a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
+++ b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
@@ -75,10 +75,11 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
false
-
+
diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapperConstants.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapperConstants.java
index b99d79a58ad..68b83e219c2 100644
--- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapperConstants.java
+++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandWrapperConstants.java
@@ -186,6 +186,7 @@ private CommandWrapperConstants() {}
public static final String ENTITY_CLIENT = "CLIENT";
public static final String ENTITY_DATATABLE = "DATATABLE";
public static final String ENTITY_LOANCHARGE = "LOANCHARGE";
+ public static final String ENTITY_WORKINGCAPITALLOANCHARGE = "WORKINGCAPITALLOANCHARGE";
public static final String ENTITY_REPAYMENT_WITH_POSTDATEDCHECKS = "REPAYMENT_WITH_POSTDATEDCHECKS";
public static final String ENTITY_DISBURSEMENTDETAIL = "DISBURSEMENTDETAIL";
public static final String ENTITY_GLIMLOAN = "GLIMLOAN";
diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 8b07bfa0e88..9ba3ec37a12 100644
--- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -240,6 +240,7 @@
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_USER;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WAIVECHARGE;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOAN;
+import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOANCHARGE;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOANPRODUCT;
import static org.apache.fineract.useradministration.service.AppUserConstants.PASSWORD;
import static org.apache.fineract.useradministration.service.AppUserConstants.REPEAT_PASSWORD;
@@ -1120,6 +1121,14 @@ public CommandWrapperBuilder createLoanCharge(final Long loanId) {
return this;
}
+ public CommandWrapperBuilder createWorkingCapitalLoanCharge(final Long loanId) {
+ this.actionName = ACTION_CREATE;
+ this.entityName = ENTITY_WORKINGCAPITALLOANCHARGE;
+ this.loanId = loanId;
+ this.href = "/loans/" + loanId + "/charges";
+ return this;
+ }
+
public CommandWrapperBuilder updateLoanCharge(final Long loanId, final Long loanChargeId) {
this.actionName = ACTION_UPDATE;
this.entityName = ENTITY_LOANCHARGE;
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeTypeConverter.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeTypeConverter.java
new file mode 100644
index 00000000000..b5a4a651028
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/charge/domain/ChargeTimeTypeConverter.java
@@ -0,0 +1,37 @@
+/**
+ * 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.charge.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+@Converter
+public class ChargeTimeTypeConverter implements AttributeConverter {
+
+ @Override
+ public Integer convertToDatabaseColumn(ChargeTimeType attribute) {
+ return attribute == null ? null : attribute.getValue();
+ }
+
+ @Override
+ public ChargeTimeType convertToEntityAttribute(Integer dbData) {
+ return dbData == null ? null : ChargeTimeType.fromInt(dbData);
+ }
+}
diff --git a/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml b/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
index 9d833abefd1..36c45420d7e 100644
--- a/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
+++ b/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
@@ -71,6 +71,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
false
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
index b9ce2a92f8d..b5806db39cc 100644
--- 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
@@ -25,6 +25,7 @@
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -35,13 +36,19 @@
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.GetLoansLoanIdChargesChargeIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdChargesTemplateResponse;
import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
+import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
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;
+import org.junit.jupiter.api.Assertions;
@Slf4j
@RequiredArgsConstructor
@@ -108,6 +115,55 @@ public void retrieveAndVerifyPaymentModeRegular() {
log.info("Verified WCL charge ID {} has Regular payment mode", id);
}
+ @Then("Admin retrieves working capital loan charge template by loan id")
+ public void getWorkingCapitalLoanChargesTemplateByLoanId() {
+ Long loanId = getLoanId();
+ Long chargeId = getChargeId();
+ Assertions.assertNotNull(chargeId);
+
+ GetLoansLoanIdChargesTemplateResponse response = ok(
+ () -> fineractClient.workingCapitalLoanCharges().retrieveTemplateWorkingCapitalLoanCharge(loanId));
+
+ Assertions.assertNotNull(response.getChargeOptions());
+
+ boolean anyMatch = response.getChargeOptions().stream().anyMatch(cO -> chargeId.equals(cO.getId()));
+ Assertions.assertTrue(anyMatch);
+
+ }
+
+ @Then("Admin add working capital loan charge by loan id and charge id with amount {double} and due date {string}")
+ public void admin_add_working_capital_loan_charge_by_loan_id_and_charge_id_with_amount(Double amount, String dueDate) {
+ Long loanId = getLoanId();
+ Assertions.assertNotNull(loanId);
+ Long chargeId = getChargeId();
+ Assertions.assertNotNull(chargeId);
+
+ PostLoansLoanIdChargesRequest request = new PostLoansLoanIdChargesRequest() //
+ .chargeId(chargeId).amount(amount).dueDate(dueDate).dateFormat("dd-MM-yyyy").locale("en");
+ PostLoansLoanIdChargesResponse response = ok(() -> fineractClient.workingCapitalLoanCharges().createLoanCharge(loanId, request));
+ Assertions.assertNotNull(response);
+ Assertions.assertNotNull(response.getResourceId());
+
+ addLoanChargeId(response.getResourceId());
+
+ }
+
+ @Then("Working Capital Loan has the created charges")
+ public void verifyWorkingCapitalLoanChargesAreCreated() {
+ Long loanId = getLoanId();
+ Assertions.assertNotNull(loanId);
+ List responses = ok(
+ () -> fineractClient.workingCapitalLoanCharges().retrieveAllWorkingCapitalLoanChargesByLoanId(loanId));
+ Assertions.assertNotNull(responses);
+ List loanChargeIds = getLoanChargeIds();
+ for (GetLoansLoanIdChargesChargeIdResponse response : responses) {
+ Assertions.assertTrue(loanChargeIds.contains(response.getId()));
+ }
+ for (Long chargeId : loanChargeIds) {
+ ok(() -> fineractClient.workingCapitalLoanCharges().retrieveWorkingCapitalLoanCharge(loanId, chargeId));
+ }
+ }
+
@Then("Admin retrieves the charge template for Working Capital Loan")
public void retrieveChargeTemplateForWcl() {
final ChargeData templateData = ok(() -> fineractClient.charges()
@@ -180,12 +236,34 @@ private Long getChargeId() {
return testContext().get(TestContextKey.WORKING_CAPITAL_CHARGE_ID);
}
+ private void addLoanChargeId(Long loanChargeId) {
+ List loanChargeIds = getLoanChargeIds();
+ if (!loanChargeIds.contains(loanChargeId)) {
+ loanChargeIds.add(loanChargeId);
+ }
+ }
+
+ private List getLoanChargeIds() {
+ List ids = testContext().get(TestContextKey.WORKING_CAPITAL_LOAN_CHARGE_IDS);
+ if (ids == null) {
+ ids = new ArrayList<>();
+ testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_CHARGE_IDS, ids);
+ }
+ return ids;
+ }
+
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;
}
+ private Long getLoanId() {
+ PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ Assertions.assertNotNull(loanResponse);
+ return loanResponse.getLoanId();
+ }
+
// Assertion Helpers
private void assertHttpStatus(final CallFailedRuntimeException exception, final int expectedStatus) {
assertThat(exception.getStatus()).as("HTTP status code should be " + expectedStatus).isEqualTo(expectedStatus);
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 9038510e9cf..8da8a0f9210 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
@@ -367,6 +367,7 @@ public abstract class TestContextKey {
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_LOAN_CHARGE_IDS = "workingCapitalLoanChargeIds";
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 074c07e005d..d50b1970ece 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanCharge.feature
@@ -47,4 +47,31 @@ Feature: WorkingCapitalLoanChargesFeature
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
+ | 400 | The parameter `chargeTimeType` must be one of [ 2 ] . |
+
+ @TestRailId:TODO__1
+ Scenario: Verify Working Capital Charge product can be added to disbursed loan
+ 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 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 |
+ | WCLP | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 |
+ And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026"
+ Then Working capital loan approval was successful
+ And Working capital loan account has the correct data:
+ | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discountApproved |
+ | WCLP | 2026-01-01 | 2026-01-01 | Approved | 9000.0 | 9000.0 | 100000.0 | 18.0 | null |
+ When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount
+ Then Working Capital loan status will be "ACTIVE"
+ And Verify Working Capital loan disbursement was successful
+ And Working capital loan account has the correct data:
+ | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount |
+ | WCLP | 2026-01-01 | 2026-01-01 | Active | 9000.0 | 9000.0 | 100000.0 | 18.0 | null |
+ When Admin sets the business date to "10 January 2026"
+ And Admin runs inline COB job for Working Capital Loan by loanId
+ Then Admin retrieves working capital loan charge template by loan id
+ Then Admin add working capital loan charge by loan id and charge id with amount 35.0 and due date "12-01-2026"
+ Then Admin add working capital loan charge by loan id and charge id with amount 45.0 and due date "21-01-2026"
+ Then Working Capital Loan has the created charges
\ No newline at end of file
diff --git a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
index 8dddaaafc7e..1d99c5fda0f 100644
--- a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
+++ b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -87,6 +89,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.investor.domain.ExternalAssetOwner
@@ -166,7 +169,7 @@
false
-
+
diff --git a/fineract-loan-origination/src/main/resources/jpa/static-weaving/module/fineract-loan-origination/persistence.xml b/fineract-loan-origination/src/main/resources/jpa/static-weaving/module/fineract-loan-origination/persistence.xml
index 791a3a8bd9a..3a9fcb1241e 100644
--- a/fineract-loan-origination/src/main/resources/jpa/static-weaving/module/fineract-loan-origination/persistence.xml
+++ b/fineract-loan-origination/src/main/resources/jpa/static-weaving/module/fineract-loan-origination/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -87,6 +89,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.collateral.domain.LoanCollateral
@@ -160,7 +163,7 @@
false
-
+
diff --git a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
index c77a4687991..0a33b4a1571 100644
--- a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
+++ b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -87,6 +89,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.collateral.domain.LoanCollateral
@@ -157,7 +160,7 @@
false
-
+
diff --git a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
index 3c07e451e41..83a38ad775a 100644
--- a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
+++ b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -87,6 +89,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.collateral.domain.LoanCollateral
@@ -161,7 +164,7 @@
false
-
+
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 60eb1deb18b..d348437bb49 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
@@ -450,6 +450,19 @@ public List retrieveShareProductCharges(final Long shareProductId) {
return this.jdbcTemplate.query(sql, rm, new Object[] { shareProductId }); // NOSONAR
}
+ @Override
+ public List retrieveWorkingCapitalLoanAccountApplicableCharges(Long loanId) {
+ final ChargeMapper rm = new ChargeMapper();
+ Map paramMap = new HashMap<>();
+ paramMap.put("loanId", loanId);
+ paramMap.put("chargeAppliesTo", ChargeAppliesTo.WORKING_CAPITAL_LOAN.getValue());
+ String sql = "select " + rm.chargeSchema() + " join m_wc_loan la on la.currency_code = c.currency_code" + " where la.id=:loanId"
+ + " and c.is_deleted=false and c.is_active=true and c.charge_applies_to_enum=:chargeAppliesTo ";
+ sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
+ sql += " order by c.name ";
+ return this.namedParameterJdbcTemplate.query(sql, paramMap, rm);
+ }
+
@Override
public List retrieveSavingsAccountApplicableCharges(Long savingsAccountId) {
diff --git a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
index 6513bac95d3..b12436ac6f3 100644
--- a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
+++ b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
@@ -48,6 +48,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -93,6 +95,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.investor.domain.ExternalAssetOwner
@@ -206,7 +209,8 @@
org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail
org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistory
org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail
- org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntryAggregationTracking
+ org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntryAggregationTracking
+
org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntrySummary
org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob
org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobConfiguration
@@ -304,16 +308,23 @@
org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDelinquencyAction
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanBreachSchedule
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDelinquencyRangeSchedule
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDelinquencyRangeScheduleTagHistory
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanBalance
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDisbursementDetails
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanNote
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPaymentAllocationRule
org.apache.fineract.portfolio.workingcapitalloanbreach.domain.WorkingCapitalBreach
+ org.apache.fineract.portfolio.workingcapitalloan.domain.ProjectedAmortizationLoanModel
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionRelation
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionAllocation
- org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionRelation
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanCharge
org.apache.fineract.portfolio.workingcapitalloannearbreach.domain.WorkingCapitalNearBreach
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodPaymentRateChange
org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct
org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductConfigurableAttributes
org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductPaymentAllocationRule
@@ -322,7 +333,7 @@
false
-
+
diff --git a/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml b/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
index 204b74b15e7..9c7259e1b6f 100644
--- a/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
+++ b/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
@@ -71,6 +71,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.floatingrates.domain.FloatingRate
diff --git a/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml b/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
index 9d833abefd1..36c45420d7e 100644
--- a/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
+++ b/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
@@ -71,6 +71,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
false
diff --git a/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml b/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
index 49991221e2f..a85b20ec083 100644
--- a/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
+++ b/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
@@ -42,6 +42,8 @@
org.apache.fineract.portfolio.charge.domain.Charge
+ org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter
+ org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter
org.apache.fineract.cob.domain.AccountLock
@@ -87,6 +89,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.floatingrates.domain.FloatingRate
@@ -125,7 +128,7 @@
false
-
+
diff --git a/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml b/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
index 655466dbcb3..5036432741c 100644
--- a/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
+++ b/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
@@ -71,6 +71,7 @@
org.apache.fineract.useradministration.domain.AppUser
org.apache.fineract.useradministration.domain.Permission
org.apache.fineract.useradministration.domain.Role
+ org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter
org.apache.fineract.portfolio.tax.domain.TaxComponent
@@ -80,7 +81,7 @@
false
-
+
diff --git a/fineract-working-capital-loan/dependencies.gradle b/fineract-working-capital-loan/dependencies.gradle
index d9154bef9d4..9494d9399a4 100644
--- a/fineract-working-capital-loan/dependencies.gradle
+++ b/fineract-working-capital-loan/dependencies.gradle
@@ -19,6 +19,7 @@
dependencies {
implementation(project(path: ':fineract-core'))
+ implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-loan'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-cob'))
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResource.java
new file mode 100644
index 00000000000..1870e8f0b63
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResource.java
@@ -0,0 +1,233 @@
+/**
+ * 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.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.UriInfo;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanChargeRepository;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanRepository;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalLoanChargeReadPlatformService;
+import org.springframework.stereotype.Component;
+
+@Path("/v1/working-capital-loans")
+@Component
+@Tag(name = "Working Capital Loan Charges", description = "Manages Charges for Working Capital loans")
+@RequiredArgsConstructor
+public class WorkingCapitalLoanChargesApiResource {
+
+ private final WorkingCapitalLoanRepository workingCapitalLoanRepository;
+ private final ChargeReadPlatformService chargeReadPlatformService;
+ private final PlatformSecurityContext securityContext;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final WorkingCapitalLoanChargeReadPlatformService loanChargeReadPlatformService;
+ private final WorkingCapitalLoanChargeRepository loanChargeRepository;
+
+ @GET
+ @Path("{loanId}/charges/template")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Working Capital Loan Charges Template", operationId = "retrieveTemplateWorkingCapitalLoanCharge", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loans/1/charges/template\n" + "\n")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesTemplateResponse.class)))
+ public LoanChargeData retrieveTemplate(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
+ return retrieveTemplate(loanId, null);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/charges/template")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Working Capital Loan Charges Template", operationId = "retrieveTemplateWorkingCapitalLoanChargeByLoanExternalId", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loans/1/charges/template\n" + "\n")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesTemplateResponse.class)))
+ public LoanChargeData retrieveTemplate(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId) {
+
+ return retrieveTemplate(null, loanExternalId);
+ }
+
+ @POST
+ @Path("{loanId}/charges")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Create a Loan Charge ", description = "Creates a Loan Charge")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.PostLoansLoanIdChargesRequest.class)))
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.PostLoansLoanIdChargesResponse.class)))
+ public CommandProcessingResult createLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
+ @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+
+ return handleExecuteLoanCharge(loanId, null, "create", apiRequestBodyAsJson);
+ }
+
+ @POST
+ @Path("external-id/{loanExternalId}/charges")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Create a Loan Charge (no command provided) or Pay a charge (command=pay)", operationId = "executeWorkingCapitalLoanChargeByLoanExternalId", description = "Creates a Loan Charge | Pay a Loan Charge")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.PostLoansLoanIdChargesRequest.class)))
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.PostLoansLoanIdChargesResponse.class)))
+ public CommandProcessingResult executeLoanCharge(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
+ @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+
+ return handleExecuteLoanCharge(null, loanExternalId, "create", apiRequestBodyAsJson);
+ }
+
+ @GET
+ @Path("{loanId}/charges")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "List Loan Charges", operationId = "retrieveAllWorkingCapitalLoanChargesByLoanId", description = "It lists all the Loan Charges specific to a Loan \n\n"
+ + "Example Requests:\n" + "\n" + "loans/1/charges\n" + "\n" + "\n" + "loans/1/charges?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))))
+ public List retrieveAllLoanCharges(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
+ @Context final UriInfo uriInfo) {
+
+ return retrieveAllLoanCharges(loanId, null, uriInfo);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/charges")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "List Loan Charges", operationId = "retrieveAllWorkingCapitalLoanChargesByLoanExternalId", description = "It lists all the Loan Charges specific to a Loan \n\n"
+ + "Example Requests:\n" + "\n" + "loans/1/charges\n" + "\n" + "\n" + "loans/1/charges?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))))
+ public List retrieveAllLoanCharges(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
+ @Context final UriInfo uriInfo) {
+
+ return retrieveAllLoanCharges(null, loanExternalId, uriInfo);
+ }
+
+ @GET
+ @Path("{loanId}/charges/{loanChargeId}")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge ID"
+ + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))
+ public LoanChargeData retrieveWorkingCapitalLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
+ @PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId) {
+
+ return retrieveWorkingCapitalLoanCharge(loanId, null, loanChargeId, null);
+ }
+
+ @GET
+ @Path("{loanId}/charges/external-id/{loanChargeExternalId}")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveWorkingCapitalLoanChargeByChargeExternalId", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge External ID"
+ + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n"
+ + "/loans/1/charges/external-id/1?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))
+ public LoanChargeData retrieveWorkingCapitalLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
+ @PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) {
+
+ return retrieveWorkingCapitalLoanCharge(loanId, null, null, loanChargeExternalId);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/charges/{loanChargeId}")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveWorkingCapitalLoanChargeByLoanExternalId", description = "Retrieves Loan Charge according to the Loan external ID and Loan Charge ID"
+ + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))
+ public LoanChargeData retrieveWorkingCapitalLoanCharge(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
+ @PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId) {
+
+ return retrieveWorkingCapitalLoanCharge(null, loanExternalId, loanChargeId, null);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveWorkingCapitalLoanChargeByLoanAndChargeExternalId", description = "Retrieves Loan Charge according to the Loan External ID and Loan Charge External ID"
+ + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage")
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))
+ public LoanChargeData retrieveWorkingCapitalLoanCharge(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
+ @PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) {
+
+ return retrieveWorkingCapitalLoanCharge(null, loanExternalId, null, loanChargeExternalId);
+ }
+
+ private LoanChargeData retrieveWorkingCapitalLoanCharge(final Long loanId, final String loanExternalIdStr, final Long loanChargeId,
+ final String loanChargeExternalIdStr) {
+ ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
+ ExternalId loanChargeExternalId = ExternalIdFactory.produce(loanChargeExternalIdStr);
+
+ Long resolvedLoanId = loanId == null ? workingCapitalLoanRepository.findIdByExternalId(loanExternalId) : loanId;
+ Long resolvedLoanChargeId = loanChargeId == null ? loanChargeRepository.findIdByExternalId(loanChargeExternalId) : loanChargeId;
+
+ return loanChargeReadPlatformService.retrieveLoanChargeDetails(resolvedLoanChargeId, resolvedLoanId);
+ }
+
+ private LoanChargeData retrieveTemplate(final Long loanId, final String loanExternalIdStr) {
+
+ securityContext.authenticatedUser().validateHasReadPermission(WorkingCapitalLoanConstants.WCL_RESOURCE_NAME);
+
+ ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
+ Long resolvedLoanId = loanId == null ? workingCapitalLoanRepository.findIdByExternalId(loanExternalId) : loanId;
+
+ final List chargeOptions = chargeReadPlatformService.retrieveWorkingCapitalLoanAccountApplicableCharges(resolvedLoanId);
+ return LoanChargeData.template(chargeOptions);
+ }
+
+ private List retrieveAllLoanCharges(final Long loanId, final String loanExternalIdStr, final UriInfo uriInfo) {
+ ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
+ Long resolvedLoanId = loanId == null ? workingCapitalLoanRepository.findIdByExternalId(loanExternalId) : loanId;
+ return loanChargeReadPlatformService.retrieveLoanCharges(resolvedLoanId);
+ }
+
+ private CommandProcessingResult handleExecuteLoanCharge(final Long loanId, final String loanExternalIdStr, final String commandParam,
+ final String apiRequestBodyAsJson) {
+
+ ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
+ Long resolvedLoanId = loanId == null ? workingCapitalLoanRepository.findIdByExternalId(loanExternalId) : loanId;
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createWorkingCapitalLoanCharge(resolvedLoanId)
+ .withJson(apiRequestBodyAsJson).build();
+ return commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResourceSwagger.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResourceSwagger.java
new file mode 100644
index 00000000000..6a534f5e695
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanChargesApiResourceSwagger.java
@@ -0,0 +1,208 @@
+/**
+ * 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.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDate;
+import java.util.Set;
+
+final class WorkingCapitalLoanChargesApiResourceSwagger {
+
+ private WorkingCapitalLoanChargesApiResourceSwagger() {}
+
+ @Schema(description = "GetLoansLoanIdChargesChargeIdResponse")
+ public static final class GetLoansLoanIdChargesChargeIdResponse {
+
+ private GetLoansLoanIdChargesChargeIdResponse() {}
+
+ public static final class GetLoanChargeTimeType {
+
+ private GetLoanChargeTimeType() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "chargeTimeType.disbursement")
+ public String code;
+ @Schema(example = "Disbursement")
+ public String description;
+ }
+
+ public static final class GetLoanChargeCalculationType {
+
+ private GetLoanChargeCalculationType() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "chargeCalculationType.flat")
+ public String code;
+ @Schema(example = "Flat")
+ public String description;
+ }
+
+ public static final class GetLoanChargeCurrency {
+
+ private GetLoanChargeCurrency() {}
+
+ @Schema(example = "USD")
+ public String code;
+ @Schema(example = "US Dollar")
+ public String name;
+ @Schema(example = "2")
+ public Integer decimalPlaces;
+ @Schema(example = "$")
+ public String displaySymbol;
+ @Schema(example = "currency.USD")
+ public String nameCode;
+ @Schema(example = "US Dollar ($)")
+ public String displayLabel;
+ }
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "1")
+ public Long chargeId;
+ @Schema(example = "Loan Processing fee")
+ public String name;
+ public GetLoanChargeTimeType chargeTimeType;
+ public GetLoanChargeCalculationType chargeCalculationType;
+ @Schema(example = "0")
+ public Double percentage;
+ @Schema(example = "0")
+ public Double amountPercentageAppliedTo;
+ public GetLoanChargeCurrency currency;
+ @Schema(example = "100.00")
+ public Double amount;
+ @Schema(example = "0.00")
+ public Double amountPaid;
+ @Schema(example = "0.00")
+ public Double amountWaived;
+ @Schema(example = "0.00")
+ public Double amountWrittenOff;
+ @Schema(example = "100.00")
+ public Double amountOutstanding;
+ @Schema(example = "100.00")
+ public Double amountOrPercentage;
+ @Schema(example = "false")
+ public Boolean penalty;
+ @Schema(example = "27 March 2013")
+ public LocalDate submittedOnDate;
+ @Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
+ public String externalId;
+ @Schema(example = "26 March 2013")
+ public LocalDate dueDate;
+ }
+
+ @Schema(description = "GetLoansLoanIdChargesTemplateResponse")
+ public static final class GetLoansLoanIdChargesTemplateResponse {
+
+ private GetLoansLoanIdChargesTemplateResponse() {}
+
+ public static final class GetLoanChargeTemplateChargeOptions {
+
+ private GetLoanChargeTemplateChargeOptions() {}
+
+ public static final class GetLoanChargeTemplateChargeTimeType {
+
+ private GetLoanChargeTemplateChargeTimeType() {}
+
+ @Schema(example = "2")
+ public Long id;
+ @Schema(example = "chargeTimeType.specifiedDueDate")
+ public String code;
+ @Schema(example = "Specified due date")
+ public String description;
+ }
+
+ public static final class GetLoanChargeTemplateChargeAppliesTo {
+
+ private GetLoanChargeTemplateChargeAppliesTo() {}
+
+ @Schema(example = "1 ")
+ public Long id;
+ @Schema(example = "chargeAppliesTo.loan")
+ public String code;
+ @Schema(example = "Loan")
+ public String description;
+ }
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "Collection fee")
+ public String name;
+ @Schema(example = "true")
+ public Boolean active;
+ @Schema(example = "false")
+ public Boolean penalty;
+ public GetLoansLoanIdChargesChargeIdResponse.GetLoanChargeCurrency currency;
+ @Schema(example = "100.00")
+ public Double amount;
+ public GetLoanChargeTemplateChargeTimeType chargeTimeType;
+ public GetLoanChargeTemplateChargeAppliesTo chargeAppliesTo;
+ public GetLoansLoanIdChargesChargeIdResponse.GetLoanChargeCalculationType chargeCalculationType;
+ }
+
+ @Schema(example = "0.00")
+ public Double amountPaid;
+ @Schema(example = "0.00")
+ public Double amountWaived;
+ @Schema(example = "0.00")
+ public Double amountWrittenOff;
+ public Set chargeOptions;
+ @Schema(example = "false")
+ public Boolean penalty;
+ }
+
+ @Schema(description = " PostLoansLoanIdChargesRequest")
+ public static final class PostLoansLoanIdChargesRequest {
+
+ private PostLoansLoanIdChargesRequest() {}
+
+ @Schema(example = "2")
+ public Long chargeId;
+ @Schema(example = "en")
+ public String locale;
+ @Schema(example = "100.00")
+ public Double amount;
+ @Schema(example = "dd MMMM yyyy")
+ public String dateFormat;
+ @Schema(example = "29 April 2013")
+ public String dueDate;
+ @Schema(example = "786444UUUYYH7")
+ public String externalId;
+ }
+
+ @Schema(description = " PostLoansLoanIdChargesResponse")
+ public static final class PostLoansLoanIdChargesResponse {
+
+ private PostLoansLoanIdChargesResponse() {}
+
+ @Schema(example = "1")
+ public Long officeId;
+ @Schema(example = "1")
+ public Long clientId;
+ @Schema(example = "1")
+ public Long loanId;
+ @Schema(example = "31")
+ public Long resourceId;
+ @Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
+ public String resourceExternalId;
+ }
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java
new file mode 100644
index 00000000000..18826a16a37
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java
@@ -0,0 +1,160 @@
+/**
+ * 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.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Convert;
+import jakarta.persistence.Entity;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationTypeConverter;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentModeConverter;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeTypeConverter;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
+
+@Setter
+@Getter
+@Entity
+@Table(name = "m_wc_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
+public class WorkingCapitalLoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false)
+ private WorkingCapitalLoan loan;
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false)
+ private Charge charge;
+
+ @Column(name = "charge_time_enum", nullable = false)
+ @Convert(converter = ChargeTimeTypeConverter.class)
+ private ChargeTimeType chargeTime;
+
+ @Column(name = "submitted_on_date")
+ private LocalDate submittedOnDate;
+
+ @Column(name = "due_for_collection_as_of_date")
+ private LocalDate dueDate;
+
+ @Column(name = "charge_calculation_enum")
+ @Convert(converter = ChargeCalculationTypeConverter.class)
+ private ChargeCalculationType chargeCalculation;
+
+ @Column(name = "charge_payment_mode_enum")
+ @Convert(converter = ChargePaymentModeConverter.class)
+ private ChargePaymentMode chargePaymentMode;
+
+ @Column(name = "calculation_percentage", scale = 6, precision = 19)
+ private BigDecimal percentage;
+
+ @Column(name = "calculation_on_amount", scale = 6, precision = 19)
+ private BigDecimal amountPercentageAppliedTo;
+
+ @Column(name = "charge_amount_or_percentage", scale = 6, precision = 19, nullable = false)
+ private BigDecimal amountOrPercentage;
+
+ @Column(name = "amount", scale = 6, precision = 19, nullable = false)
+ private BigDecimal amount;
+
+ @Column(name = "amount_paid_derived", scale = 6, precision = 19)
+ private BigDecimal amountPaid;
+
+ @Column(name = "amount_waived_derived", scale = 6, precision = 19)
+ private BigDecimal amountWaived;
+
+ @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19)
+ private BigDecimal amountWrittenOff;
+
+ @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
+ private BigDecimal amountOutstanding;
+
+ @Column(name = "is_penalty", nullable = false)
+ private boolean penaltyCharge = false;
+
+ @Column(name = "is_paid_derived", nullable = false)
+ private boolean paid = false;
+
+ @Column(name = "waived", nullable = false)
+ private boolean waived = false;
+
+ @Column(name = "min_cap", scale = 6, precision = 19)
+ private BigDecimal minCap;
+
+ @Column(name = "max_cap", scale = 6, precision = 19)
+ private BigDecimal maxCap;
+
+ @Column(name = "is_active", nullable = false)
+ private boolean active = true;
+
+ @Column(name = "external_id")
+ private ExternalId externalId;
+
+ public LoanChargeData toData() {
+ EnumOptionData chargeTimeTypeData = new EnumOptionData(getChargeTime().getValue().longValue(), getChargeTime().getCode(),
+ String.valueOf(getChargeTime().getValue()));
+ EnumOptionData chargeCalculationTypeData = new EnumOptionData(getChargeCalculation().getValue().longValue(),
+ getChargeCalculation().getCode(), String.valueOf(getChargeCalculation().getValue()));
+ EnumOptionData chargePaymentModeData = new EnumOptionData(getChargePaymentMode().getValue().longValue(),
+ getChargePaymentMode().getCode(), String.valueOf(getChargePaymentMode().getValue()));
+
+ return LoanChargeData.builder().id(getId()).chargeId(getCharge().getId()).name(getCharge().getName())
+ .currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid).amountWaived(amountWaived)
+ .amountWrittenOff(amountWrittenOff).amountOutstanding(amountOutstanding).chargeTimeType(chargeTimeTypeData)
+ .submittedOnDate(submittedOnDate).dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).percentage(percentage)
+ .amountPercentageAppliedTo(amountPercentageAppliedTo).amountOrPercentage(amountOrPercentage).penalty(penaltyCharge)
+ .chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
+ .externalId(externalId).build();
+ }
+
+ public static WorkingCapitalLoanCharge build(WorkingCapitalLoan loan, ExternalId externalId, Charge charge, BigDecimal amount,
+ LocalDate dueDate, LocalDate submittedOnDate) {
+ WorkingCapitalLoanCharge res = new WorkingCapitalLoanCharge();
+ res.setLoan(loan);
+ res.setCharge(charge);
+ res.setChargeTime(ChargeTimeType.fromInt(charge.getChargeTimeType()));
+ res.setActive(true);
+ res.setExternalId(externalId);
+ res.setChargeCalculation(ChargeCalculationType.fromInt(charge.getChargeCalculation()));
+ res.setChargePaymentMode(ChargePaymentMode.fromInt(charge.getChargePaymentMode()));
+ res.setChargeTime(ChargeTimeType.fromInt(charge.getChargeTimeType()));
+ res.setPenaltyCharge(charge.isPenalty());
+ res.setDueDate(dueDate);
+ res.setSubmittedOnDate(submittedOnDate);
+ res.setAmountOrPercentage(amount);
+ res.setAmountOutstanding(BigDecimal.ZERO);
+ res.setAmountPaid(BigDecimal.ZERO);
+ res.setAmountWaived(BigDecimal.ZERO);
+ res.setAmountWrittenOff(BigDecimal.ZERO);
+ return res;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/WorkingCapitalLoanCreateChargeCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/WorkingCapitalLoanCreateChargeCommandHandler.java
new file mode 100644
index 00000000000..ed696921b0d
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/WorkingCapitalLoanCreateChargeCommandHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.domain.CommandWrapperConstants;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalLoanChargeWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = CommandWrapperConstants.ENTITY_WORKINGCAPITALLOANCHARGE, action = CommandWrapperConstants.ACTION_CREATE)
+public class WorkingCapitalLoanCreateChargeCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanChargeWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.createLoanCharge(command.getLoanId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java
new file mode 100644
index 00000000000..f6e01b4b742
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.repository;
+
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanCharge;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface WorkingCapitalLoanChargeRepository
+ extends JpaRepository, CrudRepository {
+
+ Long findIdByExternalId(ExternalId externalId);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeConstants.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeConstants.java
new file mode 100644
index 00000000000..acd96cb8102
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeConstants.java
@@ -0,0 +1,32 @@
+/**
+ * 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.serialization;
+
+public final class WorkingCapitalLoanChargeConstants {
+
+ private WorkingCapitalLoanChargeConstants() {}
+
+ public static final String chargeIdParamName = "chargeId";
+ public static final String amountParamName = "amount";
+ public static final String dueDateParamName = "dueDate";
+ public static final String localeParamName = "locale";
+ public static final String dateFormatParamName = "dateFormat";
+ public static final String externalIdParamName = "externalId";
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeDataValidator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeDataValidator.java
new file mode 100644
index 00000000000..d0732cb0f01
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanChargeDataValidator.java
@@ -0,0 +1,88 @@
+/**
+ * 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.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class WorkingCapitalLoanChargeDataValidator {
+
+ private final FromJsonHelper fromJsonHelper;
+
+ public void validateCreateLoanCharge(final String json) {
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Set allowedParameters = new HashSet<>(
+ Arrays.asList(WorkingCapitalLoanChargeConstants.chargeIdParamName, WorkingCapitalLoanChargeConstants.dueDateParamName,
+ WorkingCapitalLoanChargeConstants.amountParamName, WorkingCapitalLoanChargeConstants.externalIdParamName,
+ WorkingCapitalLoanChargeConstants.localeParamName, WorkingCapitalLoanChargeConstants.dateFormatParamName));
+
+ final Type typeOfMap = new TypeToken