diff --git a/build.gradle b/build.gradle index 831dc3761..87272d239 100644 --- a/build.gradle +++ b/build.gradle @@ -155,6 +155,7 @@ allprojects { "**/banner.txt", "**/build.gradle.mustache", "**/pom.mustache", + "**/avro/**/*.java", ]) strictCheck true } @@ -249,7 +250,8 @@ allprojects { "**/git.properties", ".mailmap", '**/images/diag-*.svg', - '**/*.avsc' + '**/*.avsc', + "**/generated/**/*MapperImpl.java" ] } } diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 06062d49c..7b2f30eaa 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -42,6 +42,7 @@ import org.apache.fineract.client.services.AdhocQueryApiApi; import org.apache.fineract.client.services.AuditsApi; import org.apache.fineract.client.services.AuthenticationHttpBasicApi; import org.apache.fineract.client.services.BatchApiApi; +import org.apache.fineract.client.services.BusinessDateManagementApi; import org.apache.fineract.client.services.CacheApi; import org.apache.fineract.client.services.CashierJournalsApi; import org.apache.fineract.client.services.CashiersApi; @@ -57,6 +58,7 @@ import org.apache.fineract.client.services.CodesApi; import org.apache.fineract.client.services.CurrencyApi; import org.apache.fineract.client.services.DataTablesApi; import org.apache.fineract.client.services.DefaultApi; +import org.apache.fineract.client.services.DelinquencyRangeAndBucketsManagementApi; import org.apache.fineract.client.services.DocumentsApiFixed; import org.apache.fineract.client.services.EntityDataTableApi; import org.apache.fineract.client.services.EntityFieldConfigurationApi; @@ -174,6 +176,7 @@ public final class FineractClient { public final AuditsApi audits; public final AuthenticationHttpBasicApi authentication; public final BatchApiApi batches; + public final BusinessDateManagementApi businessDateManagement; public final CacheApi caches; public final CashierJournalsApi cashiersJournal; public final CashiersApi cashiers; @@ -190,6 +193,7 @@ public final class FineractClient { public final DataTablesApi dataTables; public final @Deprecated DefaultApi legacy; // TODO FINERACT-1222 public final DocumentsApiFixed documents; + public final DelinquencyRangeAndBucketsManagementApi delinquencyRangeAndBucketsManagement; public final EntityDataTableApi entityDatatableChecks; public final EntityFieldConfigurationApi entityFieldConfigurations; public final ExternalServicesApi externalServices; @@ -286,6 +290,7 @@ public final class FineractClient { audits = retrofit.create(AuditsApi.class); authentication = retrofit.create(AuthenticationHttpBasicApi.class); batches = retrofit.create(BatchApiApi.class); + businessDateManagement = retrofit.create(BusinessDateManagementApi.class); caches = retrofit.create(CacheApi.class); cashiersJournal = retrofit.create(CashierJournalsApi.class); cashiers = retrofit.create(CashiersApi.class); @@ -300,6 +305,7 @@ public final class FineractClient { codeValues = retrofit.create(CodeValuesApi.class); currencies = retrofit.create(CurrencyApi.class); dataTables = retrofit.create(DataTablesApi.class); + delinquencyRangeAndBucketsManagement = retrofit.create(DelinquencyRangeAndBucketsManagementApi.class); legacy = retrofit.create(DefaultApi.class); documents = retrofit.create(DocumentsApiFixed.class); entityDatatableChecks = retrofit.create(EntityDataTableApi.class); 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 ea96f5c50..581681ac3 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 @@ -103,6 +103,8 @@ public class CommandStrategyProvider { "createTransactionLoanCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\?command=payoutRefund").method("POST").build(), "createTransactionLoanCommandStrategy"); + this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\/\\d+").method("POST").build(), + "adjustTransactionCommandStrategy"); this.commandStrategies.put(CommandContext.resource("clients\\/\\d+\\?command=activate").method("POST").build(), "activateClientCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\?command=approve").method("POST").build(), 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 new file mode 100644 index 000000000..d5341d191 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategy.java @@ -0,0 +1,76 @@ +/** + * 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.batch.command.internal; + +import com.google.common.base.Splitter; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.UriInfo; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.command.CommandStrategyUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} to adjust a transaction. It passes the contents of the body from the BatchRequest + * to {@link LoanTransactionsApiResource} and gets back the response. This class will also catch any errors raised by + * {@link LoanTransactionsApiResource} and map those errors to appropriate status codes in BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class AdjustTransactionCommandStrategy implements CommandStrategy { + + private final LoanTransactionsApiResource loanTransactionsApiResource; + + @Override + public BatchResponse execute(final BatchRequest request, UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + final String relativeUrl = request.getRelativeUrl(); + + // 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)); + Map queryParameters = CommandStrategyUtils.getQueryParameters(relativeUrl); + String command = queryParameters.get("command"); + + // Calls 'adjustLoanTransaction' function from 'loanTransactionsApiResource' + responseBody = loanTransactionsApiResource.adjustLoanTransaction(loanId, transactionId, request.getBody(), command); + + response.setStatusCode(HttpStatus.SC_OK); + + // Sets the body of the response after retrieving the transaction + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java index 6fde1caff..94f0678a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/businessdate/api/BusinessDateApiResource.java @@ -95,7 +95,7 @@ public class BusinessDateApiResource { @Operation(summary = "Update Business Date", description = "") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateRequest.class))) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateRequest.class))) }) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = BusinessDateApiResourceSwagger.BusinessDateResponse.class))) }) public String updateBusinessDate(final String jsonRequestBody, @Context UriInfo uriInfo) { securityContext.authenticatedUser().validateHasUpdatePermission("BUSINESS_DATE"); final CommandWrapper commandRequest = new CommandWrapperBuilder().updateBusinessDate().withJson(jsonRequestBody).build(); 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 c435ca22c..d8ae731d4 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 @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import java.util.stream.Stream; import javax.ws.rs.HttpMethod; import org.apache.fineract.batch.command.internal.ActivateClientCommandStrategy; +import org.apache.fineract.batch.command.internal.AdjustTransactionCommandStrategy; import org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy; import org.apache.fineract.batch.command.internal.ApplySavingsCommandStrategy; import org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy; @@ -78,6 +79,8 @@ public class CommandStrategyProviderTest { mock(CreateTransactionLoanCommandStrategy.class)), Arguments.of("loans/123/transactions?command=payoutRefund", HttpMethod.POST, "createTransactionLoanCommandStrategy", mock(CreateTransactionLoanCommandStrategy.class)), + Arguments.of("loans/123/transactions/123", 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 new file mode 100644 index 000000000..17fb6da2e --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/AdjustTransactionCommandStrategyTest.java @@ -0,0 +1,107 @@ +/** + * 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.batch.command.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +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.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource; +import org.apache.http.HttpStatus; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class AdjustTransactionCommandStrategyTest { + + /** + * Test {@link AdjustTransactionCommandStrategy#execute} happy path scenario. + */ + @Test + public void testExecuteSuccessScenario() { + // 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 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(null))) + .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. + * + * @param loanId + * the loan id + * @param transactionId + * the transaction id + * @return BatchRequest + */ + private BatchRequest getBatchRequest(final Long loanId, final Long transactionId) { + + 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); + 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}"); + + return br; + } + + /** + * Private test context class used since testng runs in parallel to avoid state between tests + */ + private static class TestContext { + + @Mock + private UriInfo uriInfo; + + @Mock + private LoanTransactionsApiResource loanTransactionsApiResource; + + private final AdjustTransactionCommandStrategy subjectToTest; + + TestContext() { + MockitoAnnotations.openMocks(this); + subjectToTest = new AdjustTransactionCommandStrategy(loanTransactionsApiResource); + } + } +} 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 20bbf03fe..1d1ca330d 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 @@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.restassured.builder.RequestSpecBuilder; @@ -33,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import org.apache.fineract.batch.command.internal.AdjustTransactionCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; @@ -147,7 +149,7 @@ public class BatchApiTest { * rolled back. * * @see org.apache.fineract.batch.command.internal.CreateClientCommandStrategy - * @see org.apache.fineract.batch.api.BatchesApiResource + * @see org.apache.fineract.batch.api.BatchApiResource * @see org.apache.fineract.batch.service.BatchApiService */ @Test @@ -1166,6 +1168,247 @@ public class BatchApiTest { Assertions.assertEquals(HttpStatus.SC_OK, responses.get(4).getStatusCode(), "Verify Status Code 200 for payout refund"); } + /** + * Test for the successful repayment reversal transaction. A '200' status code is expected on successful responses. + * + * @see AdjustTransactionCommandStrategy + */ + @Test + public void shouldReturnOkStatusForBatchRepaymentReversal() { + + 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 LocalDate date = LocalDate.now(Utils.getZoneIdOfTenant()); + 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 reversal request + final BatchRequest repaymentReversalRequest = BatchHelper.createAdjustTransactionRequest(repayReversalRequestId, repayLoanRequestId, + "0", date); + + // Get loan transactions request + final BatchRequest getLoanTransactionsRequest = BatchHelper.getLoanByIdRequest(getLoanRequestId, applyLoanRequestId, + "associations=transactions"); + + final List batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, repaymentRequest, + repaymentReversalRequest, 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(); + final JsonArray dateArray = repayment.get("reversedOnDate").getAsJsonArray(); + final LocalDate reversedOnDate = LocalDate.of(dateArray.get(0).getAsInt(), dateArray.get(1).getAsInt(), + dateArray.get(2).getAsInt()); + + Assertions.assertEquals(HttpStatus.SC_OK, (long) responses.get(4).getStatusCode(), "Verify Status Code 200 for repayment reversal"); + Assertions.assertEquals("Repayment", repayment.get("type").getAsJsonObject().get("value").getAsString()); + Assertions.assertTrue(repayment.get("manuallyReversed").getAsBoolean()); + Assertions.assertEquals(date, reversedOnDate); + } + + /** + * Tests successful run of batch goodwill credit reversal for loans. A '200' status code is expected on successful + * responses. + * + * @see AdjustTransactionCommandStrategy + */ + @Test + public void shouldReturnOkStatusForBatchGoodwillCreditReversal() { + + final String loanProductJSON = new LoanProductTestBuilder() // + .withPrincipal("1000.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 LocalDate date = LocalDate.now(Utils.getZoneIdOfTenant()); + final Long applyLoanRequestId = 5730L; + final Long approveLoanRequestId = 5731L; + final Long disburseLoanRequestId = 5732L; + final Long goodwillCreditRequestId = 5733L; + final Long goodwillCreditReversalRequestId = 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 good will credit request + final BatchRequest goodwillCreditRequest = BatchHelper.goodwillCreditRequest(goodwillCreditRequestId, disburseLoanRequestId, "500"); + + // Create a good will credit reversal request + final BatchRequest goodwillCreditReversalRequest = BatchHelper.createAdjustTransactionRequest(goodwillCreditReversalRequestId, + goodwillCreditRequestId, "0", date); + + // Get loan transactions request + final BatchRequest getLoanTransactionsRequest = BatchHelper.getLoanByIdRequest(getLoanRequestId, applyLoanRequestId, + "associations=transactions"); + + final List batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, + goodwillCreditRequest, goodwillCreditReversalRequest, getLoanTransactionsRequest); + + final List responses = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec, + BatchHelper.toJsonString(batchRequests)); + + final FromJsonHelper jsonHelper = new FromJsonHelper(); + final JsonObject goodWillCredit = jsonHelper.parse(responses.get(5).getBody()).getAsJsonObject().get("transactions") + .getAsJsonArray().get(2).getAsJsonObject(); + final JsonArray dateArray = goodWillCredit.get("reversedOnDate").getAsJsonArray(); + final LocalDate reversedOnDate = LocalDate.of(dateArray.get(0).getAsInt(), dateArray.get(1).getAsInt(), + dateArray.get(2).getAsInt()); + + Assertions.assertEquals(HttpStatus.SC_OK, (long) responses.get(4).getStatusCode(), + "Verify Status Code 200 for goodwill credit reversal"); + Assertions.assertEquals("Goodwill Credit", goodWillCredit.get("type").getAsJsonObject().get("value").getAsString()); + Assertions.assertTrue(goodWillCredit.get("manuallyReversed").getAsBoolean()); + Assertions.assertEquals(date, reversedOnDate); + } + + /** + * Test for the successful merchant issued refund and payout refund reversal transaction. A '200' status code is + * expected on successful responses. + * + * @see AdjustTransactionCommandStrategy + */ + @Test + public void shouldReturnOkStatusOnSuccessfulTransactionMerchantIssuedAndPayoutRefundReversal() { + final String loanProductJSON = new LoanProductTestBuilder() // + .withPrincipal("10000000.00") // + .withNumberOfRepayments("24") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("2") // + .withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // + .withInterestTypeAsDecliningBalance() // + .currencyDetails("0", "100").build(null); + + final LocalDate date = LocalDate.now(Utils.getZoneIdOfTenant()); + final Long applyLoanRequestId = 5730L; + final Long approveLoanRequestId = 5731L; + final Long disburseLoanRequestId = 5732L; + final Long merchantIssuedRefundRequestId = 5733L; + final Long payoutRefundRequestId = 5734L; + final Long merchantIssuedRefundReversalRequestId = 5735L; + final Long payoutRefundReversalRequestId = 5736L; + final Long getLoanRequestId = 5737L; + + // Create product + final Integer productId = new LoanTransactionHelper(this.requestSpec, this.responseSpec).getLoanProductId(loanProductJSON); + + // 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, + date.minusDays(1)); + + // Create a merchant issued refund request + final BatchRequest merchantIssuedRefundRequest = BatchHelper.merchantIssuedRefundRequest(merchantIssuedRefundRequestId, + applyLoanRequestId, "10"); + + // Create a payout refund request + final BatchRequest payoutRefundRequest = BatchHelper.payoutRefundRequest(payoutRefundRequestId, applyLoanRequestId, "10"); + + // Create a merchant issued refund reversal request + final BatchRequest merchantIssuedRefundReversalRequest = BatchHelper + .createAdjustTransactionRequest(merchantIssuedRefundReversalRequestId, merchantIssuedRefundRequestId, "0", date); + + // Create a payout refund reversal request + final BatchRequest payoutRefundReversalRequest = BatchHelper.createAdjustTransactionRequest(payoutRefundReversalRequestId, + payoutRefundRequestId, "0", date); + + // Get loan transactions request + final BatchRequest getLoanTransactionsRequest = BatchHelper.getLoanByIdRequest(getLoanRequestId, applyLoanRequestId, + "associations=transactions"); + + final List batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, + merchantIssuedRefundRequest, payoutRefundRequest, merchantIssuedRefundReversalRequest, payoutRefundReversalRequest, + getLoanTransactionsRequest); + + final List responses = BatchHelper.postBatchRequestsWithEnclosingTransaction(this.requestSpec, this.responseSpec, + BatchHelper.toJsonString(batchRequests)); + + final FromJsonHelper jsonHelper = new FromJsonHelper(); + final JsonObject merchantIssuedRefund = jsonHelper.parse(responses.get(7).getBody()).getAsJsonObject().get("transactions") + .getAsJsonArray().get(2).getAsJsonObject(); + final JsonObject payoutRefund = jsonHelper.parse(responses.get(7).getBody()).getAsJsonObject().get("transactions").getAsJsonArray() + .get(3).getAsJsonObject(); + final JsonArray merchantIssuedDateArray = merchantIssuedRefund.get("reversedOnDate").getAsJsonArray(); + final LocalDate merchantIssuedDate = LocalDate.of(merchantIssuedDateArray.get(0).getAsInt(), + merchantIssuedDateArray.get(1).getAsInt(), merchantIssuedDateArray.get(2).getAsInt()); + final JsonArray payoutRefundDateArray = payoutRefund.getAsJsonObject().get("reversedOnDate").getAsJsonArray(); + final LocalDate payoutRefundDate = LocalDate.of(payoutRefundDateArray.get(0).getAsInt(), payoutRefundDateArray.get(1).getAsInt(), + payoutRefundDateArray.get(2).getAsInt()); + + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(5).getStatusCode(), + "Verify Status Code 200 for merchant issued refund reversal"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(6).getStatusCode(), "Verify Status Code 200 for payout refund reversal"); + Assertions.assertEquals("Merchant Issued Refund", merchantIssuedRefund.get("type").getAsJsonObject().get("value").getAsString()); + Assertions.assertEquals("Payout Refund", payoutRefund.get("type").getAsJsonObject().get("value").getAsString()); + Assertions.assertTrue(merchantIssuedRefund.get("manuallyReversed").getAsBoolean()); + Assertions.assertTrue(payoutRefund.get("manuallyReversed").getAsBoolean()); + Assertions.assertEquals(date, merchantIssuedDate); + Assertions.assertEquals(date, payoutRefundDate); + } + /** * Delete datatable * 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 2d5fb2963..93693c882 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 @@ -785,4 +785,21 @@ public final class BatchHelper { return br; } + + public static BatchRequest createAdjustTransactionRequest(final Long requestId, final Long reference, final String amount, + final LocalDate date) { + final BatchRequest br = new BatchRequest(); + + br.setRequestId(requestId); + br.setReference(reference); + br.setRelativeUrl("loans/$.loanId/transactions/$.resourceId"); + br.setMethod("POST"); + String dateString = date.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + br.setBody(String.format( + "{\"locale\": \"en\", \"dateFormat\": \"dd MMMM yyyy\", " + "\"transactionDate\": \"%s\", \"transactionAmount\": %s}", + dateString, amount)); + + return br; + + } }