This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 8dcb711e4 [FINERACT-1760] Client transactions APIs with external
client ID
8dcb711e4 is described below
commit 8dcb711e4449d569c8c544802f0e5eef1087c8d0
Author: taskain7 <[email protected]>
AuthorDate: Mon Jul 10 15:51:29 2023 +0200
[FINERACT-1760] Client transactions APIs with external client ID
---
.../client/api/ClientTransactionsApiResource.java | 116 ++++++++++++++++++---
.../client/ClientTransactionTest.java | 94 +++++++++++++++++
.../integrationtests/common/ClientHelper.java | 17 +++
3 files changed, 214 insertions(+), 13 deletions(-)
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<ClientTransactionData>
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<ClientTransactionData> 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<ClientTransactionData> 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" + "?"