Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdResponse;
import org.apache.fineract.client.models.WorkingCapitalLoanCommandTemplateData;
import org.apache.fineract.client.models.WorkingCapitalLoanPeriodPaymentRateChangeData;
import org.apache.fineract.client.models.WorkingCapitalLoanTransactionTemplateResponse;
import org.apache.fineract.test.api.FineractClientConfiguration;
import org.apache.fineract.test.data.FundId;
import org.apache.fineract.test.data.LoanStatus;
Expand Down Expand Up @@ -3338,16 +3339,14 @@ public void makeWorkingCapitalLoanRepaymentWithPaymentDetails(final String trans
@Then("Admin closes the Working Capital loan with a full repayment on {string}")
public void closeWorkingCapitalLoanWithFullRepayment(final String transactionDate) {
final Long loanId = getCreatedLoanId();
final GetWorkingCapitalLoansLoanIdResponse loanDetails = ok(
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
Assertions.assertNotNull(loanDetails.getBalance());
Assertions.assertNotNull(loanDetails.getBalance().getTotalOutstanding());
final BigDecimal totalOutstanding = loanDetails.getBalance().getTotalOutstanding();
WorkingCapitalLoanTransactionTemplateResponse templateResponse = ok(() -> fineractClient.workingCapitalLoanTransactions()
.getWorkingCapitalLoanTransactionTemplateById(loanId, "prepayLoan", DATE_FORMAT, transactionDate, "en"));
Assertions.assertNotNull(templateResponse);
final BigDecimal transactionAmount = templateResponse.getTransactionAmount();
final PostWorkingCapitalLoanTransactionsRequest repaymentRequest = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest().transactionDate(transactionDate).transactionAmount(totalOutstanding);
.defaultWorkingCapitalLoanRepaymentRequest().transactionDate(transactionDate).transactionAmount(transactionAmount);
final PostWorkingCapitalLoanTransactionsResponse response = executeRepaymentLikeById(loanId, "repayment", repaymentRequest);
Assertions.assertNotNull(loanDetails.getBalance());
validateRepaymentResponse(response, totalOutstanding.doubleValue(), transactionDate, loanId);
validateRepaymentResponse(response, transactionAmount.doubleValue(), transactionDate, loanId);
}

@Then("Admin closes the Working Capital loan with all obligations met with a full repayment on {string}")
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,26 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
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.api.DateParam;
import org.apache.fineract.infrastructure.core.api.jersey.Pagination;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.DateFormat;
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.DateUtils;
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.WorkingCapitalLoanCommandTemplateData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction;
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanTransactionNotFoundException;
Expand Down Expand Up @@ -287,6 +293,55 @@ public CommandProcessingResult executeWorkingCapitalLoanTransactionCommandByLoan
return executeWorkingCapitalLoanTransactionCommand(loanId, null, null, transactionExternalId, command, apiRequestBodyAsJson);
}

@GET
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "getWorkingCapitalLoanTransactionTemplateById", summary = "Get Working Capital Loan transaction template by loan id", description = "Supported command query parameter: prepayLoan.")
@Path("{loanId}/transactions/template")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.WorkingCapitalLoanTransactionTemplateResponse.class))) })
public WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplateById(
@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) {

return getWorkingCapitalLoanTransactionTemplate(commandParam, loanId, null, locale, rawDateFormat, transactionDateParam);
}

@GET
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "getWorkingCapitalLoanTransactionTemplateByExternalId", summary = "Get Working Capital Loan transaction template by loan external id", description = "Supported command query parameter: prepayLoan.")
@Path("external-id/{loanExternalId}/transactions/template")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.WorkingCapitalLoanTransactionTemplateResponse.class))) })
public WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplateByExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) {
return getWorkingCapitalLoanTransactionTemplate(commandParam, null, loanExternalId, locale, rawDateFormat, transactionDateParam);
}

private WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplate(String commandParam, Long loanId,
String externalId, String locale, String rawDateFormat, DateParam transactionDateParam) {
final Long resolvedLoanId = resolveLoanId(loanId, externalId);
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
if (CommandParameterUtil.is(commandParam, "prepayLoan")) {
LocalDate transactionDate;
if (transactionDateParam == null) {
transactionDate = DateUtils.getBusinessLocalDate();
} else {
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
}
return this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(resolvedLoanId, transactionDate);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
}

@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,23 @@ private ExecuteWorkingCapitalLoanTransactionCommandResponse() {}
@Schema(example = "repayment-ext-001")
public String resourceExternalId;
}

public static final class WorkingCapitalLoanTransactionTemplateResponse {

@Schema(example = "1")
public Long id;
@Schema(description = "Transaction type")
public LoanTransactionEnumData type;
@Schema(example = "[2024, 2, 1]")
public LocalDate transactionDate;
@Schema(example = "10000.00")
public BigDecimal transactionAmount;

@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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* 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.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WorkingCapitalLoanTransactionTemplateData implements Serializable {

private Long wcLoanId;
private CurrencyData currency;

private LocalDate transactionDate;
private BigDecimal transactionAmount;
private LoanTransactionEnumData type;

private BigDecimal principalPortion;
private BigDecimal feeChargesPortion;
private BigDecimal penaltyChargesPortion;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
*/
package org.apache.fineract.portfolio.workingcapitalloan.service;

import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.accountdetails.data.WorkingCapitalLoanAccountSummaryData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTemplateData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Expand Down Expand Up @@ -58,4 +60,6 @@ public interface WorkingCapitalLoanApplicationReadPlatformService {
* Checks if a Working Capital Loan exists with the given Id
*/
boolean existsByLoanId(Long loanId);

WorkingCapitalLoanTransactionTemplateData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate transactionDate);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -44,10 +45,13 @@
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentType;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanCollectionData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTemplateData;
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodFrequencyType;
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
Expand Down Expand Up @@ -274,4 +278,15 @@ public List<WorkingCapitalLoanAccountSummaryData> retrieveLoanSummaryData(final
public boolean existsByLoanId(Long loanId) {
return this.repository.existsById(loanId);
}

@Override
public WorkingCapitalLoanTransactionTemplateData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate transactionDate) {
WorkingCapitalLoan workingCapitalLoan = repository.getReferenceById(loanId);
return WorkingCapitalLoanTransactionTemplateData.builder().wcLoanId(loanId).currency(workingCapitalLoan.getCurrency().toData())
.transactionDate(transactionDate).transactionAmount(workingCapitalLoan.getBalance().getTotalOutstanding())
.type(LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT))
.principalPortion(workingCapitalLoan.getBalance().getPrincipalOutstanding())
.feeChargesPortion(workingCapitalLoan.getBalance().getFeeOutstanding())
.penaltyChargesPortion(workingCapitalLoan.getBalance().getPenaltyOutstanding()).build();
}
}