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 a25e595b0c4..801f25831ed 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
@@ -594,6 +594,46 @@ public CommandWrapperBuilder deleteWorkingCapitalLoanApplication() {
return this;
}
+ public CommandWrapperBuilder approveWorkingCapitalLoanApplication(final Long loanId) {
+ this.actionName = "APPROVE";
+ this.entityName = "WORKINGCAPITALLOAN";
+ this.entityId = loanId;
+ this.href = "/workingcapitalloans/" + loanId;
+ return this;
+ }
+
+ public CommandWrapperBuilder rejectWorkingCapitalLoanApplication(final Long loanId) {
+ this.actionName = "REJECT";
+ this.entityName = "WORKINGCAPITALLOAN";
+ this.entityId = loanId;
+ this.href = "/workingcapitalloans/" + loanId;
+ return this;
+ }
+
+ public CommandWrapperBuilder undoWorkingCapitalLoanApplicationApproval(final Long loanId) {
+ this.actionName = "APPROVALUNDO";
+ this.entityName = "WORKINGCAPITALLOAN";
+ this.entityId = loanId;
+ this.href = "/workingcapitalloans/" + loanId;
+ return this;
+ }
+
+ public CommandWrapperBuilder disburseWorkingCapitalLoanApplication(final Long loanId) {
+ this.actionName = "DISBURSE";
+ this.entityName = "WORKINGCAPITALLOAN";
+ this.entityId = loanId;
+ this.href = "/workingcapitalloans/" + loanId;
+ return this;
+ }
+
+ public CommandWrapperBuilder undoWorkingCapitalLoanApplicationDisbursal(final Long loanId) {
+ this.actionName = "DISBURSALUNDO";
+ this.entityName = "WORKINGCAPITALLOAN";
+ this.entityId = loanId;
+ this.href = "/workingcapitalloans/" + loanId;
+ return this;
+ }
+
public CommandWrapperBuilder createClientIdentifier(final Long clientId) {
this.actionName = "CREATE";
this.entityName = "CLIENTIDENTIFIER";
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 19f793c2b93..3b1284d2305 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
@@ -219,6 +219,9 @@
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanBalance
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDisbursementDetails
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPaymentAllocationRule
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionAllocation
+ org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionPaymentDetail
org.apache.fineract.portfolio.loanproduct.domain.AllocationTypeListConverter
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/WorkingCapitalLoanConstants.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/WorkingCapitalLoanConstants.java
index 95266bdb5b2..2084ce73bbb 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/WorkingCapitalLoanConstants.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/WorkingCapitalLoanConstants.java
@@ -42,4 +42,24 @@ private WorkingCapitalLoanConstants() {
public static final String submittedOnNoteParameterName = "submittedOnNote";
public static final String totalPaymentParamName = "totalPayment";
public static final String principalAmountParamName = "principalAmount";
+
+ // Approval / Rejection / Undo-approval parameters
+ public static final String RESOURCE_NAME = WCL_RESOURCE_NAME;
+ public static final String approvedOnDateParamName = "approvedOnDate";
+ public static final String approvedLoanAmountParamName = "approvedLoanAmount";
+ public static final String expectedDisbursementDateParamName = "expectedDisbursementDate";
+ public static final String discountAmountParamName = "discountAmount";
+ public static final String noteParamName = "note";
+ public static final String rejectedOnDateParamName = "rejectedOnDate";
+
+ // Disbursal / Undo disbursal parameters
+ public static final String actualDisbursementDateParamName = "actualDisbursementDate";
+ public static final String transactionAmountParamName = "transactionAmount";
+ public static final String paymentDetailsParamName = "paymentDetails";
+ public static final String paymentTypeIdParamName = "paymentTypeId";
+ public static final String accountNumberParamName = "accountNumber";
+ public static final String checkNumberParamName = "checkNumber";
+ public static final String routingCodeParamName = "routingCode";
+ public static final String receiptNumberParamName = "receiptNumber";
+ public static final String bankNumberParamName = "bankNumber";
}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResource.java
index 1b186465676..e4c47400d39 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResource.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResource.java
@@ -43,6 +43,8 @@
import org.apache.fineract.infrastructure.core.api.jersey.Pagination;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants;
@@ -190,6 +192,36 @@ public CommandProcessingResult deleteLoanApplication(
return deleteLoanApplication(null, loanExternalId);
}
+ @POST
+ @Path("{loanId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "stateTransitionWorkingCapitalLoanById", summary = "Approve/Reject/Undo-approve/Disburse/Undo-disburse a Working Capital Loan", description = "Mandatory command query parameter: approve, reject, undoapproval, disburse, or undodisbursal.")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanApiResourceSwagger.PostWorkingCapitalLoansLoanIdRequest.class)))
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanApiResourceSwagger.PostWorkingCapitalLoansLoanIdResponse.class))) })
+ public CommandProcessingResult stateTransitionById(
+ @PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
+ @QueryParam("command") @Parameter(description = "command", required = true) final String commandParam,
+ @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+ return handleStateTransition(loanId, null, commandParam, apiRequestBodyAsJson);
+ }
+
+ @POST
+ @Path("external-id/{loanExternalId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "stateTransitionWorkingCapitalLoanByExternalId", summary = "Approve/Reject/Undo-approve/Disburse/Undo-disburse a Working Capital Loan by external id", description = "Mandatory command query parameter: approve, reject, undoapproval, disburse, or undodisbursal.")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanApiResourceSwagger.PostWorkingCapitalLoansLoanIdRequest.class)))
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanApiResourceSwagger.PostWorkingCapitalLoansLoanIdResponse.class))) })
+ public CommandProcessingResult stateTransitionByExternalId(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
+ @QueryParam("command") @Parameter(description = "command", required = true) final String commandParam,
+ @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+ return handleStateTransition(null, loanExternalId, commandParam, apiRequestBodyAsJson);
+ }
+
private CommandProcessingResult modifyLoanApplication(final Long loanId, final String loanExternalIdStr,
final String apiRequestBodyAsJson) {
final Long resolvedLoanId = loanId != null ? loanId
@@ -212,4 +244,33 @@ private CommandProcessingResult deleteLoanApplication(final Long loanId, final S
.build();
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
+
+ private CommandProcessingResult handleStateTransition(final Long loanId, final String loanExternalIdStr, final String commandParam,
+ final String apiRequestBodyAsJson) {
+ final Long resolvedLoanId = loanId != null ? loanId
+ : readPlatformService.getResolvedLoanId(ExternalIdFactory.produce(loanExternalIdStr));
+ if (resolvedLoanId == null) {
+ throw new WorkingCapitalLoanNotFoundException(ExternalIdFactory.produce(loanExternalIdStr));
+ }
+
+ final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+ CommandWrapper commandRequest = null;
+ if (CommandParameterUtil.is(commandParam, "approve")) {
+ commandRequest = builder.approveWorkingCapitalLoanApplication(resolvedLoanId).build();
+ } else if (CommandParameterUtil.is(commandParam, "reject")) {
+ commandRequest = builder.rejectWorkingCapitalLoanApplication(resolvedLoanId).build();
+ } else if (CommandParameterUtil.is(commandParam, "undoapproval")) {
+ commandRequest = builder.undoWorkingCapitalLoanApplicationApproval(resolvedLoanId).build();
+ } else if (CommandParameterUtil.is(commandParam, "disburse")) {
+ commandRequest = builder.disburseWorkingCapitalLoanApplication(resolvedLoanId).build();
+ } else if (CommandParameterUtil.is(commandParam, "undodisbursal")) {
+ commandRequest = builder.undoWorkingCapitalLoanApplicationDisbursal(resolvedLoanId).build();
+ }
+
+ if (commandRequest == null) {
+ throw new UnrecognizedQueryParamException("command", commandParam);
+ }
+
+ return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }
}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java
index 881a9881f19..86140f6e900 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java
@@ -197,6 +197,8 @@ private GetWorkingCapitalLoansLoanIdResponse() {}
public List disbursementDetails;
/** Running balances (principal outstanding, total payment, etc.). */
public GetBalance balance;
+ @Schema(description = "Transaction history (e.g. disbursement).")
+ public List transactions;
}
@Schema(description = "Working capital loan running balances")
@@ -244,6 +246,19 @@ private GetPaymentAllocation() {}
public List paymentAllocationOrder;
}
+ @Schema(description = "Loan transaction type enum data (same as basic loan)")
+ public static final class LoanTransactionEnumData {
+
+ private LoanTransactionEnumData() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "loanTransactionType.disbursement")
+ public String code;
+ @Schema(example = "Disbursement")
+ public String value;
+ }
+
@Schema(description = "GetPaymentAllocationOrder")
public static final class GetPaymentAllocationOrder {
@@ -260,9 +275,9 @@ public static final class PostWorkingCapitalLoansRequest {
private PostWorkingCapitalLoansRequest() {}
- @Schema(example = "1", required = true)
+ @Schema(example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
public Long clientId;
- @Schema(example = "1", required = true)
+ @Schema(example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
public Long productId;
@Schema(example = "1")
public Long fundId;
@@ -270,7 +285,7 @@ private PostWorkingCapitalLoansRequest() {}
public String accountNo;
@Schema(example = "ext-id-001")
public String externalId;
- @Schema(example = "10000.00", required = true, description = "Principal (disbursement) amount")
+ @Schema(example = "10000.00", requiredMode = Schema.RequiredMode.REQUIRED, description = "Principal (disbursement) amount")
public BigDecimal principalAmount;
@Schema(example = "10500.00")
public BigDecimal totalPayment;
@@ -335,6 +350,25 @@ private PostWorkingCapitalLoansResponse() {}
public Long loanId;
}
+ @Schema(description = "Payment details for disbursement (Account No, Cheque No, Routing Code, Receipt No, Bank code)")
+ public static final class PostWorkingCapitalLoansLoanIdDisbursementPaymentDetails {
+
+ private PostWorkingCapitalLoansLoanIdDisbursementPaymentDetails() {}
+
+ @Schema(example = "1", description = "Payment type id")
+ public Integer paymentTypeId;
+ @Schema(example = "acc123", description = "Account No")
+ public String accountNumber;
+ @Schema(example = "che123", description = "Cheque No")
+ public String checkNumber;
+ @Schema(example = "rou123", description = "Routing Code")
+ public String routingCode;
+ @Schema(example = "rec123", description = "Receipt No")
+ public String receiptNumber;
+ @Schema(example = "ban123", description = "Bank code")
+ public String bankNumber;
+ }
+
@Schema(description = "PutWorkingCapitalLoansLoanIdRequest")
public static final class PutWorkingCapitalLoansLoanIdRequest {
@@ -397,4 +431,53 @@ private DeleteWorkingCapitalLoansLoanIdResponse() {}
@Schema(example = "1")
public Long resourceId;
}
+
+ @Schema(description = "PostWorkingCapitalLoansLoanIdResponse")
+ public static final class PostWorkingCapitalLoansLoanIdResponse {
+
+ private PostWorkingCapitalLoansLoanIdResponse() {}
+
+ @Schema(example = "2")
+ public Long officeId;
+ @Schema(example = "6")
+ public Long clientId;
+ @Schema(example = "3")
+ public Long loanId;
+ @Schema(example = "3")
+ public Long resourceId;
+ @Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
+ public String resourceExternalId;
+ public Object changes;
+ }
+
+ @Schema(description = "Request for state transition: approve, reject, undoapproval, disburse, undodisbursal")
+ public static final class PostWorkingCapitalLoansLoanIdRequest {
+
+ private PostWorkingCapitalLoansLoanIdRequest() {}
+
+ @Schema(example = "15 January 2024", description = "Date of approval")
+ public String approvedOnDate;
+ @Schema(example = "10000.00", description = "Approved principal amount (optional, defaults to proposed principal)")
+ public BigDecimal approvedLoanAmount;
+ @Schema(example = "1 February 2024", description = "Expected disbursement date")
+ public String expectedDisbursementDate;
+ @Schema(example = "0.0", description = "Discount amount (cannot exceed creation-time discount)")
+ public BigDecimal discountAmount;
+ @Schema(example = "15 January 2024", description = "Date of rejection")
+ public String rejectedOnDate;
+ @Schema(example = "Approval/Rejection/Disbursal Note")
+ public String note;
+ @Schema(example = "en_GB")
+ public String locale;
+ @Schema(example = "dd MMMM yyyy")
+ public String dateFormat;
+ @Schema(example = "28 June 2024", description = "Required for disburse - Actual Disbursement date")
+ public String actualDisbursementDate;
+ @Schema(example = "1000", description = "Disbursement amount; required for disburse. Cannot exceed approved principal.")
+ public BigDecimal transactionAmount;
+ @Schema(example = "ext-disburse-001", description = "External ID; optional for disburse")
+ public String externalId;
+ @Schema(description = "Payment details (Account No, Cheque No, Routing Code, Receipt No, Bank code)")
+ public PostWorkingCapitalLoansLoanIdDisbursementPaymentDetails paymentDetails;
+ }
}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResource.java
new file mode 100644
index 00000000000..7e574baa0ab
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResource.java
@@ -0,0 +1,140 @@
+/**
+ * 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.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.jersey.Pagination;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalLoanTransactionReadPlatformService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Component;
+
+@Component
+@Path("/v1/working-capital-loans")
+@Tag(name = "Working Capital Loan Transactions", description = "Retrieve Working Capital Loan transactions (e.g. disbursements).")
+@RequiredArgsConstructor
+public class WorkingCapitalLoanTransactionsApiResource {
+
+ private static final String RESOURCE_NAME_FOR_PERMISSIONS = WorkingCapitalLoanConstants.WCL_RESOURCE_NAME;
+
+ private final PlatformSecurityContext context;
+ private final WorkingCapitalLoanTransactionReadPlatformService transactionReadPlatformService;
+
+ @GET
+ @Path("{loanId}/transactions")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionsById", summary = "Retrieve transactions", description = "Retrieves transactions of a Working Capital Loan.\n\nExample: working-capital-loans/1/transactions")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionsResponse.class))) })
+ public Page retrieveTransactionsByLoanId(
+ @PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
+ @Parameter(hidden = true) @Pagination final Pageable pageable) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransactions(loanId, pageable);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/transactions")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionsByExternalId", summary = "Retrieve transactions by loan external id", description = "Retrieves transactions of a Working Capital Loan by loan external id.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionsResponse.class))) })
+ public Page retrieveTransactionsByExternalLoanId(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
+ @Parameter(hidden = true) @Pagination final Pageable pageable) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransactions(ExternalIdFactory.produce(loanExternalId), pageable);
+ }
+
+ @GET
+ @Path("{loanId}/transactions/{transactionId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionById", summary = "Retrieve a transaction", description = "Retrieves a single Working Capital Loan transaction.\n\nExample: working-capital-loans/1/transactions/1")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionIdResponse.class))) })
+ public WorkingCapitalLoanTransactionData retrieveTransactionByLoanIdAndTransactionId(
+ @PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
+ @PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransaction(loanId, transactionId);
+ }
+
+ @GET
+ @Path("{loanId}/transactions/external-id/{externalTransactionId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionByExternalTransactionId", summary = "Retrieve a transaction by external id", description = "Retrieves a single Working Capital Loan transaction by loan id and transaction external id.\n\nExample: working-capital-loans/1/transactions/external-id/txn-ext-001")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionIdResponse.class))) })
+ public WorkingCapitalLoanTransactionData retrieveTransactionByLoanIdAndTransactionExternalId(
+ @PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
+ @PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String externalTransactionId) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransaction(loanId, ExternalIdFactory.produce(externalTransactionId));
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/transactions/{transactionId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionByExternalLoanIdAndTransactionId", summary = "Retrieve a transaction by loan external id and transaction id", description = "Retrieves a single Working Capital Loan transaction by loan external id and transaction id.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionIdResponse.class))) })
+ public WorkingCapitalLoanTransactionData retrieveTransactionByExternalLoanIdAndTransactionId(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
+ @PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransaction(ExternalIdFactory.produce(loanExternalId), transactionId);
+ }
+
+ @GET
+ @Path("external-id/{loanExternalId}/transactions/external-id/{externalTransactionId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(operationId = "retrieveWorkingCapitalLoanTransactionByExternalLoanIdAndExternalTransactionId", summary = "Retrieve a transaction by loan and transaction external ids", description = "Retrieves a single Working Capital Loan transaction by loan external id and transaction external id.\n\nExample: working-capital-loans/external-id/loan-ext-001/transactions/external-id/txn-ext-001")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.GetWorkingCapitalLoanTransactionIdResponse.class))) })
+ public WorkingCapitalLoanTransactionData retrieveTransactionByExternalLoanIdAndTransactionExternalId(
+ @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
+ @PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String externalTransactionId) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return this.transactionReadPlatformService.retrieveTransaction(ExternalIdFactory.produce(loanExternalId),
+ ExternalIdFactory.produce(externalTransactionId));
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResourceSwagger.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResourceSwagger.java
new file mode 100644
index 00000000000..1ec36f07450
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResourceSwagger.java
@@ -0,0 +1,115 @@
+/**
+ * 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.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * Swagger documentation classes for Working Capital Loan Transactions API (GET list / GET one).
+ */
+public final class WorkingCapitalLoanTransactionsApiResourceSwagger {
+
+ private WorkingCapitalLoanTransactionsApiResourceSwagger() {}
+
+ @Schema(description = "GetWorkingCapitalLoanTransactionsResponse (Spring Data Page: content, totalElements, totalPages, number, size, first, last)")
+ public static final class GetWorkingCapitalLoanTransactionsResponse {
+
+ private GetWorkingCapitalLoanTransactionsResponse() {}
+
+ public List content;
+ @Schema(example = "5")
+ public Long totalElements;
+ @Schema(example = "1")
+ public Integer totalPages;
+ @Schema(example = "0")
+ public Integer number;
+ @Schema(example = "20")
+ public Integer size;
+ public Boolean first;
+ public Boolean last;
+ }
+
+ @Schema(description = "Working Capital Loan transaction (e.g. disbursement) in GET transaction response.")
+ public static final class GetWorkingCapitalLoanTransactionIdResponse {
+
+ private GetWorkingCapitalLoanTransactionIdResponse() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(description = "Transaction type")
+ public LoanTransactionEnumData type;
+ @Schema(example = "[2024, 2, 1]")
+ public LocalDate transactionDate;
+ @Schema(example = "[2024, 2, 1]")
+ public LocalDate submittedOnDate;
+ @Schema(example = "10000.00")
+ public BigDecimal transactionAmount;
+ @Schema(description = "Payment detail")
+ public WorkingCapitalLoanTransactionPaymentDetailData paymentDetailData;
+ @Schema(example = "txn-ext-001")
+ public String externalId;
+ @Schema(example = "false")
+ public Boolean reversed;
+ @Schema(example = "reversal-ext-001")
+ public String reversalExternalId;
+ @Schema(example = "[2024, 2, 5]")
+ public LocalDate reversedOnDate;
+ @Schema(example = "10000.00", description = "Principal portion from allocation")
+ public BigDecimal principalPortion;
+ @Schema(example = "0.00", description = "Fee charges portion from allocation")
+ public BigDecimal feeChargesPortion;
+ @Schema(example = "0.00", description = "Penalty charges portion from allocation")
+ public BigDecimal penaltyChargesPortion;
+ }
+
+ @Schema(description = "Loan transaction type enum data (same as basic loan)")
+ public static final class LoanTransactionEnumData {
+
+ private LoanTransactionEnumData() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "loanTransactionType.disbursement")
+ public String code;
+ @Schema(example = "Disbursement")
+ public String value;
+ }
+
+ @Schema(description = "Payment detail data")
+ public static final class WorkingCapitalLoanTransactionPaymentDetailData {
+
+ private WorkingCapitalLoanTransactionPaymentDetailData() {}
+
+ @Schema(example = "62")
+ public Long id;
+ @Schema(example = "acc123")
+ public String accountNumber;
+ @Schema(example = "che123")
+ public String checkNumber;
+ @Schema(example = "rou123")
+ public String routingCode;
+ @Schema(example = "rec123")
+ public String receiptNumber;
+ @Schema(example = "ban123")
+ public String bankNumber;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java
index eb754cdf51d..b9f01e3c432 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java
@@ -73,4 +73,5 @@ public class WorkingCapitalLoanData implements Serializable {
private LoanApplicationTimelineData timeline;
private List disbursementDetails;
private WorkingCapitalLoanBalanceData balance;
+ private List transactions;
}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java
new file mode 100644
index 00000000000..e52108ba42d
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java
@@ -0,0 +1,54 @@
+/**
+ * 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.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkingCapitalLoanTransactionData implements Serializable {
+
+ private Long id;
+ private LoanTransactionEnumData type;
+ private LocalDate transactionDate;
+ private LocalDate submittedOnDate;
+ private BigDecimal transactionAmount;
+ private ExternalId externalId;
+ private Boolean reversed;
+ private ExternalId reversalExternalId;
+ private LocalDate reversedOnDate;
+
+ private WorkingCapitalLoanTransactionPaymentDetailData paymentDetailData;
+ // Portions from allocation (principal, fee, penalty).
+ private BigDecimal principalPortion;
+ private BigDecimal feeChargesPortion;
+ private BigDecimal penaltyChargesPortion;
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionPaymentDetailData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionPaymentDetailData.java
new file mode 100644
index 00000000000..cc9c31e70d3
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionPaymentDetailData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.data;
+
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkingCapitalLoanTransactionPaymentDetailData implements Serializable {
+
+ private Long id;
+ private String accountNumber;
+ private String checkNumber;
+ private String routingCode;
+ private String receiptNumber;
+ private String bankNumber;
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoan.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoan.java
index 13420fc0b91..6330a88b2a2 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoan.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoan.java
@@ -28,6 +28,7 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
+import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
@@ -162,6 +163,10 @@ public class WorkingCapitalLoan extends AbstractAuditableWithUTCDateTimeCustom disbursementDetails = new ArrayList<>();
+ @OrderBy(value = "dateOf, createdDate, id")
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "wcLoan", orphanRemoval = true, fetch = FetchType.LAZY)
+ private List transactions = new ArrayList<>();
+
@Setter
@Embedded
private WorkingCapitalLoanProductRelatedDetails loanProductRelatedDetails;
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanEvent.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanEvent.java
new file mode 100644
index 00000000000..9d65e876a36
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanEvent.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.domain;
+
+public enum WorkingCapitalLoanEvent {
+
+ LOAN_APPROVED, //
+ LOAN_APPROVAL_UNDO, //
+ LOAN_REJECTED, //
+ LOAN_DISBURSED, //
+ LOAN_DISBURSAL_UNDO //
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanLifecycleStateMachine.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanLifecycleStateMachine.java
new file mode 100644
index 00000000000..82d5d6d2ada
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanLifecycleStateMachine.java
@@ -0,0 +1,56 @@
+/**
+ * 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 org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.springframework.stereotype.Component;
+
+@Component
+public class WorkingCapitalLoanLifecycleStateMachine {
+
+ public void transition(final WorkingCapitalLoanEvent event, final WorkingCapitalLoan loan) {
+ LoanStatus newStatus = getNextStatus(event, loan);
+ if (newStatus != null) {
+ loan.setLoanStatus(newStatus);
+ } else {
+ throw new PlatformApiDataValidationException("validation.msg.wc.loan.transition.not.allowed",
+ "Transition " + event + " is not allowed from status " + loan.getLoanStatus(), "loanStatus");
+ }
+ }
+
+ public boolean canTransition(final WorkingCapitalLoanEvent event, final WorkingCapitalLoan loan) {
+ return getNextStatus(event, loan) != null;
+ }
+
+ private LoanStatus getNextStatus(final WorkingCapitalLoanEvent event, final WorkingCapitalLoan loan) {
+ LoanStatus from = loan.getLoanStatus();
+ if (from == null) {
+ return null;
+ }
+
+ return switch (event) {
+ case LOAN_APPROVED -> from.isSubmittedAndPendingApproval() ? LoanStatus.APPROVED : null;
+ case LOAN_APPROVAL_UNDO -> from.isApproved() ? LoanStatus.SUBMITTED_AND_PENDING_APPROVAL : null;
+ case LOAN_REJECTED -> from.isSubmittedAndPendingApproval() ? LoanStatus.REJECTED : null;
+ case LOAN_DISBURSED -> from.isApproved() ? LoanStatus.ACTIVE : null;
+ case LOAN_DISBURSAL_UNDO -> from.isActive() ? LoanStatus.APPROVED : null;
+ };
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransaction.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransaction.java
new file mode 100644
index 00000000000..73d0c3231b1
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransaction.java
@@ -0,0 +1,117 @@
+/**
+ * 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.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Convert;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import jakarta.persistence.Version;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionTypeConverter;
+
+@Entity
+@Table(name = "m_wc_loan_transaction", uniqueConstraints = {
+ @UniqueConstraint(columnNames = { "external_id" }, name = "wc_loan_transaction_external_id_UNIQUE") })
+@Getter
+public class WorkingCapitalLoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @ManyToOne(optional = false, fetch = FetchType.LAZY)
+ @JoinColumn(name = "wc_loan_id", nullable = false)
+ private WorkingCapitalLoan wcLoan;
+
+ @Column(name = "transaction_type_id", nullable = false)
+ @Convert(converter = LoanTransactionTypeConverter.class)
+ private LoanTransactionType transactionType;
+
+ @Column(name = "transaction_date", nullable = false)
+ private LocalDate dateOf;
+
+ @Column(name = "submitted_on_date", nullable = false)
+ private LocalDate submittedOnDate;
+
+ @Column(name = "transaction_amount", scale = 6, precision = 19, nullable = false)
+ private BigDecimal transactionAmount;
+
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @JoinColumn(name = "payment_detail_id")
+ private WorkingCapitalLoanTransactionPaymentDetail paymentDetail;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "classification_cv_id")
+ private CodeValue classification;
+
+ @Column(name = "external_id", length = 100, unique = true)
+ @Setter
+ private ExternalId externalId;
+
+ @Column(name = "is_reversed", nullable = false)
+ @Setter
+ private boolean reversed;
+
+ @Column(name = "reversal_external_id", length = 100, unique = true)
+ @Setter
+ private ExternalId reversalExternalId;
+
+ @Column(name = "reversed_on_date")
+ @Setter
+ private LocalDate reversedOnDate;
+
+ @Version
+ @Column(name = "version")
+ private Integer version;
+
+ @OneToOne(mappedBy = "wcLoanTransaction", cascade = CascadeType.ALL, orphanRemoval = true)
+ private WorkingCapitalLoanTransactionAllocation allocation;
+
+ protected WorkingCapitalLoanTransaction() {}
+
+ public LoanTransactionType getTypeOf() {
+ return transactionType;
+ }
+
+ public static WorkingCapitalLoanTransaction disbursement(final WorkingCapitalLoan loan, final BigDecimal amount,
+ final WorkingCapitalLoanTransactionPaymentDetail paymentDetail, final LocalDate disbursementDate, final ExternalId externalId) {
+ final WorkingCapitalLoanTransaction txn = new WorkingCapitalLoanTransaction();
+ txn.wcLoan = loan;
+ txn.transactionType = LoanTransactionType.DISBURSEMENT;
+ txn.dateOf = disbursementDate;
+ txn.submittedOnDate = disbursementDate;
+ txn.transactionAmount = amount;
+ txn.paymentDetail = paymentDetail;
+ txn.externalId = externalId != null ? externalId : ExternalId.empty();
+ txn.reversed = false;
+ txn.reversalExternalId = null;
+ txn.reversedOnDate = null;
+ return txn;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionAllocation.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionAllocation.java
new file mode 100644
index 00000000000..de117923a64
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionAllocation.java
@@ -0,0 +1,71 @@
+/**
+ * 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.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import jakarta.persistence.Version;
+import java.math.BigDecimal;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Entity
+@Table(name = "m_wc_loan_transaction_allocation", uniqueConstraints = {
+ @UniqueConstraint(columnNames = { "wc_loan_transaction_id" }, name = "uq_m_wc_loan_transaction_allocation_transaction_id") })
+@Getter
+public class WorkingCapitalLoanTransactionAllocation extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @OneToOne(optional = false, fetch = FetchType.LAZY)
+ @JoinColumn(name = "wc_loan_transaction_id", nullable = false, unique = true)
+ private WorkingCapitalLoanTransaction wcLoanTransaction;
+
+ @Column(name = "principal_portion_derived", scale = 6, precision = 19)
+ @Setter
+ private BigDecimal principalPortion;
+
+ @Column(name = "fee_charges_portion_derived", scale = 6, precision = 19)
+ @Setter
+ private BigDecimal feeChargesPortion;
+
+ @Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19)
+ @Setter
+ private BigDecimal penaltyChargesPortion;
+
+ @Version
+ @Column(name = "version")
+ private Integer version;
+
+ protected WorkingCapitalLoanTransactionAllocation() {}
+
+ public static WorkingCapitalLoanTransactionAllocation forDisbursement(final WorkingCapitalLoanTransaction transaction,
+ final BigDecimal principalAmount) {
+ final WorkingCapitalLoanTransactionAllocation allocation = new WorkingCapitalLoanTransactionAllocation();
+ allocation.wcLoanTransaction = transaction;
+ allocation.principalPortion = principalAmount != null ? principalAmount : BigDecimal.ZERO;
+ allocation.feeChargesPortion = BigDecimal.ZERO;
+ allocation.penaltyChargesPortion = BigDecimal.ZERO;
+ return allocation;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionPaymentDetail.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionPaymentDetail.java
new file mode 100644
index 00000000000..99a66ab5d2e
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanTransactionPaymentDetail.java
@@ -0,0 +1,59 @@
+/**
+ * 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.Entity;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_wc_loan_transaction_payment_detail")
+@Getter
+public class WorkingCapitalLoanTransactionPaymentDetail extends AbstractPersistableCustom {
+
+ @Column(name = "account_number", length = 50)
+ private String accountNumber;
+
+ @Column(name = "check_number", length = 50)
+ private String checkNumber;
+
+ @Column(name = "routing_code", length = 50)
+ private String routingCode;
+
+ @Column(name = "receipt_number", length = 50)
+ private String receiptNumber;
+
+ @Column(name = "bank_number", length = 50)
+ private String bankNumber;
+
+ protected WorkingCapitalLoanTransactionPaymentDetail() {}
+
+ public static WorkingCapitalLoanTransactionPaymentDetail of(final String accountNumber, final String checkNumber,
+ final String routingCode, final String receiptNumber, final String bankNumber) {
+ final WorkingCapitalLoanTransactionPaymentDetail d = new WorkingCapitalLoanTransactionPaymentDetail();
+ d.accountNumber = accountNumber;
+ d.checkNumber = checkNumber;
+ d.routingCode = routingCode;
+ d.receiptNumber = receiptNumber;
+ d.bankNumber = bankNumber;
+ return d;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/WorkingCapitalLoanTransactionNotFoundException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/WorkingCapitalLoanTransactionNotFoundException.java
new file mode 100644
index 00000000000..070715bd88a
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/WorkingCapitalLoanTransactionNotFoundException.java
@@ -0,0 +1,39 @@
+/**
+ * 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.exception;
+
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * Thrown when a Working Capital Loan transaction is not found.
+ */
+public class WorkingCapitalLoanTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public WorkingCapitalLoanTransactionNotFoundException(final Long transactionId, final Long loanId) {
+ super("error.msg.wc.loan.transaction.not.found", "Working Capital Loan transaction with identifier " + transactionId
+ + " does not exist for loan with identifier " + loanId + ".", transactionId, loanId);
+ }
+
+ public WorkingCapitalLoanTransactionNotFoundException(final ExternalId transactionExternalId) {
+ super("error.msg.wc.loan.transaction.not.found",
+ "Working Capital Loan transaction with external identifier " + transactionExternalId.getValue() + " does not exist",
+ transactionExternalId.getValue());
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/ApproveWorkingCapitalLoanCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/ApproveWorkingCapitalLoanCommandHandler.java
new file mode 100644
index 00000000000..ca08d912181
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/ApproveWorkingCapitalLoanCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * 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.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.WorkingCapitalLoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "WORKINGCAPITALLOAN", action = "APPROVE")
+public class ApproveWorkingCapitalLoanCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.approveApplication(command.entityId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/DisburseWorkingCapitalLoanCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/DisburseWorkingCapitalLoanCommandHandler.java
new file mode 100644
index 00000000000..015794b9489
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/DisburseWorkingCapitalLoanCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * 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.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.WorkingCapitalLoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "WORKINGCAPITALLOAN", action = "DISBURSE")
+public class DisburseWorkingCapitalLoanCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.disburseLoan(command.entityId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/RejectWorkingCapitalLoanCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/RejectWorkingCapitalLoanCommandHandler.java
new file mode 100644
index 00000000000..4573053f9c9
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/RejectWorkingCapitalLoanCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * 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.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.WorkingCapitalLoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "WORKINGCAPITALLOAN", action = "REJECT")
+public class RejectWorkingCapitalLoanCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.rejectApplication(command.entityId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoApproveWorkingCapitalLoanCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoApproveWorkingCapitalLoanCommandHandler.java
new file mode 100644
index 00000000000..d1bd9d0162c
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoApproveWorkingCapitalLoanCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * 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.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.WorkingCapitalLoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "WORKINGCAPITALLOAN", action = "APPROVALUNDO")
+public class UndoApproveWorkingCapitalLoanCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.undoApplicationApproval(command.entityId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoDisburseWorkingCapitalLoanCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoDisburseWorkingCapitalLoanCommandHandler.java
new file mode 100644
index 00000000000..9ac28d5d8d3
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/handler/UndoDisburseWorkingCapitalLoanCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * 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.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.WorkingCapitalLoanWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "WORKINGCAPITALLOAN", action = "DISBURSALUNDO")
+public class UndoDisburseWorkingCapitalLoanCommandHandler implements NewCommandSourceHandler {
+
+ private final WorkingCapitalLoanWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.undoDisbursal(command.entityId(), command);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java
index c30aa67cc80..97b260cc48b 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java
@@ -46,7 +46,8 @@
import org.mapstruct.factory.Mappers;
@Mapper(config = MapstructMapperConfig.class, uses = { DelinquencyBucketMapper.class, WorkingCapitalLoanProductMapper.class,
- WorkingCapitalLoanBalanceMapper.class, WorkingCapitalLoanDisbursementDetailMapper.class })
+ WorkingCapitalLoanBalanceMapper.class, WorkingCapitalLoanDisbursementDetailMapper.class,
+ WorkingCapitalLoanTransactionMapper.class })
public interface WorkingCapitalLoanMapper {
@Mapping(target = "accountNo", source = "accountNumber")
@@ -66,6 +67,7 @@ public interface WorkingCapitalLoanMapper {
@Mapping(target = "paymentAllocation", source = "paymentAllocationRules", qualifiedByName = "paymentAllocationRulesToData")
@Mapping(target = "timeline", source = "loan", qualifiedByName = "timelineData")
@Mapping(target = "disbursementDetails", source = "disbursementDetails")
+ @Mapping(target = "transactions", source = "transactions")
WorkingCapitalLoanData toData(WorkingCapitalLoan loan);
List toDataList(List loans);
@@ -120,6 +122,8 @@ default LoanApplicationTimelineData timelineData(final WorkingCapitalLoan loan)
: loan.getDisbursementDetails().getFirst().getExpectedDisbursementDate();
timelineData.setExpectedDisbursementDate(expectedDisbursementDate);
timelineData.setSubmittedOnDate(loan.getSubmittedOnDate());
+ timelineData.setExpectedMaturityDate(loan.getExpectedMaturityDate());
+ timelineData.setActualMaturityDate(loan.getMaturedOnDate());
if (loan.getApprovedBy() != null) {
timelineData.setApprovedByUsername(loan.getApprovedBy().getUsername());
timelineData.setApprovedByFirstname(loan.getApprovedBy().getFirstname());
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java
new file mode 100644
index 00000000000..d5017da9bd9
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java
@@ -0,0 +1,60 @@
+/**
+ * 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.mapper;
+
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionPaymentDetailData;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionPaymentDetail;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Named;
+
+@Mapper(config = MapstructMapperConfig.class)
+public interface WorkingCapitalLoanTransactionMapper {
+
+ @Mapping(target = "type", source = "transactionType", qualifiedByName = "loanTransactionTypeToEnumData")
+ @Mapping(target = "paymentDetailData", source = "paymentDetail", qualifiedByName = "paymentDetailToData")
+ @Mapping(target = "transactionDate", source = "dateOf")
+ @Mapping(target = "principalPortion", source = "allocation.principalPortion")
+ @Mapping(target = "feeChargesPortion", source = "allocation.feeChargesPortion")
+ @Mapping(target = "penaltyChargesPortion", source = "allocation.penaltyChargesPortion")
+ WorkingCapitalLoanTransactionData toData(WorkingCapitalLoanTransaction transaction);
+
+ @Named("loanTransactionTypeToEnumData")
+ default LoanTransactionEnumData loanTransactionTypeToEnumData(final LoanTransactionType type) {
+ return type == null ? null : LoanEnumerations.transactionType(type);
+ }
+
+ @Named("paymentDetailToData")
+ default WorkingCapitalLoanTransactionPaymentDetailData paymentDetailToData(
+ final WorkingCapitalLoanTransactionPaymentDetail paymentDetail) {
+ if (paymentDetail == null) {
+ return null;
+ }
+ return WorkingCapitalLoanTransactionPaymentDetailData.builder().id(paymentDetail.getId())
+ .accountNumber(paymentDetail.getAccountNumber()).checkNumber(paymentDetail.getCheckNumber())
+ .routingCode(paymentDetail.getRoutingCode()).receiptNumber(paymentDetail.getReceiptNumber())
+ .bankNumber(paymentDetail.getBankNumber()).build();
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanBalanceRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanBalanceRepository.java
new file mode 100644
index 00000000000..cf5e11bd301
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanBalanceRepository.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.repository;
+
+import java.util.Optional;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanBalance;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface WorkingCapitalLoanBalanceRepository extends JpaRepository {
+
+ Optional findByWcLoan_Id(Long wcLoanId);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanRepository.java
index 9b68d08bfdd..d8056bb6d0e 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanRepository.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanRepository.java
@@ -48,6 +48,8 @@ public interface WorkingCapitalLoanRepository extends JpaRepository findByExternalIdWithDetails(@Param("externalId") ExternalId externalId);
@@ -70,6 +74,8 @@ public interface WorkingCapitalLoanRepository extends JpaRepository findByIdInWithFullDetails(@Param("ids") List ids);
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionAllocationRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionAllocationRepository.java
new file mode 100644
index 00000000000..ee2d1370a16
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionAllocationRepository.java
@@ -0,0 +1,24 @@
+/**
+ * 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.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionAllocation;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface WorkingCapitalLoanTransactionAllocationRepository extends JpaRepository {}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionPaymentDetailRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionPaymentDetailRepository.java
new file mode 100644
index 00000000000..0e37197bdb3
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionPaymentDetailRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionPaymentDetail;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface WorkingCapitalLoanTransactionPaymentDetailRepository
+ extends JpaRepository {}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionRepository.java
new file mode 100644
index 00000000000..7429dd17246
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanTransactionRepository.java
@@ -0,0 +1,40 @@
+/**
+ * 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 java.util.List;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface WorkingCapitalLoanTransactionRepository extends JpaRepository {
+
+ List findByWcLoan_IdOrderByDateOfAscIdAsc(Long wcLoanId);
+
+ Page findByWcLoan_IdOrderByDateOfAscIdAsc(Long wcLoanId, Pageable pageable);
+
+ Optional findByIdAndWcLoan_Id(Long id, Long wcLoanId);
+
+ Optional findByWcLoan_IdAndExternalId(Long wcLoanId, ExternalId externalId);
+
+ boolean existsByExternalId(ExternalId externalId);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java
new file mode 100644
index 00000000000..f2a88a1cd52
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java
@@ -0,0 +1,359 @@
+/**
+ * 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.JsonObject;
+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.domain.ExternalId;
+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.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
+import org.apache.fineract.portfolio.loanaccount.domain.ExpectedDisbursementDateValidator;
+import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanTransactionRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class WorkingCapitalLoanDataValidator {
+
+ private final FromJsonHelper fromApiJsonHelper;
+ private final ExpectedDisbursementDateValidator expectedDisbursementDateValidator;
+ private final WorkingCapitalLoanTransactionRepository transactionRepository;
+
+ // Per requirement: only principal, discount, approved date, expected disbursement date, and notes
+ private static final Set APPROVAL_SUPPORTED_PARAMETERS = new HashSet<>(
+ Arrays.asList("locale", "dateFormat", WorkingCapitalLoanConstants.approvedOnDateParamName,
+ WorkingCapitalLoanConstants.approvedLoanAmountParamName, WorkingCapitalLoanConstants.expectedDisbursementDateParamName,
+ WorkingCapitalLoanConstants.discountAmountParamName, WorkingCapitalLoanConstants.noteParamName));
+
+ private static final Set REJECTION_SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("locale", "dateFormat",
+ WorkingCapitalLoanConstants.rejectedOnDateParamName, WorkingCapitalLoanConstants.noteParamName));
+
+ private static final Set UNDO_APPROVAL_SUPPORTED_PARAMETERS = new HashSet<>(
+ Arrays.asList("locale", "dateFormat", WorkingCapitalLoanConstants.noteParamName));
+
+ private static final Set DISBURSAL_SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("locale", "dateFormat",
+ WorkingCapitalLoanConstants.actualDisbursementDateParamName, WorkingCapitalLoanConstants.transactionAmountParamName,
+ WorkingCapitalLoanConstants.discountAmountParamName, WorkingCapitalLoanConstants.noteParamName,
+ WorkingCapitalLoanConstants.paymentDetailsParamName, WorkingCapitalLoanConstants.externalIdParameterName));
+
+ private static final Set PAYMENT_DETAILS_SUPPORTED_PARAMETERS = new HashSet<>(
+ Arrays.asList(WorkingCapitalLoanConstants.paymentTypeIdParamName, WorkingCapitalLoanConstants.accountNumberParamName,
+ WorkingCapitalLoanConstants.checkNumberParamName, WorkingCapitalLoanConstants.routingCodeParamName,
+ WorkingCapitalLoanConstants.receiptNumberParamName, WorkingCapitalLoanConstants.bankNumberParamName));
+
+ private static final Set UNDO_DISBURSAL_SUPPORTED_PARAMETERS = new HashSet<>(
+ Arrays.asList("locale", "dateFormat", WorkingCapitalLoanConstants.noteParamName));
+
+ private static final int NOTE_MAX_LENGTH = 1000;
+ private static final int EXTERNAL_ID_MAX_LENGTH = 100;
+ private static final int PAYMENT_DETAIL_STRING_MAX_LENGTH = 50;
+
+ public void validateApproval(final String json, final WorkingCapitalLoan loan) {
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeOfMap = new TypeToken