From 388612b1ae68bc3ea69678cf352029372fe10faf Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Fri, 14 Oct 2022 16:57:26 +0200 Subject: [PATCH] FINERACT-1724 Added batch support for the Get Charge by Id call. Added externalId field to PostLoansLoanIdChargesRequest. Added submittedOnDate column to m_loan_charge table. Updated logic to return new submittedOnDate field in GetLoansLoanIdChargesChargeIdResponse. --- build.gradle | 1 + .../command/CommandStrategyProvider.java | 2 + .../GetChargeByIdCommandStrategy.java | 87 ++++++++++ .../api/LoanChargesApiResourceSwagger.java | 5 + .../loanaccount/data/LoanChargeData.java | 18 ++- .../loanaccount/domain/LoanCharge.java | 9 ++ .../LoanChargeReadPlatformServiceImpl.java | 9 +- .../LoanProductDataValidator.java | 3 +- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...5_add_submitted_on_date_to_loan_charge.xml | 30 ++++ .../command/CommandStrategyProviderTest.java | 6 +- .../GetChargeByIdCommandStrategyTest.java | 149 ++++++++++++++++++ .../integrationtests/BatchApiTest.java | 65 ++++++++ .../integrationtests/common/BatchHelper.java | 38 ++++- 14 files changed, 410 insertions(+), 13 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml create mode 100644 fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java diff --git a/build.gradle b/build.gradle index 87272d239..af38e01e2 100644 --- a/build.gradle +++ b/build.gradle @@ -156,6 +156,7 @@ allprojects { "**/build.gradle.mustache", "**/pom.mustache", "**/avro/**/*.java", + "**/org/apache/fineract/client/**/*.java" ]) strictCheck true } 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 581681ac3..6005a4a9e 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 @@ -91,6 +91,8 @@ public class CommandStrategyProvider { this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("POST").build(), "createChargeCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("GET").build(), "collectChargesCommandStrategy"); + this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges\\/\\d+").method("GET").build(), + "getChargeByIdCommandStrategy"); this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\?command=repayment").method("POST").build(), "createTransactionLoanCommandStrategy"); this.commandStrategies.put( diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java new file mode 100644 index 000000000..2df109b1d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java @@ -0,0 +1,87 @@ +/** + * 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.commons.lang3.StringUtils; +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.infrastructure.core.api.MutableUriInfo; +import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} to retrieve a charge by id. It passes the contents of the body from the + * BatchRequest to {@link LoanChargesApiResource} and gets back the response. This class will also catch any errors + * raised by {@link LoanChargesApiResource} and map those errors to appropriate status codes in BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class GetChargeByIdCommandStrategy implements CommandStrategy { + + private final LoanChargesApiResource loanChargesApiResource; + + @Override + public BatchResponse execute(final BatchRequest request, UriInfo uriInfo) { + final MutableUriInfo parameterizedUriInfo = new MutableUriInfo(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 charge ids for use in loanChargesApiResource + final List pathParameters = Splitter.on('/').splitToList(relativeUrl); + final Long loanId = Long.parseLong(pathParameters.get(1)); + Long chargeId; + if (relativeUrl.indexOf('?') > 0) { + chargeId = Long.parseLong(StringUtils.substringBeforeLast(pathParameters.get(3), "?")); + } else { + chargeId = Long.parseLong(pathParameters.get(3)); + } + + Map queryParameters; + if (relativeUrl.indexOf('?') > 0) { + queryParameters = CommandStrategyUtils.getQueryParameters(relativeUrl); + + // Add the query parameters sent in the relative URL to UriInfo + CommandStrategyUtils.addQueryParametersToUriInfo(parameterizedUriInfo, queryParameters); + } + + responseBody = loanChargesApiResource.retrieveLoanCharge(loanId, chargeId, parameterizedUriInfo); + response.setStatusCode(HttpStatus.SC_OK); + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java index 32853c09b..5f97c02a5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; import java.util.Set; /** @@ -102,6 +103,8 @@ final class LoanChargesApiResourceSwagger { public Double amountOrPercentage; @Schema(example = "false") public Boolean penalty; + @Schema(example = "27 March 2013") + public LocalDate submittedOnDate; } @Schema(description = "GetLoansLoanIdChargesTemplateResponse") @@ -179,6 +182,8 @@ final class LoanChargesApiResourceSwagger { public String dateFormat; @Schema(example = "29 April 2013") public String dueDate; + @Schema(example = "786444UUUYYH7") + public String externalId; } @Schema(description = " PostLoansLoanIdChargesResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java index 68cd919b6..b22825ad1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java @@ -39,6 +39,8 @@ public class LoanChargeData { private final String name; private final EnumOptionData chargeTimeType; + private final LocalDate submittedOnDate; + private final LocalDate dueDate; private final EnumOptionData chargeCalculationType; @@ -103,10 +105,10 @@ public class LoanChargeData { public LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, - final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueDate, - final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, - final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid, final boolean waived, final Long loanId, - final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage, + final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate submittedOnDate, + final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, + final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid, + final boolean waived, final Long loanId, final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal amountOrPercentage, Collection installmentChargeData, final String externalId) { this.id = id; this.chargeId = chargeId; @@ -118,6 +120,7 @@ public class LoanChargeData { this.amountWrittenOff = amountWrittenOff; this.amountOutstanding = amountOutstanding; this.chargeTimeType = chargeTimeType; + this.submittedOnDate = submittedOnDate; this.dueDate = dueDate; this.chargeCalculationType = chargeCalculationType; this.percentage = percentage; @@ -162,6 +165,7 @@ public class LoanChargeData { this.amountWrittenOff = BigDecimal.ZERO; this.amountOutstanding = amount; this.chargeTimeType = chargeTimeType; + this.submittedOnDate = null; this.dueDate = null; this.chargeCalculationType = chargeCalculationType; this.percentage = percentage; @@ -203,6 +207,7 @@ public class LoanChargeData { this.amountWrittenOff = null; this.amountOutstanding = amountOutstanding; this.chargeTimeType = chargeTimeType; + this.submittedOnDate = null; this.dueDate = dueAsOfDate; this.chargeCalculationType = null; this.percentage = null; @@ -234,6 +239,7 @@ public class LoanChargeData { this.amountWrittenOff = null; this.amountOutstanding = null; this.chargeTimeType = chargeTimeType; + this.submittedOnDate = null; this.dueDate = dueAsOfDate; this.chargeCalculationType = null; this.percentage = null; @@ -264,6 +270,7 @@ public class LoanChargeData { this.amountWrittenOff = null; this.amountOutstanding = null; this.chargeTimeType = chargeData.chargeTimeType; + this.submittedOnDate = chargeData.submittedOnDate; this.dueDate = chargeData.dueDate; this.chargeCalculationType = null; this.percentage = null; @@ -294,6 +301,7 @@ public class LoanChargeData { this.amountWrittenOff = chargeData.amountWrittenOff; this.amountOutstanding = chargeData.amountOutstanding; this.chargeTimeType = chargeData.chargeTimeType; + this.submittedOnDate = chargeData.submittedOnDate; this.dueDate = chargeData.dueDate; this.chargeCalculationType = chargeData.chargeCalculationType; this.percentage = chargeData.percentage; @@ -326,6 +334,7 @@ public class LoanChargeData { this.amountWrittenOff = null; this.amountOutstanding = null; this.chargeTimeType = chargeTimeType; + this.submittedOnDate = null; this.dueDate = dueAsOfDate; this.chargeCalculationType = chargeCalculationType; this.percentage = null; @@ -356,6 +365,7 @@ public class LoanChargeData { this.amountWrittenOff = null; this.amountOutstanding = null; this.chargeTimeType = null; + this.submittedOnDate = null; this.dueDate = dueAsOfDate; this.chargeCalculationType = null; this.percentage = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 09079b4ca..302a2b461 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -42,6 +42,7 @@ import javax.persistence.Table; import javax.persistence.UniqueConstraint; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; @@ -68,6 +69,9 @@ public class LoanCharge extends AbstractPersistableCustom { @Column(name = "charge_time_enum", nullable = false) private Integer chargeTime; + @Column(name = "submitted_on_date", nullable = true) + private LocalDate submittedOnDate; + @Column(name = "due_for_collection_as_of_date") private LocalDate dueDate; @@ -229,6 +233,7 @@ public class LoanCharge extends AbstractPersistableCustom { final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge) { this.loan = loan; this.charge = chargeDefinition; + this.submittedOnDate = DateUtils.getBusinessLocalDate(); this.penaltyCharge = chargeDefinition.isPenalty(); this.minCap = chargeDefinition.getMinCap(); this.maxCap = chargeDefinition.getMaxCap(); @@ -597,6 +602,10 @@ public class LoanCharge extends AbstractPersistableCustom { return this.dueDate; } + public LocalDate getSubmittedOnDate() { + return submittedOnDate; + } + private boolean determineIfFullyPaid() { if (this.amount == null) { return true; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index 3c5a7f12c..f9300bcc2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -59,8 +59,8 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform private static final class LoanChargeMapper implements RowMapper { public String schema() { - return "lc.id as id, lc.external_id as externalId, c.id as chargeId, c.name as name, " + "lc.amount as amountDue, " - + "lc.amount_paid_derived as amountPaid, " + "lc.amount_waived_derived as amountWaived, " + return "lc.id as id, lc.external_id as externalId, c.id as chargeId, c.name as name, lc.submitted_on_date as submittedOnDate, " + + "lc.amount as amountDue, " + "lc.amount_paid_derived as amountPaid, " + "lc.amount_waived_derived as amountWaived, " + "lc.amount_writtenoff_derived as amountWrittenOff, " + "lc.amount_outstanding_derived as amountOutstanding, " + "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " + "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, " @@ -116,6 +116,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final BigDecimal maxCap = rs.getBigDecimal("maxCap"); final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage"); final LocalDate disbursementDate = JdbcSupport.getLocalDate(rs, "disbursementDate"); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); if (disbursementDate != null) { dueAsOfDate = disbursementDate; @@ -123,8 +124,8 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final String externalId = rs.getString("externalId"); return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, - chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode, paid, - waived, null, minCap, maxCap, amountOrPercentage, null, externalId); + chargeTimeType, submittedOnDate, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, + paymentMode, paid, waived, null, minCap, maxCap, amountOrPercentage, null, externalId); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 1a83d5120..cb89efb54 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -526,7 +526,8 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName) .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); - if (!amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue()) && fixedPrincipalPercentagePerInstallment != null) { + if (amortizationType != null && !amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue()) + && fixedPrincipalPercentagePerInstallment != null) { baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode( "not.supported.principal.fixing.not.allowed.with.equal.installments", "Principal fixing cannot be done with equal installment amortization"); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index c7e64d63a..1d9f51d22 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -74,4 +74,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml new file mode 100644 index 000000000..a27aff8e9 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + 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 d8ae731d4..1fa216a46 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 @@ -36,6 +36,7 @@ import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy; import org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy; +import org.apache.fineract.batch.command.internal.GetChargeByIdCommandStrategy; import org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy; import org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy; import org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy; @@ -69,6 +70,8 @@ public class CommandStrategyProviderTest { Arguments.of("loans/123/charges", HttpMethod.POST, "createChargeCommandStrategy", mock(CreateChargeCommandStrategy.class)), Arguments.of("loans/123/charges", HttpMethod.GET, "collectChargesCommandStrategy", mock(CollectChargesCommandStrategy.class)), + Arguments.of("loans/123/charges/123", HttpMethod.GET, "getChargeByIdCommandStrategy", + mock(GetChargeByIdCommandStrategy.class)), Arguments.of("loans/123/transactions?command=repayment", HttpMethod.POST, "createTransactionLoanCommandStrategy", mock(CreateTransactionLoanCommandStrategy.class)), Arguments.of("loans/123/transactions?command=creditBalanceRefund", HttpMethod.POST, "createTransactionLoanCommandStrategy", @@ -130,7 +133,8 @@ public class CommandStrategyProviderTest { private static Stream provideCommandStrategyResourceDetailsForErrors() { return Stream.of(Arguments.of("loans/123?command=reject", HttpMethod.POST), Arguments.of("loans/glimAccount/746?command=approve", HttpMethod.POST), Arguments.of("loans/123", HttpMethod.PUT), - Arguments.of("datatables/test_dt_table", HttpMethod.GET), Arguments.of("datatables", HttpMethod.GET)); + Arguments.of("datatables/test_dt_table", HttpMethod.GET), Arguments.of("datatables", HttpMethod.GET), + Arguments.of("loans//charges/123", HttpMethod.GET), Arguments.of("loans/123/charges/", HttpMethod.GET)); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java new file mode 100644 index 000000000..3e81a1dd6 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java @@ -0,0 +1,149 @@ +/** + * 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.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import java.util.stream.Stream; +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.infrastructure.core.api.MutableUriInfo; +import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource; +import org.apache.http.HttpStatus; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * The {@link GetChargeByIdCommandStrategy} test class. + */ +public class GetChargeByIdCommandStrategyTest { + + /** + * Query parameter provider. + * + * @return the test data stream + */ + private static Stream provideQueryParameters() { + return Stream.of(Arguments.of(null, 0), Arguments.of("fields=name,amountOrPercentage", 1)); + } + + /** + * Test {@link GetChargeByIdCommandStrategy#execute} happy path scenario. + */ + @ParameterizedTest + @MethodSource("provideQueryParameters") + public void testExecuteSuccessScenario(final String queryParameter, final int numberOfQueryParams) { + final TestContext testContext = new TestContext(); + + final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final Long chargeId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final BatchRequest request = getBatchRequest(loanId, chargeId, queryParameter); + final String responseBody = "someResponseBody"; + + given(testContext.loanChargesApiResource.retrieveLoanCharge(eq(loanId), eq(chargeId), any(UriInfo.class))).willReturn(responseBody); + + final BatchResponse response = testContext.subjectToTest.execute(request, testContext.uriInfo); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getRequestId()).isEqualTo(request.getRequestId()); + assertThat(response.getHeaders()).isEqualTo(request.getHeaders()); + assertThat(response.getBody()).isEqualTo(responseBody); + + verify(testContext.loanChargesApiResource).retrieveLoanCharge(eq(loanId), eq(chargeId), testContext.uriInfoCaptor.capture()); + MutableUriInfo mutableUriInfo = testContext.uriInfoCaptor.getValue(); + assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(numberOfQueryParams); + } + + /** + * Creates and returns a request with the given loan id and charge id. + * + * @param loanId + * the loan id + * @param chargeId + * the charge id + * @param queryParameter + * the query parameter + * @return BatchRequest + */ + private BatchRequest getBatchRequest(final Long loanId, final Long chargeId, final String queryParameter) { + final BatchRequest br = new BatchRequest(); + String relativeUrl = "loans/" + loanId + "/charges/" + chargeId; + + if (queryParameter != null) { + relativeUrl = relativeUrl + "?" + queryParameter; + } + + br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setRelativeUrl(relativeUrl); + br.setMethod(HttpMethod.GET); + br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setBody("{}"); + + return br; + } + + /** + * Private test context class used since testng runs in parallel to avoid state between tests + */ + private static class TestContext { + + /** + * The subject to test + */ + private final GetChargeByIdCommandStrategy subjectToTest; + + /** + * The mock uri info + */ + @Mock + private UriInfo uriInfo; + + /** + * The mock {@link LoanChargesApiResource} object. + */ + @Mock + private LoanChargesApiResource loanChargesApiResource; + + /** + * The uri info captor + */ + @Captor + private ArgumentCaptor uriInfoCaptor; + + /** + * Test Context constructor + */ + TestContext() { + MockitoAnnotations.openMocks(this); + subjectToTest = new GetChargeByIdCommandStrategy(loanChargesApiResource); + } + } +} 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 1d1ca330d..c29d6371f 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 @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.fineract.batch.command.internal.AdjustTransactionCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.domain.BatchRequest; @@ -43,6 +44,7 @@ import org.apache.fineract.integrationtests.common.BatchHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CollateralManagementHelper; import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; @@ -377,6 +379,69 @@ public class BatchApiTest { Assertions.assertEquals(HttpStatus.SC_OK, (long) response.get(3).getStatusCode(), "Verify Status Code 200 for Create Loan Charge"); } + /** + * Tests that a new charge was added to a newly created loan and charges are Collected properly 200(OK) status was + * returned for successful responses. It first creates a new client and apply a loan, then creates a new charge for + * the create loan and then fetches all the applied charges + * + * @see org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy + * @see org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy + */ + @Test + public void shouldReturnOkStatusForCreateAndGetChargeByIdCommand() { + final String loanProductJSON = new LoanProductTestBuilder() // + .withPrincipal("1000.00") // + .withNumberOfRepayments("24") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("2") // + .withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // + .withInterestTypeAsDecliningBalance() // + .currencyDetails("0", "100").build(null); + + final Long applyLoanRequestId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final Long approveLoanRequestId = applyLoanRequestId + 1; + final Long disburseLoanRequestId = approveLoanRequestId + 1; + final Long createChargeRequestId = disburseLoanRequestId + 1; + final Long getChargeByIdRequestId = createChargeRequestId + 1; + + // 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 charge object and get id + final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON()); + + final BatchRequest applyLoanRequest = BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId, productId); + + final BatchRequest approveLoanRequest = BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId); + + final BatchRequest disburseLoanRequest = BatchHelper.disburseLoanRequest(disburseLoanRequestId, approveLoanRequestId); + + final BatchRequest createChargeRequest = BatchHelper.createChargeRequest(createChargeRequestId, disburseLoanRequestId, chargeId); + + final BatchRequest getChargeByIdRequest = BatchHelper.getChargeByIdCommandStrategy(getChargeByIdRequestId, createChargeRequestId); + + // Create batch requests list + final List batchRequests = Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest, + createChargeRequest, getChargeByIdRequest); + + // Create batch responses list + final List responses = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, this.responseSpec, + BatchHelper.toJsonString(batchRequests)); + + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(0).getStatusCode(), "Verify Status Code 200 for Apply Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(1).getStatusCode(), "Verify Status Code 200 for Approve Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(2).getStatusCode(), "Verify Status Code 200 for Disburse Loan"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(3).getStatusCode(), "Verify Status Code 200 for Create Charge"); + Assertions.assertEquals(HttpStatus.SC_OK, responses.get(4).getStatusCode(), "Verify Status Code 200 for Get Charge By Id"); + } + /** * Tests that batch repayment for loans is happening properly. Collected properly 200(OK) status was returned for * successful responses. It first creates a new loan and then makes two repayments for it and then verifies that 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 93693c882..925764c67 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 @@ -349,10 +349,14 @@ public final class BatchHelper { * given requestId and reference * * @param requestId + * the batch request id. * @param reference + * the reference id. + * @param chargeId + * the charge id used for getting charge type. * @return BatchRequest */ - public static BatchRequest createChargeRequest(final Long requestId, final Long reference) { + public static BatchRequest createChargeRequest(final Long requestId, final Long reference, final Integer chargeId) { final BatchRequest br = new BatchRequest(); br.setRequestId(requestId); @@ -360,8 +364,12 @@ public final class BatchHelper { br.setMethod("POST"); br.setReference(reference); - final String body = "{\"chargeId\": \"2\", \"locale\": \"en\", \"amount\": \"100\", " - + "\"dateFormat\": \"dd MMMM yyyy\", \"dueDate\": \"29 April 2013\"}"; + final String dateFormat = "dd MMMM yyyy"; + final String dateString = LocalDate.now(Utils.getZoneIdOfTenant()).format(DateTimeFormatter.ofPattern(dateFormat)); + + final String body = String.format( + "{\"chargeId\": \"%d\", \"locale\": \"en\", \"amount\": \"11.15\", " + "\"dateFormat\": \"%s\", \"dueDate\": \"%s\"}", + chargeId, dateFormat, dateString); br.setBody(body); return br; @@ -388,6 +396,30 @@ public final class BatchHelper { return br; } + /** + * Creates and returns a {@link org.apache.fineract.batch.command.internal.GetChargeByIdCommandStrategy} request + * with given requestId and reference. + * + * @param requestId + * the request id + * @param reference + * the reference + * @return the {@link BatchRequest} + */ + public static BatchRequest getChargeByIdCommandStrategy(final Long requestId, final Long reference) { + + final BatchRequest br = new BatchRequest(); + String relativeUrl = "loans/$.loanId/charges/$.resourceId"; + + br.setRequestId(requestId); + br.setRelativeUrl(relativeUrl); + br.setMethod(HttpMethod.GET); + br.setReference(reference); + br.setBody("{}"); + + return br; + } + /** * Creates and returns a {@link org.apache.fineract.batch.command.internal.ActivateClientCommandStrategy} Request * with given requestId and reference.