Skip to content

Commit 2e88f6c

Browse files
committed
FINERACT-2455: WC - Transaction - Prepay Loan
1 parent 8d7260b commit 2e88f6c

7 files changed

Lines changed: 190 additions & 8 deletions

File tree

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdResponse;
9292
import org.apache.fineract.client.models.WorkingCapitalLoanCommandTemplateData;
9393
import org.apache.fineract.client.models.WorkingCapitalLoanPeriodPaymentRateChangeData;
94+
import org.apache.fineract.client.models.WorkingCapitalLoanTransactionTemplateResponse;
9495
import org.apache.fineract.test.api.FineractClientConfiguration;
9596
import org.apache.fineract.test.data.FundId;
9697
import org.apache.fineract.test.data.LoanStatus;
@@ -3338,16 +3339,14 @@ public void makeWorkingCapitalLoanRepaymentWithPaymentDetails(final String trans
33383339
@Then("Admin closes the Working Capital loan with a full repayment on {string}")
33393340
public void closeWorkingCapitalLoanWithFullRepayment(final String transactionDate) {
33403341
final Long loanId = getCreatedLoanId();
3341-
final GetWorkingCapitalLoansLoanIdResponse loanDetails = ok(
3342-
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
3343-
Assertions.assertNotNull(loanDetails.getBalance());
3344-
Assertions.assertNotNull(loanDetails.getBalance().getTotalOutstanding());
3345-
final BigDecimal totalOutstanding = loanDetails.getBalance().getTotalOutstanding();
3342+
WorkingCapitalLoanTransactionTemplateResponse templateResponse = ok(() -> fineractClient.workingCapitalLoanTransactions()
3343+
.getWorkingCapitalLoanTransactionTemplateById(loanId, "prepayLoan", DATE_FORMAT, transactionDate, "en"));
3344+
Assertions.assertNotNull(templateResponse);
3345+
final BigDecimal transactionAmount = templateResponse.getTransactionAmount();
33463346
final PostWorkingCapitalLoanTransactionsRequest repaymentRequest = workingCapitalProductRequestFactory
3347-
.defaultWorkingCapitalLoanRepaymentRequest().transactionDate(transactionDate).transactionAmount(totalOutstanding);
3347+
.defaultWorkingCapitalLoanRepaymentRequest().transactionDate(transactionDate).transactionAmount(transactionAmount);
33483348
final PostWorkingCapitalLoanTransactionsResponse response = executeRepaymentLikeById(loanId, "repayment", repaymentRequest);
3349-
Assertions.assertNotNull(loanDetails.getBalance());
3350-
validateRepaymentResponse(response, totalOutstanding.doubleValue(), transactionDate, loanId);
3349+
validateRepaymentResponse(response, transactionAmount.doubleValue(), transactionDate, loanId);
33513350
}
33523351

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

fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanRepayment.feature

Lines changed: 40 additions & 0 deletions
Large diffs are not rendered by default.

fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResource.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,26 @@
4141
import jakarta.ws.rs.core.Context;
4242
import jakarta.ws.rs.core.MediaType;
4343
import jakarta.ws.rs.core.UriInfo;
44+
import java.time.LocalDate;
4445
import lombok.RequiredArgsConstructor;
46+
import org.apache.commons.lang3.StringUtils;
4547
import org.apache.fineract.commands.domain.CommandWrapper;
4648
import org.apache.fineract.commands.service.CommandWrapperBuilder;
4749
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
50+
import org.apache.fineract.infrastructure.core.api.DateParam;
4851
import org.apache.fineract.infrastructure.core.api.jersey.Pagination;
4952
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
53+
import org.apache.fineract.infrastructure.core.data.DateFormat;
5054
import org.apache.fineract.infrastructure.core.domain.ExternalId;
5155
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
5256
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
57+
import org.apache.fineract.infrastructure.core.service.DateUtils;
5358
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
5459
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
5560
import org.apache.fineract.portfolio.workingcapitalloan.WorkingCapitalLoanConstants;
5661
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanCommandTemplateData;
5762
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData;
63+
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
5864
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction;
5965
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
6066
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanTransactionNotFoundException;
@@ -287,6 +293,55 @@ public CommandProcessingResult executeWorkingCapitalLoanTransactionCommandByLoan
287293
return executeWorkingCapitalLoanTransactionCommand(loanId, null, null, transactionExternalId, command, apiRequestBodyAsJson);
288294
}
289295

296+
@GET
297+
@Produces({ MediaType.APPLICATION_JSON })
298+
@Operation(operationId = "getWorkingCapitalLoanTransactionTemplateById", summary = "Get Working Capital Loan transaction template by loan id", description = "Supported command query parameter: prepayLoan.")
299+
@Path("{loanId}/transactions/template")
300+
@ApiResponses({
301+
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.WorkingCapitalLoanTransactionTemplateResponse.class))) })
302+
public WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplateById(
303+
@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
304+
@QueryParam("command") @Parameter(description = "command") final String commandParam,
305+
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
306+
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
307+
@QueryParam("locale") @Parameter(description = "locale") final String locale) {
308+
309+
return getWorkingCapitalLoanTransactionTemplate(commandParam, loanId, null, locale, rawDateFormat, transactionDateParam);
310+
}
311+
312+
@GET
313+
@Produces({ MediaType.APPLICATION_JSON })
314+
@Operation(operationId = "getWorkingCapitalLoanTransactionTemplateByExternalId", summary = "Get Working Capital Loan transaction template by loan external id", description = "Supported command query parameter: prepayLoan.")
315+
@Path("external-id/{loanExternalId}/transactions/template")
316+
@ApiResponses({
317+
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanTransactionsApiResourceSwagger.WorkingCapitalLoanTransactionTemplateResponse.class))) })
318+
public WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplateByExternalId(
319+
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
320+
@QueryParam("command") @Parameter(description = "command") final String commandParam,
321+
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
322+
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
323+
@QueryParam("locale") @Parameter(description = "locale") final String locale) {
324+
return getWorkingCapitalLoanTransactionTemplate(commandParam, null, loanExternalId, locale, rawDateFormat, transactionDateParam);
325+
}
326+
327+
private WorkingCapitalLoanTransactionTemplateData getWorkingCapitalLoanTransactionTemplate(String commandParam, Long loanId,
328+
String externalId, String locale, String rawDateFormat, DateParam transactionDateParam) {
329+
final Long resolvedLoanId = resolveLoanId(loanId, externalId);
330+
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
331+
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
332+
if (CommandParameterUtil.is(commandParam, "prepayLoan")) {
333+
LocalDate transactionDate;
334+
if (transactionDateParam == null) {
335+
transactionDate = DateUtils.getBusinessLocalDate();
336+
} else {
337+
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
338+
}
339+
return this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(resolvedLoanId, transactionDate);
340+
} else {
341+
throw new UnrecognizedQueryParamException("command", commandParam);
342+
}
343+
}
344+
290345
@POST
291346
@Consumes({ MediaType.APPLICATION_JSON })
292347
@Produces({ MediaType.APPLICATION_JSON })

fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanTransactionsApiResourceSwagger.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,23 @@ private ExecuteWorkingCapitalLoanTransactionCommandResponse() {}
264264
@Schema(example = "repayment-ext-001")
265265
public String resourceExternalId;
266266
}
267+
268+
public static final class WorkingCapitalLoanTransactionTemplateResponse {
269+
270+
@Schema(example = "1")
271+
public Long id;
272+
@Schema(description = "Transaction type")
273+
public LoanTransactionEnumData type;
274+
@Schema(example = "[2024, 2, 1]")
275+
public LocalDate transactionDate;
276+
@Schema(example = "10000.00")
277+
public BigDecimal transactionAmount;
278+
279+
@Schema(example = "10000.00", description = "Principal portion from allocation")
280+
public BigDecimal principalPortion;
281+
@Schema(example = "0.00", description = "Fee charges portion from allocation")
282+
public BigDecimal feeChargesPortion;
283+
@Schema(example = "0.00", description = "Penalty charges portion from allocation")
284+
public BigDecimal penaltyChargesPortion;
285+
}
267286
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fineract.portfolio.workingcapitalloan.data;
21+
22+
import java.io.Serializable;
23+
import java.math.BigDecimal;
24+
import java.time.LocalDate;
25+
import lombok.AllArgsConstructor;
26+
import lombok.Builder;
27+
import lombok.Getter;
28+
import lombok.NoArgsConstructor;
29+
import lombok.Setter;
30+
import org.apache.fineract.organisation.monetary.data.CurrencyData;
31+
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
32+
33+
@Getter
34+
@Setter
35+
@Builder
36+
@NoArgsConstructor
37+
@AllArgsConstructor
38+
public class WorkingCapitalLoanTransactionTemplateData implements Serializable {
39+
40+
private Long wcLoanId;
41+
private CurrencyData currency;
42+
43+
private LocalDate transactionDate;
44+
private BigDecimal transactionAmount;
45+
private LoanTransactionEnumData type;
46+
47+
private BigDecimal principalPortion;
48+
private BigDecimal feeChargesPortion;
49+
private BigDecimal penaltyChargesPortion;
50+
}

fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
*/
1919
package org.apache.fineract.portfolio.workingcapitalloan.service;
2020

21+
import java.time.LocalDate;
2122
import java.util.List;
2223
import org.apache.fineract.infrastructure.core.domain.ExternalId;
2324
import org.apache.fineract.portfolio.accountdetails.data.WorkingCapitalLoanAccountSummaryData;
2425
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanData;
2526
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTemplateData;
27+
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
2628
import org.springframework.data.domain.Page;
2729
import org.springframework.data.domain.Pageable;
2830

@@ -58,4 +60,6 @@ public interface WorkingCapitalLoanApplicationReadPlatformService {
5860
* Checks if a Working Capital Loan exists with the given Id
5961
*/
6062
boolean existsByLoanId(Long loanId);
63+
64+
WorkingCapitalLoanTransactionTemplateData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate transactionDate);
6165
}

fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import jakarta.persistence.criteria.Predicate;
2222
import java.math.BigDecimal;
2323
import java.math.MathContext;
24+
import java.time.LocalDate;
2425
import java.util.ArrayList;
2526
import java.util.Collection;
2627
import java.util.Collections;
@@ -44,10 +45,13 @@
4445
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
4546
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentType;
4647
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
48+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
4749
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
50+
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
4851
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanCollectionData;
4952
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanData;
5053
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTemplateData;
54+
import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionTemplateData;
5155
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
5256
import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodFrequencyType;
5357
import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
@@ -274,4 +278,15 @@ public List<WorkingCapitalLoanAccountSummaryData> retrieveLoanSummaryData(final
274278
public boolean existsByLoanId(Long loanId) {
275279
return this.repository.existsById(loanId);
276280
}
281+
282+
@Override
283+
public WorkingCapitalLoanTransactionTemplateData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate transactionDate) {
284+
WorkingCapitalLoan workingCapitalLoan = repository.getReferenceById(loanId);
285+
return WorkingCapitalLoanTransactionTemplateData.builder().wcLoanId(loanId).currency(workingCapitalLoan.getCurrency().toData())
286+
.transactionDate(transactionDate).transactionAmount(workingCapitalLoan.getBalance().getTotalOutstanding())
287+
.type(LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT))
288+
.principalPortion(workingCapitalLoan.getBalance().getPrincipalOutstanding())
289+
.feeChargesPortion(workingCapitalLoan.getBalance().getFeeOutstanding())
290+
.penaltyChargesPortion(workingCapitalLoan.getBalance().getPenaltyOutstanding()).build();
291+
}
277292
}

0 commit comments

Comments
 (0)