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>() {}.getType(); + fromJsonHelper.checkForUnsupportedParameters(typeOfMap, json, allowedParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("workingCapitalLoanCharge"); + + final JsonElement element = this.fromJsonHelper.parse(json); + final Long chargeId = this.fromJsonHelper.extractLongNamed(WorkingCapitalLoanChargeConstants.chargeIdParamName, element); + baseDataValidator.reset().parameter(WorkingCapitalLoanChargeConstants.chargeIdParamName).value(chargeId).notNull() + .integerGreaterThanZero(); + + final BigDecimal amount = this.fromJsonHelper.extractBigDecimalWithLocaleNamed(WorkingCapitalLoanChargeConstants.amountParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanChargeConstants.amountParamName).value(amount).notNull().positiveAmount(); + + if (this.fromJsonHelper.parameterExists(WorkingCapitalLoanChargeConstants.dueDateParamName, element)) { + final LocalDate dueDate = this.fromJsonHelper.extractLocalDateNamed(WorkingCapitalLoanChargeConstants.dueDateParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanChargeConstants.dueDateParamName).value(dueDate).notBlank(); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformService.java new file mode 100644 index 00000000000..fc9dc46f422 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformService.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.workingcapitalloan.service; + +import java.util.List; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; + +public interface WorkingCapitalLoanChargeReadPlatformService { + + LoanChargeData retrieveLoanChargeDetails(Long id, Long loanId); + + List retrieveLoanCharges(Long loanId); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformServiceImpl.java new file mode 100644 index 00000000000..390263f4a52 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeReadPlatformServiceImpl.java @@ -0,0 +1,137 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.workingcapitalloan.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; +import org.apache.fineract.portfolio.charge.service.ChargeEnumerations; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class WorkingCapitalLoanChargeReadPlatformServiceImpl implements WorkingCapitalLoanChargeReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + + @Override + public LoanChargeData retrieveLoanChargeDetails(final Long id, final Long loanId) { + try { + final LoanChargeMapper rm = new LoanChargeMapper(); + final String sql = "select " + rm.schema() + " where lc.id=? and lc.loan_id=?"; + return this.jdbcTemplate.queryForObject(sql, rm, id, loanId); // NOSONAR + } catch (final EmptyResultDataAccessException e) { + throw new LoanChargeNotFoundException(id, loanId, e); + } + } + + @Override + public List retrieveLoanCharges(final Long loanId) { + final LoanChargeMapper rm = new LoanChargeMapper(); + final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = true" + + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; + return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR + } + + private static final class LoanChargeMapper implements RowMapper { + + public String schema() { + return "lc.id as id, lc.external_id as externalId, c.id as chargeId, c.name as name, lc.submitted_on_date as submittedOnDate, " // + + "lc.amount as amountDue, lc.amount_paid_derived as amountPaid, lc.amount_waived_derived as amountWaived, " // + + "lc.amount_writtenoff_derived as amountWrittenOff, lc.amount_outstanding_derived as amountOutstanding, " // + + "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " // + + "lc.charge_time_enum as chargeTime, lc.is_penalty as penalty, " // + + "lc.due_for_collection_as_of_date as dueAsOfDate, lc.charge_calculation_enum as chargeCalculation, " // + + "lc.charge_payment_mode_enum as chargePaymentMode, lc.is_paid_derived as paid, lc.waived as waived, " // + + "lc.min_cap as minCap, lc.max_cap as maxCap, lc.charge_amount_or_percentage as amountOrPercentage, " // + + "lc.loan_id as loanId, c.currency_code as currencyCode, oc.name as currencyName, " // + + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " // + + "oc.internationalized_name_code as currencyNameCode, l.external_id as externalLoanId from m_charge c " // + + "join m_organisation_currency oc on c.currency_code = oc.code join m_wc_loan_charge lc on lc.charge_id = c.id " // + + " join m_wc_loan l on lc.loan_id = l.id"; + } + + @Override + public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + final Long id = rs.getLong("id"); + final Long chargeId = rs.getLong("chargeId"); + final Long loanId = rs.getLong("loanId"); + final String name = rs.getString("name"); + final BigDecimal amount = rs.getBigDecimal("amountDue"); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid"); + final BigDecimal amountWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWaived"); + final BigDecimal amountWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountWrittenOff"); + final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding"); + + final BigDecimal percentageOf = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentageOf"); + final BigDecimal amountPercentageAppliedTo = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPercentageAppliedTo"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final int chargeTime = rs.getInt("chargeTime"); + final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); + + LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); + + final int chargeCalculation = rs.getInt("chargeCalculation"); + final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); + final boolean penalty = rs.getBoolean("penalty"); + + final int chargePaymentMode = rs.getInt("chargePaymentMode"); + final EnumOptionData paymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode); + final boolean paid = rs.getBoolean("paid"); + final boolean waived = rs.getBoolean("waived"); + final BigDecimal minCap = rs.getBigDecimal("minCap"); + final BigDecimal maxCap = rs.getBigDecimal("maxCap"); + final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage"); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + + final String externalIdStr = rs.getString("externalId"); + final ExternalId externalId = ExternalIdFactory.produce(externalIdStr); + final String externalLoanIdStr = rs.getString("externalLoanId"); + final ExternalId externalLoanId = ExternalIdFactory.produce(externalLoanIdStr); + + return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, + chargeTimeType, submittedOnDate, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, + paymentMode, paid, waived, loanId, externalLoanId, minCap, maxCap, amountOrPercentage, null, externalId); + } + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformService.java new file mode 100644 index 00000000000..55cca6ab6f6 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformService.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.workingcapitalloan.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface WorkingCapitalLoanChargeWritePlatformService { + + CommandProcessingResult createLoanCharge(Long loanId, JsonCommand command); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformServiceImpl.java new file mode 100644 index 00000000000..2b233ef3231 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanChargeWritePlatformServiceImpl.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.workingcapitalloan.service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.portfolio.charge.domain.Charge; +import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanCharge; +import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanChargeRepository; +import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanRepository; +import org.apache.fineract.portfolio.workingcapitalloan.serialization.WorkingCapitalLoanChargeDataValidator; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class WorkingCapitalLoanChargeWritePlatformServiceImpl implements WorkingCapitalLoanChargeWritePlatformService { + + private final WorkingCapitalLoanChargeDataValidator loanChargeDataValidator; + private final WorkingCapitalLoanRepository workingCapitalLoanRepository; + private final ChargeRepositoryWrapper chargeRepository; + private final WorkingCapitalLoanChargeRepository loanChargeRepository; + private final ExternalIdFactory externalIdFactory; + + @Override + public CommandProcessingResult createLoanCharge(Long loanId, JsonCommand command) { + loanChargeDataValidator.validateCreateLoanCharge(command.json()); + WorkingCapitalLoan loan = workingCapitalLoanRepository.findById(loanId) + .orElseThrow(() -> new WorkingCapitalLoanNotFoundException(loanId)); + + final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount"); + final LocalDate dueDate = command.dateValueOfParameterNamed("dueDate"); + final Long chargeId = command.longValueOfParameterNamed("chargeId"); + final ExternalId externalId = externalIdFactory.createFromCommand(command, WorkingCapitalLoanConstants.externalIdParameterName); + + final Charge chargeDefinition = chargeRepository.findOneWithNotFoundDetection(chargeId); + WorkingCapitalLoanCharge loanCharge = WorkingCapitalLoanCharge.build(loan, externalId, chargeDefinition, amount, dueDate, + ThreadLocalContextUtil.getBusinessDate()); + + loanCharge = loanChargeRepository.saveAndFlush(loanCharge); + + // update loan balances + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(loanCharge.getId()) // + .withEntityExternalId(loanCharge.getExternalId()) // + .withOfficeId(loan.getOfficeId()) // + .withClientId(loan.getClientId()) // + .withLoanId(loanId) // + .build(); + } +} diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml index df95a05cdc9..f446be6e670 100644 --- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml @@ -60,4 +60,5 @@ + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0039_wc_loan_charge.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0039_wc_loan_charge.xml new file mode 100644 index 00000000000..5ad55f53220 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0039_wc_loan_charge.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml b/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml index 227859b7d04..17fa19e7760 100644 --- a/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml +++ b/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-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 @@ -172,6 +175,7 @@ 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.WorkingCapitalLoanCharge org.apache.fineract.portfolio.workingcapitalloannearbreach.domain.WorkingCapitalNearBreach org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodPaymentRateChange org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct @@ -182,7 +186,7 @@ false - + diff --git a/gradle.properties b/gradle.properties index 70829cbd9bb..979ecc41e8c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ # specific language governing permissions and limitations # under the License. # -org.gradle.jvmargs=-Xmx8g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null +org.gradle.jvmargs=-Xmx18g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null buildType=BUILD org.gradle.caching=true org.gradle.parallel=true