diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index d7f39f1fd..436f0ce30 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -228,12 +228,12 @@ public class CommandStrategyProvider { .resource("v1\\/datatables\\/" + ALPHANUMBERIC_WITH_UNDERSCORE_REGEX + "\\/query" + MANDATORY_QUERY_PARAM_REGEX).method(GET) .build(), "getDatatableEntryByQueryCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/interest-pauses").method(GET).build(), - "getLoanInterestPausesByExternalIdCommandStrategy"); + "getLoanInterestPausesByLoanIdCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/interest-pauses").method(POST).build(), - "createLoanInterestPauseByExternalIdCommandStrategy"); + "createLoanInterestPauseByLoanIdCommandStrategy"); commandStrategies.put( CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/interest-pauses\\/" + NUMBER_REGEX).method(PUT).build(), - "updateLoanInterestPauseByExternalIdCommandStrategy"); + "updateLoanInterestPauseByLoanIdCommandStrategy"); commandStrategies.put( CommandContext.resource("v1\\/loans\\/external-id\\/" + UUID_PARAM_REGEX + "\\/interest-pauses").method(GET).build(), "getLoanInterestPausesByExternalIdCommandStrategy"); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java index c171b40ec..2e6864373 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java @@ -26,6 +26,7 @@ import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import java.io.IOException; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -954,6 +955,112 @@ public class BatchApiStepDef extends AbstractStepDef { } } + @When("Run Batch API with steps: createClient, createLoan, approveLoan, disburseLoan, applyInterestPause by external ids") + public void runBatchApiWithInterestPauseByExternalIds() throws IOException { + String idempotencyKey = UUID.randomUUID().toString(); + String clientExternalId = UUID.randomUUID().toString(); + String loanExternalId = UUID.randomUUID().toString(); + + List requestList = new ArrayList<>(); + + // Create client + requestList.add(createClient(1L, idempotencyKey, clientExternalId)); + + // Create loan + requestList.add(createProgressiveLoan(2L, 1L, idempotencyKey, loanExternalId)); + + // Approve loan + requestList.add(approveLoanByExternalId(3L, 2L, idempotencyKey)); + + // Disburse loan + PostLoansLoanIdRequest loanDisburseRequest = LoanRequestFactory.defaultLoanDisburseRequest(); + String bodyLoanDisburseRequest = GSON.toJson(loanDisburseRequest); + BatchRequest disburseRequest = new BatchRequest(); + disburseRequest.requestId(4L); + disburseRequest.relativeUrl("loans/external-id/$.resourceExternalId?command=disburse"); + disburseRequest.method(BATCH_API_METHOD_POST); + disburseRequest.reference(2L); + disburseRequest.headers(setHeaders(idempotencyKey)); + disburseRequest.body(bodyLoanDisburseRequest); + requestList.add(disburseRequest); + + // Apply interest pause (1 day starting from tomorrow) + String startDate = DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.now().minusMonths(1).plusDays(1)); + String endDate = DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.now().minusMonths(1).plusDays(2)); + requestList.add(applyInterestPauseByExternalId(5L, 2L, idempotencyKey, startDate, endDate)); + + // Execute batch request + Response> batchResponseList = batchApiApi.handleBatchRequests(requestList, true).execute(); + testContext().set(TestContextKey.BATCH_API_CALL_RESPONSE, batchResponseList); + testContext().set(TestContextKey.BATCH_API_CALL_IDEMPOTENCY_KEY, idempotencyKey); + testContext().set(TestContextKey.BATCH_API_CALL_CLIENT_EXTERNAL_ID, clientExternalId); + testContext().set(TestContextKey.BATCH_API_CALL_LOAN_EXTERNAL_ID, loanExternalId); + + // Log response for debugging + if (batchResponseList.isSuccessful() && batchResponseList.body() != null && !batchResponseList.body().isEmpty()) { + for (int i = 0; i < batchResponseList.body().size(); i++) { + BatchResponse response = batchResponseList.body().get(i); + log.debug("Batch step {} status code: {}", i + 1, response.getStatusCode()); + log.debug("Batch step {} response body: {}", i + 1, response.getBody()); + } + } else { + log.warn("Batch API call failed or returned empty response"); + } + } + + @When("Run Batch API with steps: createClient, createLoan, approveLoan, disburseLoan, applyInterestPause") + public void runBatchApiWithInterestPause() throws IOException { + String idempotencyKey = UUID.randomUUID().toString(); + String clientExternalId = UUID.randomUUID().toString(); + String loanExternalId = UUID.randomUUID().toString(); + + List requestList = new ArrayList<>(); + + // Create client + requestList.add(createClient(1L, idempotencyKey, clientExternalId)); + + // Create loan + requestList.add(createProgressiveLoan(2L, 1L, idempotencyKey, loanExternalId)); + + // Approve loan + requestList.add(approveLoanByExternalId(3L, 2L, idempotencyKey)); + + // Disburse loan + PostLoansLoanIdRequest loanDisburseRequest = LoanRequestFactory.defaultLoanDisburseRequest(); + String bodyLoanDisburseRequest = GSON.toJson(loanDisburseRequest); + BatchRequest disburseRequest = new BatchRequest(); + disburseRequest.requestId(4L); + disburseRequest.relativeUrl("loans/$.loanId?command=disburse"); + disburseRequest.method(BATCH_API_METHOD_POST); + disburseRequest.reference(2L); + disburseRequest.headers(setHeaders(idempotencyKey)); + disburseRequest.body(bodyLoanDisburseRequest); + requestList.add(disburseRequest); + + // Apply interest pause (1 day starting from tomorrow) + String startDate = DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.now().minusMonths(1).plusDays(1)); + String endDate = DateTimeFormatter.ofPattern(DATE_FORMAT).format(Utils.now().minusMonths(1).plusDays(2)); + requestList.add(applyInterestPause(5L, 2L, idempotencyKey, startDate, endDate)); + + // Execute batch request + Response> batchResponseList = batchApiApi.handleBatchRequests(requestList, true).execute(); + testContext().set(TestContextKey.BATCH_API_CALL_RESPONSE, batchResponseList); + testContext().set(TestContextKey.BATCH_API_CALL_IDEMPOTENCY_KEY, idempotencyKey); + testContext().set(TestContextKey.BATCH_API_CALL_CLIENT_EXTERNAL_ID, clientExternalId); + testContext().set(TestContextKey.BATCH_API_CALL_LOAN_EXTERNAL_ID, loanExternalId); + + // Log response for debugging + if (batchResponseList.isSuccessful() && batchResponseList.body() != null && !batchResponseList.body().isEmpty()) { + for (int i = 0; i < batchResponseList.body().size(); i++) { + BatchResponse response = batchResponseList.body().get(i); + log.debug("Batch step {} status code: {}", i + 1, response.getStatusCode()); + log.debug("Batch step {} response body: {}", i + 1, response.getBody()); + } + } else { + log.warn("Batch API call failed or returned empty response"); + } + } + private BatchRequest createChargeOffRequest(Long requestId, Long loanId, String idempotencyKey, String chargeOffDate) { // Create a charge-off request with the specified date Map requestMap = new HashMap<>(); @@ -1043,6 +1150,24 @@ public class BatchApiStepDef extends AbstractStepDef { return batchRequest; } + private BatchRequest createProgressiveLoan(Long requestId, Long referenceId, String idempotencyKey, String loanExternalId) { + PostLoansRequest loansRequest = loanExternalId == null ? loanRequestFactory.defaultProgressiveLoansRequest(1L) + : loanRequestFactory.defaultProgressiveLoansRequest(1L).externalId(loanExternalId); + loansRequest.setInterestRatePerPeriod(BigDecimal.ONE); + String bodyLoansRequest = GSON.toJson(loansRequest); + String bodyLoansRequestMod = bodyLoansRequest.replace("\"clientId\":1", "\"clientId\":\"$.clientId\""); + + BatchRequest batchRequest = new BatchRequest(); + batchRequest.requestId(requestId); + batchRequest.relativeUrl(BATCH_API_SAMPLE_RELATIVE_URL_LOANS); + batchRequest.method(BATCH_API_METHOD_POST); + batchRequest.headers(setHeaders(idempotencyKey)); + batchRequest.reference(referenceId); + batchRequest.body(bodyLoansRequestMod); + + return batchRequest; + } + private BatchRequest queryDatatable(Long requestId) { String datatableName = testContext().get(DATATABLE_NAME); @@ -1113,6 +1238,37 @@ public class BatchApiStepDef extends AbstractStepDef { return batchRequest; } + private BatchRequest applyInterestPause(Long requestId, Long referenceId, String idempotencyKey, String startDate, String endDate) { + BatchRequest batchRequest = new BatchRequest(); + batchRequest.requestId(requestId); + batchRequest.relativeUrl("loans/$.loanId/interest-pauses"); + batchRequest.method(BATCH_API_METHOD_POST); + batchRequest.reference(referenceId); + batchRequest.headers(setHeaders(idempotencyKey)); + + String interestPauseRequest = String + .format("{\"dateFormat\":\"dd MMMM yyyy\",\"locale\":\"en\",\"startDate\":\"%s\",\"endDate\":\"%s\"}", startDate, endDate); + batchRequest.body(interestPauseRequest); + + return batchRequest; + } + + private BatchRequest applyInterestPauseByExternalId(Long requestId, Long referenceId, String idempotencyKey, String startDate, + String endDate) { + BatchRequest batchRequest = new BatchRequest(); + batchRequest.requestId(requestId); + batchRequest.relativeUrl("loans/external-id/$.resourceExternalId/interest-pauses"); + batchRequest.method(BATCH_API_METHOD_POST); + batchRequest.reference(referenceId); + batchRequest.headers(setHeaders(idempotencyKey)); + + String interestPauseRequest = String + .format("{\"dateFormat\":\"dd MMMM yyyy\",\"locale\":\"en\",\"startDate\":\"%s\",\"endDate\":\"%s\"}", startDate, endDate); + batchRequest.body(interestPauseRequest); + + return batchRequest; + } + private Set
setHeaders(String idempotencyKey) { Set
headers = new HashSet<>(); headers.add(HEADER); @@ -1122,4 +1278,43 @@ public class BatchApiStepDef extends AbstractStepDef { return headers; } + + @Then("Loan should have an active interest pause period starting on {int}st day and ending on {int}nd day") + public void verifyInterestPausePeriod(int startDay, int endDay) throws IOException { + // Get the loan ID from the batch response + Response> batchResponseList = testContext().get(TestContextKey.BATCH_API_CALL_RESPONSE); + assertThat(batchResponseList.isSuccessful()).isTrue(); + assertThat(batchResponseList.body()).isNotNull(); + + // The loan creation response is the second response in the batch (index 1) + BatchResponse loanCreateResponse = batchResponseList.body().get(1); + assertThat(loanCreateResponse.getStatusCode()).isEqualTo(200); + + // Parse the loan ID from the response + String loanCreateResponseBody = loanCreateResponse.getBody(); + com.google.gson.JsonObject loanCreateJson = com.google.gson.JsonParser.parseString(loanCreateResponseBody).getAsJsonObject(); + long loanId = loanCreateJson.get("loanId").getAsLong(); + + // Get the loan details + Response loanResponse = loansApi.retrieveLoan(loanId, false, "all", "", "").execute(); + assertThat(loanResponse.isSuccessful()).isTrue(); + assertThat(loanResponse.body()).isNotNull(); + + // Verify the interest pause period + GetLoansLoanIdResponse loan = loanResponse.body(); + assertThat(loan.getLoanTermVariations().get(0).getTermType().getValue().equals("interestPause")).isTrue(); + + // Verify the start date is the specified day of the previous month + LocalDate today = Utils.now(); + LocalDate expectedStartDate = today.minusMonths(1).plusDays(startDay); + LocalDate actualStartDate = loan.getLoanTermVariations().get(0).getTermVariationApplicableFrom(); + assertThat(actualStartDate).isEqualTo(expectedStartDate); + + // Verify the end date is the specified day of the previous month + LocalDate expectedEndDate = today.minusMonths(1).plusDays(endDay); + LocalDate actualEndDate = loan.getLoanTermVariations().get(0).getDateValue(); + assertThat(actualEndDate).isEqualTo(expectedEndDate); + + log.debug("Verified interest pause period from {} to {}", actualStartDate, actualEndDate); + } } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/BatchApi.feature b/fineract-e2e-tests-runner/src/test/resources/features/BatchApi.feature index d76e10abb..e3a7cb578 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/BatchApi.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/BatchApi.feature @@ -48,6 +48,18 @@ Feature: Batch API When Batch API call with steps done twice: createClient, createLoan, approveLoan, getLoanDetails runs with enclosingTransaction: "false" Then Admin checks that all steps result 200OK + @TestRailId:C3876 + Scenario: Create loan, approve, disburse and apply interest pause in a single Batch API call + And Run Batch API with steps: createClient, createLoan, approveLoan, disburseLoan, applyInterestPause + Then Admin checks that all steps result 200OK + And Loan should have an active interest pause period starting on 1st day and ending on 2nd day + + @TestRailId:C3877 + Scenario: Create loan, approve, disburse and apply interest pause in a single Batch API call by external ids + And Run Batch API with steps: createClient, createLoan, approveLoan, disburseLoan, applyInterestPause by external ids + Then Admin checks that all steps result 200OK + And Loan should have an active interest pause period starting on 1st day and ending on 2nd day + @TestRailId:C2645 Scenario: Verify Batch API call in case of enclosing transaction is FALSE, there are two reference-trees and one of the steps in second tree fails When Batch API call with steps done twice: createClient, createLoan, approveLoan, getLoanDetails runs with enclosingTransaction: "false", with failed approve step in second tree @@ -141,4 +153,4 @@ Feature: Batch API | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | | 01 February 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | | 01 February 2024 | Charge-off | 83.57 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | - And Admin checks the loan has been charged-off on "01 February 2024" \ No newline at end of file + And Admin checks the loan has been charged-off on "01 February 2024" diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java index 77efbbcb9..6763c2b87 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/data/InterestPauseRequestDto.java @@ -50,4 +50,12 @@ public class InterestPauseRequestDto { throw new IllegalArgumentException("Error serializing request to JSON", e); } } + + public static InterestPauseRequestDto fromJson(String json) { + try { + return new ObjectMapper().readValue(json, InterestPauseRequestDto.class); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Error deserializing request from JSON", e); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByExternalIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByExternalIdCommandStrategy.java new file mode 100644 index 000000000..7c2e225e7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByExternalIdCommandStrategy.java @@ -0,0 +1,71 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseRequestDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and creates a loan interest pause by external id. It passes the contents of the + * body from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also + * catch any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class CreateLoanInterestPauseByExternalIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/external-id\/[\w\d_-]+\/interest-pauses + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final String loanExternalId = pathParameters.get(2); + + final InterestPauseRequestDto interestPauseRequestDto = InterestPauseRequestDto.fromJson(request.getBody()); + final CommandProcessingResult commandProcessingResult = loanInterestPauseApiResource.createInterestPauseByExternalId(loanExternalId, + interestPauseRequestDto); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(commandProcessingResult)); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByLoanIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByLoanIdCommandStrategy.java new file mode 100644 index 000000000..b66dffcd5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateLoanInterestPauseByLoanIdCommandStrategy.java @@ -0,0 +1,71 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseRequestDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and creates a loan interest pause by loan id. It passes the contents of the body + * from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also catch + * any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class CreateLoanInterestPauseByLoanIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/\d+\/interest-pauses + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final Long loanId = Long.parseLong(pathParameters.get(1)); + + final InterestPauseRequestDto interestPauseRequestDto = InterestPauseRequestDto.fromJson(request.getBody()); + final CommandProcessingResult commandProcessingResult = loanInterestPauseApiResource.createInterestPause(loanId, + interestPauseRequestDto); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(commandProcessingResult)); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByExternalIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByExternalIdCommandStrategy.java new file mode 100644 index 000000000..fabf42ab5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByExternalIdCommandStrategy.java @@ -0,0 +1,69 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseResponseDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and retrieves loan interest pauses by external id. It passes the contents of the + * body from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also + * catch any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class GetLoanInterestPausesByExternalIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer> toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/external-id\/[\w\d_-]+\/interest-pauses + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final String loanExternalId = pathParameters.get(2); + + final List responseDtos = loanInterestPauseApiResource.retrieveInterestPausesByExternalId(loanExternalId); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(responseDtos)); + + return response; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByLoanIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByLoanIdCommandStrategy.java new file mode 100644 index 000000000..a96ce7131 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanInterestPausesByLoanIdCommandStrategy.java @@ -0,0 +1,68 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseResponseDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and retrieves loan interest pauses by loan id. It passes the contents of the body + * from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also catch + * any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class GetLoanInterestPausesByLoanIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer> toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/\d+\/interest-pauses + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final Long loanId = Long.parseLong(pathParameters.get(1)); + + final List responseDtos = loanInterestPauseApiResource.retrieveInterestPauses(loanId); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(responseDtos)); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByExternalIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByExternalIdCommandStrategy.java new file mode 100644 index 000000000..47daab068 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByExternalIdCommandStrategy.java @@ -0,0 +1,72 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseRequestDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and updates a loan interest pause by external id. It passes the contents of the + * body from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also + * catch any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class UpdateLoanInterestPauseByExternalIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/external-id\/[\w\d_-]+\/interest-pauses\/\d+ + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final String loanExternalId = pathParameters.get(2); + final Long variationId = Long.parseLong(pathParameters.get(4)); + + final InterestPauseRequestDto interestPauseRequestDto = InterestPauseRequestDto.fromJson(request.getBody()); + final CommandProcessingResult commandProcessingResult = loanInterestPauseApiResource.updateInterestPauseByExternalId(loanExternalId, + variationId, interestPauseRequestDto); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(commandProcessingResult)); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByLoanIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByLoanIdCommandStrategy.java new file mode 100644 index 000000000..64f3f0850 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/UpdateLoanInterestPauseByLoanIdCommandStrategy.java @@ -0,0 +1,72 @@ +/** + * 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.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.interestpauses.api.LoanInterestPauseApiResource; +import org.apache.fineract.portfolio.interestpauses.data.InterestPauseRequestDto; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and updates a loan interest pause by loan id. It passes the contents of the body + * from the BatchRequest to {@link LoanInterestPauseApiResource} and gets back the response. This class will also catch + * any errors raised by {@link LoanInterestPauseApiResource} and map those errors to appropriate status codes in + * BatchResponse. + */ +@Component +@RequiredArgsConstructor +public class UpdateLoanInterestPauseByLoanIdCommandStrategy implements CommandStrategy { + + private final LoanInterestPauseApiResource loanInterestPauseApiResource; + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + + @Override + public BatchResponse execute(final BatchRequest request, @SuppressWarnings("unused") final UriInfo uriInfo) { + final BatchResponse response = new BatchResponse(); + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + // Expected pattern - loans\/\d+\/interest-pauses\/\d+ + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final Long loanId = Long.parseLong(pathParameters.get(1)); + final Long variationId = Long.parseLong(pathParameters.get(3)); + + final InterestPauseRequestDto interestPauseRequestDto = InterestPauseRequestDto.fromJson(request.getBody()); + final CommandProcessingResult commandProcessingResult = loanInterestPauseApiResource.updateInterestPause(loanId, variationId, + interestPauseRequestDto); + + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(toApiJsonSerializer.serialize(commandProcessingResult)); + + return response; + } +} 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 b2b815d0e..c78b72d4f 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 @@ -195,11 +195,11 @@ public class CommandStrategyProviderTest { "getDatatableEntryByQueryCommandStrategy", mock(GetDatatableEntryByQueryCommandStrategy.class)), Arguments.of("datatables/test_dt_table/query?columnFilter=custom_id&valueFilter=10a62-d438-2319&resultColumns=id", HttpMethod.GET, "getDatatableEntryByQueryCommandStrategy", mock(GetDatatableEntryByQueryCommandStrategy.class)), - Arguments.of("loans/123/interest-pauses", HttpMethod.GET, "getLoanInterestPausesByExternalIdCommandStrategy", + Arguments.of("loans/123/interest-pauses", HttpMethod.GET, "getLoanInterestPausesByLoanIdCommandStrategy", mock(CommandStrategy.class)), - Arguments.of("loans/123/interest-pauses", HttpMethod.POST, "createLoanInterestPauseByExternalIdCommandStrategy", + Arguments.of("loans/123/interest-pauses", HttpMethod.POST, "createLoanInterestPauseByLoanIdCommandStrategy", mock(CommandStrategy.class)), - Arguments.of("loans/123/interest-pauses/123", HttpMethod.PUT, "updateLoanInterestPauseByExternalIdCommandStrategy", + Arguments.of("loans/123/interest-pauses/456", HttpMethod.PUT, "updateLoanInterestPauseByLoanIdCommandStrategy", mock(CommandStrategy.class)), Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/interest-pauses", HttpMethod.GET, "getLoanInterestPausesByExternalIdCommandStrategy", mock(CommandStrategy.class)),