From 1899b517b4d48db58745678bd385faf4f853649f Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Wed, 23 Nov 2022 15:51:34 +0100 Subject: [PATCH] FINERACT-1724: Batch API support for Loan chargeback and fineract client adjustments --- .../command/CommandStrategyProvider.java | 2 +- .../AdjustTransactionCommandStrategy.java | 10 ++- .../api/LoansApiResourceSwagger.java | 4 ++ .../api/LoanProductsApiResourceSwagger.java | 4 ++ .../command/CommandStrategyProviderTest.java | 2 + .../AdjustTransactionCommandStrategyTest.java | 40 ++++++++++- .../integrationtests/BatchApiTest.java | 71 +++++++++++++++++++ .../integrationtests/common/BatchHelper.java | 37 ++++++++++ 8 files changed, 165 insertions(+), 5 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index 42ff0c5ae..cf52777b8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -94,7 +94,7 @@ public class CommandStrategyProvider { "getChargeByIdCommandStrategy"); commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\?(\\w+(\\=[\\w]+))").method("POST").build(), "createTransactionLoanCommandStrategy"); - commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\/\\d+").method("POST").build(), + commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\/\\d+(\\?command=[\\w]+)?").method("POST").build(), "adjustTransactionCommandStrategy"); commandStrategies.put(CommandContext.resource("clients\\/\\d+\\?command=activate").method("POST").build(), "activateClientCommandStrategy"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java index d5341d191..038a9064a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java @@ -59,7 +59,15 @@ public class AdjustTransactionCommandStrategy implements CommandStrategy { // Get the loan and transaction ids for use in loanTransactionsApiResource final List pathParameters = Splitter.on('/').splitToList(relativeUrl); Long loanId = Long.parseLong(pathParameters.get(1)); - Long transactionId = Long.parseLong(pathParameters.get(3)); + + final String transactionIdPathParameter = pathParameters.get(3); + Long transactionId; + if (transactionIdPathParameter.contains("?")) { + transactionId = Long.parseLong(pathParameters.get(3).substring(0, pathParameters.get(3).indexOf("?"))); + } else { + transactionId = Long.parseLong(pathParameters.get(3)); + } + Map queryParameters = CommandStrategyUtils.getQueryParameters(relativeUrl); String command = queryParameters.get("command"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 74ffdd8e4..5e5829594 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -976,6 +976,8 @@ final class LoansApiResourceSwagger { public GetDelinquencyRangesResponse delinquencyRange; @Schema(example = "false") public Boolean fraud; + @Schema(example = "250.000000") + public Double totalOverpaid; } @Schema(description = "GetLoansResponse") @@ -1049,6 +1051,8 @@ final class LoansApiResourceSwagger { public List disbursementData; @Schema(description = "Maximum allowed outstanding balance") public BigDecimal maxOutstandingLoanBalance; + @Schema(example = "[2011, 10, 20]") + public LocalDate repaymentsStartingFromDate; } @Schema(description = "PostLoansResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index d25930356..d16740cad 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -1175,6 +1175,10 @@ final class LoanProductsApiResourceSwagger { public GetLoanCharge charge; public GetLoanAccountingMappings.GetLoanIncomeFromFeeAccount incomeAccount; + @Schema(example = "10") + public Long chargeId; + @Schema(example = "39") + public Long incomeAccountId; } @Schema(example = "11") diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java index 5ce803bf2..507e615e9 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java @@ -88,6 +88,8 @@ public class CommandStrategyProviderTest { mock(CreateTransactionLoanCommandStrategy.class)), Arguments.of("loans/123/transactions/123", HttpMethod.POST, "adjustTransactionCommandStrategy", mock(AdjustTransactionCommandStrategy.class)), + Arguments.of("loans/123/transactions/123?command=chargeback", HttpMethod.POST, "adjustTransactionCommandStrategy", + mock(AdjustTransactionCommandStrategy.class)), Arguments.of("clients/456?command=activate", HttpMethod.POST, "activateClientCommandStrategy", mock(ActivateClientCommandStrategy.class)), Arguments.of("loans/123?command=approve", HttpMethod.POST, "approveLoanCommandStrategy", diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategyTest.java index 17fb6da2e..20dae9a60 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategyTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategyTest.java @@ -25,6 +25,7 @@ import static org.mockito.BDDMockito.given; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource; @@ -39,13 +40,13 @@ public class AdjustTransactionCommandStrategyTest { * Test {@link AdjustTransactionCommandStrategy#execute} happy path scenario. */ @Test - public void testExecuteSuccessScenario() { + public void testExecuteWithoutCommandSuccessScenario() { // given final TestContext testContext = new TestContext(); final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4)); final Long transactionId = Long.valueOf(RandomStringUtils.randomNumeric(4)); - final BatchRequest request = getBatchRequest(loanId, transactionId); + final BatchRequest request = getBatchRequest(loanId, transactionId, null); final String responseBody = "{\"officeId\":1,\"clientId\":107,\"loanId\":71,\"resourceId\":193,\"changes\"" + ":{\"transactionDate\":\"03 October 2022\",\"transactionAmount\":\"500\",\"locale\":\"en\",\"dateFormat\":" + "\"dd MMMM yyyy\",\"paymentTypeId\":\"\"}}"; @@ -63,6 +64,34 @@ public class AdjustTransactionCommandStrategyTest { assertEquals(response.getBody(), responseBody); } + /** + * Test {@link AdjustTransactionCommandStrategy#execute} happy path scenario. + */ + @Test + public void testExecuteWithCommandSuccessScenario() { + // given + final TestContext testContext = new TestContext(); + + final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final Long transactionId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final BatchRequest request = getBatchRequest(loanId, transactionId, "chargeback"); + final String responseBody = "{\"officeId\":1,\"clientId\":107,\"loanId\":71,\"resourceId\":193,\"changes\"" + + ":{\"transactionDate\":\"03 October 2022\",\"transactionAmount\":\"500\",\"locale\":\"en\",\"dateFormat\":" + + "\"dd MMMM yyyy\",\"paymentTypeId\":\"\"}}"; + + given(testContext.loanTransactionsApiResource.adjustLoanTransaction(eq(loanId), eq(transactionId), eq(request.getBody()), + eq("chargeback"))).willReturn(responseBody); + + // when + final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo); + + // then + assertEquals(response.getStatusCode(), HttpStatus.SC_OK); + assertEquals(response.getRequestId(), request.getRequestId()); + assertEquals(response.getHeaders(), request.getHeaders()); + assertEquals(response.getBody(), responseBody); + } + /** * Creates and returns a request with the given loan id and transaction id. * @@ -70,15 +99,20 @@ public class AdjustTransactionCommandStrategyTest { * the loan id * @param transactionId * the transaction id + * @param transactionCommand + * the optional transaction command * @return BatchRequest */ - private BatchRequest getBatchRequest(final Long loanId, final Long transactionId) { + private BatchRequest getBatchRequest(final Long loanId, final Long transactionId, final String transactionCommand) { final BatchRequest br = new BatchRequest(); String relativeUrl = String.format("loans/%s/transactions/%s", loanId, transactionId); br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5))); br.setRelativeUrl(relativeUrl); + if (StringUtils.isNotBlank(transactionCommand)) { + br.setRelativeUrl(br.getRelativeUrl() + String.format("?command=%s", transactionCommand)); + } br.setMethod(HttpMethod.POST); br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5))); br.setBody("{\"locale\":\"en\",\"dateFormat\":\"dd MMMM yyyy\",\"transactionDate\":\"03 October 2022\",\"transactionAmount\":500}"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java index afb0505fe..f81774280 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java @@ -1391,6 +1391,77 @@ public class BatchApiTest { Assertions.assertEquals(date, reversedOnDate); } + /** + * Test for the successful repayment chargeback transaction. A '200' status code is expected on successful + * responses. + * + * @see AdjustTransactionCommandStrategy + */ + @Test + public void shouldReturnOkStatusForBatchRepaymentChargeback() { + + final String loanProductJSON = new LoanProductTestBuilder() // + .withPrincipal("10000000.00") // + .withNumberOfRepayments("24") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("2") // + .withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // + .withInterestTypeAsDecliningBalance() // + .currencyDetails("0", "100").build(null); + + final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON); + + final Long applyLoanRequestId = 5730L; + final Long approveLoanRequestId = 5731L; + final Long disburseLoanRequestId = 5732L; + final Long repayLoanRequestId = 5733L; + final Long repayReversalRequestId = 5734L; + final Long getLoanRequestId = 5735L; + + // Create client + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientId); + + // Create an apply loan request + final BatchRequest applyLoanRequest = BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId, productId); + + // Create an approve loan request + final BatchRequest approveLoanRequest = BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId); + + // Create a disburse loan request + final BatchRequest disburseLoanRequest = BatchHelper.disburseLoanRequest(disburseLoanRequestId, approveLoanRequestId); + + // Create a repayment request + final BatchRequest repaymentRequest = BatchHelper.repayLoanRequest(repayLoanRequestId, disburseLoanRequestId, "500"); + + // Create a repayment chargeback request + final BatchRequest repaymentChargebackRequest = BatchHelper.createChargebackTransactionRequest(repayReversalRequestId, + repayLoanRequestId, "500"); + + // Get loan transactions request + final BatchRequest getLoanTransactionsRequest = BatchHelper.getLoanByIdRequest(getLoanRequestId, applyLoanRequestId, + "associations=transactions"); + + final List batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, repaymentRequest, + repaymentChargebackRequest, getLoanTransactionsRequest); + + final List responses = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec, + BatchHelper.toJsonString(batchRequests)); + + final FromJsonHelper jsonHelper = new FromJsonHelper(); + final JsonObject repayment = jsonHelper.parse(responses.get(5).getBody()).getAsJsonObject().get("transactions").getAsJsonArray() + .get(2).getAsJsonObject(); + + Assertions.assertEquals(HttpStatus.SC_OK, (long) responses.get(4).getStatusCode(), + "Verify Status Code 200 for repayment chargeback"); + Assertions.assertEquals("Repayment", repayment.get("type").getAsJsonObject().get("value").getAsString()); + final JsonArray transactionRelations = repayment.get("transactionRelations").getAsJsonArray(); + Assertions.assertEquals(1, transactionRelations.size()); + Assertions.assertEquals("CHARGEBACK", transactionRelations.get(0).getAsJsonObject().get("relationType").getAsString()); + } + /** * Tests successful run of batch goodwill credit reversal for loans. A '200' status code is expected on successful * responses. diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java index 50f373570..ca54486fb 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java @@ -883,6 +883,19 @@ public final class BatchHelper { return br; } + /** + * Creates and returns a batch request to create an adjust transaction request. + * + * @param requestId + * the request ID + * @param reference + * the reference + * @param amount + * the amount + * @param date + * the date + * @return the {@link BatchRequest} + */ public static BatchRequest createAdjustTransactionRequest(final Long requestId, final Long reference, final String amount, final LocalDate date) { final BatchRequest br = new BatchRequest(); @@ -897,6 +910,30 @@ public final class BatchHelper { dateString, amount)); return br; + } + + /** + * Creates and returns a batch request to create a chargeback transaction request. + * + * @param requestId + * the request ID + * @param reference + * the reference + * @param amount + * the amount + * @return the {@link BatchRequest} + */ + public static BatchRequest createChargebackTransactionRequest(final Long requestId, final Long reference, final String amount) { + + final BatchRequest br = new BatchRequest(); + + br.setRequestId(requestId); + br.setReference(reference); + br.setRelativeUrl("loans/$.loanId/transactions/$.resourceId?command=chargeback"); + br.setMethod("POST"); + br.setBody(String.format("{\"locale\": \"en\", \"transactionAmount\": %s, \"paymentTypeId\": 2}", amount)); + + return br; } }