From 8dcb711e4449d569c8c544802f0e5eef1087c8d0 Mon Sep 17 00:00:00 2001 From: taskain7 Date: Mon, 10 Jul 2023 15:51:29 +0200 Subject: [PATCH] [FINERACT-1760] Client transactions APIs with external client ID --- .../api/ClientTransactionsApiResource.java | 116 ++++++++++++++++-- .../client/ClientTransactionTest.java | 94 ++++++++++++++ .../integrationtests/common/ClientHelper.java | 17 +++ 3 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTransactionTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java index 6126f0892..f70e8c1a3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java @@ -35,6 +35,7 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.UriInfo; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.commands.domain.CommandWrapper; @@ -45,14 +46,17 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.client.data.ClientTransactionData; +import org.apache.fineract.portfolio.client.exception.ClientNotFoundException; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.portfolio.client.service.ClientTransactionReadPlatformService; import org.springframework.stereotype.Component; -@Path("/v1/clients/{clientId}/transactions") +@Path("/v1/clients") @Component @Tag(name = "Client Transaction", description = "Client Transactions refer to transactions made directly against a Client's internal account. Currently, these transactions are only created as a result of charge payments/waivers. You are allowed to undo a transaction, however you cannot explicitly create one. ") @RequiredArgsConstructor @@ -63,8 +67,10 @@ public class ClientTransactionsApiResource { private final DefaultToApiJsonSerializer toApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ClientReadPlatformService clientReadPlatformService; @GET + @Path("{clientId}/transactions") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "List Client Transactions", description = "The list capability of client transaction can support pagination." @@ -76,16 +82,11 @@ public class ClientTransactionsApiResource { @QueryParam("limit") @Parameter(description = "limit") final Integer limit) { context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - SearchParameters searchParameters = SearchParameters.forPagination(offset, limit); - final Page clientTransactions = clientTransactionReadPlatformService.retrieveAllTransactions(clientId, - searchParameters); - - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return toApiJsonSerializer.serialize(settings, clientTransactions, ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS); + return getAllClientTransactions(clientId, uriInfo, offset, limit); } @GET - @Path("{transactionId}") + @Path("{clientId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Retrieve a Client Transaction", description = "Example Requests:\n" + "clients/1/transactions/1\n" + "\n" + "\n" @@ -98,14 +99,11 @@ public class ClientTransactionsApiResource { context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); - final ClientTransactionData clientTransaction = clientTransactionReadPlatformService.retrieveTransaction(clientId, transactionId); - - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return toApiJsonSerializer.serialize(settings, clientTransaction, ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS); + return getClientTransaction(clientId, transactionId, uriInfo); } @POST - @Path("{transactionId}") + @Path("{clientId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Undo a Client Transaction", description = "Undoes a Client Transaction") @@ -116,6 +114,94 @@ public class ClientTransactionsApiResource { @QueryParam("command") @Parameter(description = "command") final String commandParam, @Parameter(hidden = true) final String apiRequestBodyAsJson) { + return undoTransaction(clientId, transactionId, commandParam, apiRequestBodyAsJson); + } + + @GET + @Path("external-id/{clientExternalId}/transactions") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Client Transactions", description = "The list capability of client transaction can support pagination." + + "\n\n" + "Example Requests:\n\n" + "clients/189/transactions\n\n" + "clients/189/transactions?offset=10&limit=50") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.GetClientsClientIdTransactionsResponse.class))) }) + public String retrieveAllClientTransactions( + @PathParam("clientExternalId") @Parameter(description = "clientExternalId") final String clientExternalId, + @Context final UriInfo uriInfo, @QueryParam("offset") @Parameter(description = "offset") final Integer offset, + @QueryParam("limit") @Parameter(description = "limit") final Integer limit) { + context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); + + Long clientId = resolveClientId(clientExternalId); + if (Objects.isNull(clientId)) { + throw new ClientNotFoundException(); + } + + return getAllClientTransactions(clientId, uriInfo, offset, limit); + } + + @GET + @Path("external-id/{clientExternalId}/transactions/{transactionId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a Client Transaction", description = "Example Requests:\n" + "clients/1/transactions/1\n" + "\n" + "\n" + + "clients/1/transactions/1?fields=id,officeName") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.GetClientsClientIdTransactionsTransactionIdResponse.class))) }) + public String retrieveClientTransaction( + @PathParam("clientExternalId") @Parameter(description = "clientExternalId") final String clientExternalId, + @PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId, + @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME); + + Long clientId = resolveClientId(clientExternalId); + + if (Objects.isNull(clientId)) { + throw new ClientNotFoundException(); + } + + return getClientTransaction(clientId, transactionId, uriInfo); + } + + @POST + @Path("external-id/{clientExternalId}/transactions/{transactionId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Undo a Client Transaction", description = "Undoes a Client Transaction") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.PostClientsClientIdTransactionsTransactionIdResponse.class))) }) + public String undoClientTransaction( + @PathParam("clientExternalId") @Parameter(description = "clientExternalId") final String clientExternalId, + @PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + Long clientId = resolveClientId(clientExternalId); + if (Objects.isNull(clientId)) { + throw new ClientNotFoundException(); + } + + return undoTransaction(clientId, transactionId, commandParam, apiRequestBodyAsJson); + } + + private String getClientTransaction(Long clientId, Long transactionId, UriInfo uriInfo) { + final ClientTransactionData clientTransaction = clientTransactionReadPlatformService.retrieveTransaction(clientId, transactionId); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return toApiJsonSerializer.serialize(settings, clientTransaction, ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS); + } + + private String getAllClientTransactions(Long clientId, UriInfo uriInfo, Integer offset, Integer limit) { + + SearchParameters searchParameters = SearchParameters.forPagination(offset, limit); + final Page clientTransactions = clientTransactionReadPlatformService.retrieveAllTransactions(clientId, + searchParameters); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return toApiJsonSerializer.serialize(settings, clientTransactions, ClientApiConstants.CLIENT_TRANSACTION_RESPONSE_DATA_PARAMETERS); + } + + private String undoTransaction(Long clientId, Long transactionId, String commandParam, String apiRequestBodyAsJson) { if (is(commandParam, ClientApiConstants.CLIENT_TRANSACTION_COMMAND_UNDO)) { final CommandWrapper commandRequest = new CommandWrapperBuilder().undoClientTransaction(clientId, transactionId) .withJson(apiRequestBodyAsJson).build(); @@ -128,6 +214,10 @@ public class ClientTransactionsApiResource { } } + private Long resolveClientId(String clientExternalId) { + return clientReadPlatformService.retrieveClientIdByExternalId(ExternalIdFactory.produce(clientExternalId)); + } + private boolean is(final String commandParam, final String commandValue) { return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTransactionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTransactionTest.java new file mode 100644 index 000000000..925c5dbce --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTransactionTest.java @@ -0,0 +1,94 @@ +/** + * 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.integrationtests.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.UUID; +import org.apache.fineract.client.models.GetClientsClientIdTransactionsResponse; +import org.apache.fineract.client.models.GetClientsClientIdTransactionsTransactionIdResponse; +import org.apache.fineract.client.models.PostClientsClientIdTransactionsTransactionIdResponse; +import org.apache.fineract.client.models.PostClientsRequest; +import org.apache.fineract.client.models.PostClientsResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ClientTransactionTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private ClientHelper clientHelper; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + clientHelper = new ClientHelper(requestSpec, responseSpec); + } + + @Test + public void testClientTransactions() { + PostClientsRequest createClientRequest = ClientHelper.defaultClientCreationRequest(); + String clientExternalId = UUID.randomUUID().toString(); + createClientRequest.setExternalId(clientExternalId); + PostClientsResponse client = clientHelper.createClient(createClientRequest); + Long clientId = client.getClientId(); + assertNotNull(clientId); + + final Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper.getChargeSpecifiedDueDateJSON()); + Assertions.assertNotNull(chargeId); + final Integer clientChargeId1 = ClientHelper.addChargesForClient(requestSpec, responseSpec, clientId.intValue(), + ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011")); + Assertions.assertNotNull(clientChargeId1); + final String clientChargePaidTransactionId1 = ClientHelper.payChargesForClients(requestSpec, responseSpec, clientId.intValue(), + clientChargeId1, ClientHelper.getPayChargeJSON("25 AUGUST 2015", "10")); + assertNotNull(clientChargePaidTransactionId1); + + final Integer clientChargeId2 = ClientHelper.addChargesForClient(requestSpec, responseSpec, clientId.intValue(), + ClientHelper.getSpecifiedDueDateChargesClientAsJSON(chargeId.toString(), "29 October 2011")); + Assertions.assertNotNull(clientChargeId2); + final String clientChargePaidTransactionId2 = ClientHelper.payChargesForClients(requestSpec, responseSpec, clientId.intValue(), + clientChargeId2, ClientHelper.getPayChargeJSON("25 AUGUST 2015", "10")); + assertNotNull(clientChargePaidTransactionId2); + + GetClientsClientIdTransactionsResponse allClientTransactionsByExternalId = clientHelper + .getAllClientTransactionsByExternalId(clientExternalId); + assertEquals(2, allClientTransactionsByExternalId.getTotalFilteredRecords()); + + GetClientsClientIdTransactionsTransactionIdResponse clientTransactionByExternalId = clientHelper + .getClientTransactionByExternalId(clientExternalId, clientChargePaidTransactionId1); + assertEquals(Integer.parseInt(clientChargePaidTransactionId1), clientTransactionByExternalId.getId()); + + PostClientsClientIdTransactionsTransactionIdResponse undoTransactionResponse = clientHelper + .undoClientTransactionByExternalId(clientExternalId, clientChargePaidTransactionId2); + assertNotNull(undoTransactionResponse.getResourceId()); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java index 5ef262046..7d9fa2cab 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java @@ -44,12 +44,15 @@ import org.apache.fineract.client.models.GetClientClientIdAddressesResponse; import org.apache.fineract.client.models.GetClientTransferProposalDateResponse; import org.apache.fineract.client.models.GetClientsClientIdAccountsResponse; import org.apache.fineract.client.models.GetClientsClientIdResponse; +import org.apache.fineract.client.models.GetClientsClientIdTransactionsResponse; +import org.apache.fineract.client.models.GetClientsClientIdTransactionsTransactionIdResponse; import org.apache.fineract.client.models.GetObligeeData; import org.apache.fineract.client.models.PageClientSearchData; import org.apache.fineract.client.models.PagedRequestClientTextSearch; import org.apache.fineract.client.models.PostClientClientIdAddressesRequest; import org.apache.fineract.client.models.PostClientClientIdAddressesResponse; import org.apache.fineract.client.models.PostClientsClientIdResponse; +import org.apache.fineract.client.models.PostClientsClientIdTransactionsTransactionIdResponse; import org.apache.fineract.client.models.PostClientsRequest; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PutClientsClientIdResponse; @@ -786,6 +789,20 @@ public class ClientHelper extends IntegrationTest { return Utils.performServerGet(requestSpec, responseSpec, CHARGES_URL, "reversed"); } + public GetClientsClientIdTransactionsResponse getAllClientTransactionsByExternalId(final String externalId) { + return ok(fineract().clientTransactions.retrieveAllClientTransactions1(externalId, 0, 100)); + } + + public GetClientsClientIdTransactionsTransactionIdResponse getClientTransactionByExternalId(final String externalId, + final String transactionId) { + return ok(fineract().clientTransactions.retrieveClientTransaction1(externalId, Long.parseLong(transactionId))); + } + + public PostClientsClientIdTransactionsTransactionIdResponse undoClientTransactionByExternalId(final String externalId, + final String transactionId) { + return ok(fineract().clientTransactions.undoClientTransaction1(externalId, Long.parseLong(transactionId), "undo")); + } + public Workbook getClientEntityWorkbook(GlobalEntityType clientsEntity, String dateFormat) throws IOException { requestSpec.header(HttpHeaders.CONTENT_TYPE, "application/vnd.ms-excel"); byte[] byteArray = Utils.performGetBinaryResponse(requestSpec, responseSpec, CLIENT_URL + "/downloadtemplate" + "?"