This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 77fd4ff4c Fine tuning the batch APIs and minor adjustments
77fd4ff4c is described below
commit 77fd4ff4c1fc25cb8d2f9955a8b68c2b5657bfcb
Author: Arnold Galovics <[email protected]>
AuthorDate: Wed May 11 11:30:00 2022 +0200
Fine tuning the batch APIs and minor adjustments
---
.../fineract/client/util/FineractClient.java | 3 +
.../fineract/batch/api/BatchApiResource.java | 5 +-
.../batch/command/CommandStrategyProvider.java | 5 +
.../internal/GetLoanByIdCommandStrategy.java | 138 +++++++++++++
.../GetTransactionByIdCommandStrategy.java | 87 ++++++++
.../populator/AbstractWorkbookPopulator.java | 2 +-
.../infrastructure/core/api/MutableUriInfo.java | 147 +++++++++++++
.../dataqueries/api/DatatablesApiResource.java | 4 +-
.../api/DatatablesApiResourceSwagger.java | 58 ++----
.../monetary/api/CurrenciesApiResourceSwagger.java | 4 +-
.../charge/api/ChargesApiResourceSwagger.java | 4 +
.../client/api/ClientsApiResourceSwagger.java | 4 +
.../api/LoanTransactionsApiResourceSwagger.java | 21 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 230 ++++++++++++++++++++-
.../portfolio/loanaccount/domain/Loan.java | 16 +-
.../loanschedule/domain/LoanApplicationTerms.java | 10 +-
.../service/LoanReadPlatformServiceImpl.java | 2 +-
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 11 +-
.../api/LoanProductsApiResourceSwagger.java | 121 +++++++++--
.../api/SelfLoansApiResourceSwagger.java | 2 +
.../batch/command/CommandStrategyProviderTest.java | 134 ++++++++++++
.../internal/GetLoanByIdCommandStrategyTest.java | 186 +++++++++++++++++
.../GetTransactionByIdCommandStrategyTest.java | 205 ++++++++++++++++++
.../portfolio/loanaccount/domain/LoanTest.java | 88 ++++++++
.../fineract/integrationtests/BatchApiTest.java | 207 ++++++++++++++++++-
.../integrationtests/common/BatchHelper.java | 98 ++++++++-
26 files changed, 1703 insertions(+), 89 deletions(-)
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 dcaa1ccff..fa141a63b 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
@@ -77,6 +77,7 @@ import org.apache.fineract.client.services.JournalEntriesApi;
import org.apache.fineract.client.services.ListReportMailingJobHistoryApi;
import org.apache.fineract.client.services.LoanChargesApi;
import org.apache.fineract.client.services.LoanCollateralApi;
+import org.apache.fineract.client.services.LoanDisbursementDetailsApi;
import org.apache.fineract.client.services.LoanProductsApi;
import org.apache.fineract.client.services.LoanReschedulingApi;
import org.apache.fineract.client.services.LoanTransactionsApi;
@@ -208,6 +209,7 @@ public final class FineractClient {
public final LoanProductsApi loanProducts;
public final LoanReschedulingApi loanSchedules;
public final LoansApi loans;
+ public final LoanDisbursementDetailsApi loanDisbursementDetails;
public final LoanTransactionsApi loanTransactions;
public final MakerCheckerOr4EyeFunctionalityApi makerCheckers;
public final MappingFinancialActivitiesToAccountsApi
financialActivyAccountMappings;
@@ -318,6 +320,7 @@ public final class FineractClient {
loanProducts = retrofit.create(LoanProductsApi.class);
loanSchedules = retrofit.create(LoanReschedulingApi.class);
loans = retrofit.create(LoansApi.class);
+ loanDisbursementDetails =
retrofit.create(LoanDisbursementDetailsApi.class);
loanTransactions = retrofit.create(LoanTransactionsApi.class);
makerCheckers =
retrofit.create(MakerCheckerOr4EyeFunctionalityApi.class);
financialActivyAccountMappings =
retrofit.create(MappingFinancialActivitiesToAccountsApi.class);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
index f8fbcb430..f49003e41 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/batch/api/BatchApiResource.java
@@ -20,6 +20,7 @@ package org.apache.fineract.batch.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
@@ -92,9 +93,9 @@ public class BatchApiResource {
@Operation(summary = "Batch requests in a single transaction", description
= "The Apache Fineract Batch API is also capable of executing all the requests
in a single transaction, by setting a Query Parameter,
\"enclosingTransaction=true\". So, if one or more of the requests in a batch
returns an erroneous response all of the Data base transactions made by other
successful requests will be rolled back.\n"
+ "\n"
+ "If there has been a rollback in a transaction then a single
response will be provided, with a '400' status code and a body consisting of
the error details of the first failed request.")
- @RequestBody(required = true, content = @Content(schema =
@Schema(implementation = BatchApiResourceSwagger.PostBatchesRequest.class,
description = "request body")))
+ @RequestBody(required = true, content = @Content(array =
@ArraySchema(schema = @Schema(implementation = BatchRequest.class, description
= "request body"))))
@ApiResponses({
- @ApiResponse(responseCode = "200", description = "Success",
content = @Content(schema = @Schema(implementation = BatchResponse.class))) })
+ @ApiResponse(responseCode = "200", description = "Success",
content = @Content(array = @ArraySchema(schema = @Schema(implementation =
BatchResponse.class)))) })
public String handleBatchRequests(
@DefaultValue("false") @QueryParam("enclosingTransaction")
@Parameter(description = "enclosingTransaction", required = false) final
boolean enclosingTransaction,
@Parameter(hidden = true) final String jsonRequestString, @Context
UriInfo uriInfo) {
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 c8316fe91..d14afd0d7 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
@@ -84,6 +84,9 @@ public class CommandStrategyProvider {
this.commandStrategies.put(CommandContext.resource("clients").method("POST").build(),
"createClientCommandStrategy");
this.commandStrategies.put(CommandContext.resource("clients\\/\\d+").method("PUT").build(),
"updateClientCommandStrategy");
this.commandStrategies.put(CommandContext.resource("loans").method("POST").build(),
"applyLoanCommandStrategy");
+
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+").method("GET").build(),
"getLoanByIdCommandStrategy");
+
this.commandStrategies.put(CommandContext.resource("loans/\\d+(\\?(\\w+(?:\\=[\\w,]+|&)+)+)").method("GET").build(),
+ "getLoanByIdCommandStrategy");
this.commandStrategies.put(CommandContext.resource("savingsaccounts").method("POST").build(),
"applySavingsCommandStrategy");
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("POST").build(),
"createChargeCommandStrategy");
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("GET").build(),
@@ -98,6 +101,8 @@ public class CommandStrategyProvider {
"disburseLoanCommandStrategy");
this.commandStrategies.put(CommandContext.resource("rescheduleloans\\/\\d+\\?command=approve").method("POST").build(),
"approveLoanRescheduleCommandStrategy");
+
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\/\\d+").method("GET").build(),
+ "getTransactionByIdCommandStrategy");
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategy.java
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategy.java
new file mode 100644
index 000000000..7408aee42
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategy.java
@@ -0,0 +1,138 @@
+/**
+ * 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 java.util.HashMap;
+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.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoansApiResource;
+import org.apache.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link CommandStrategy} and get loan by id. It passes the
contents of the body from the BatchRequest to
+ * {@link LoansApiResource} and gets back the response. This class will also
catch any errors raised by
+ * {@link LoansApiResource} and map those errors to appropriate status codes
in BatchResponse.
+ */
+@Component
+@RequiredArgsConstructor
+public class GetLoanByIdCommandStrategy implements CommandStrategy {
+
+ /**
+ * Loans api resource {@link LoansApiResource}.
+ */
+ private final LoansApiResource loansApiResource;
+
+ @Override
+ public BatchResponse execute(final BatchRequest request, final 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();
+ Long loanId;
+
+ // Try-catch blocks to map exceptions to appropriate status codes
+ try {
+ // uriInfo will contain the query parameter value(s) that are sent
in the actual batch uri.
+ // for example: batches?enclosingTransaction=true
+ // But the query parameters that are sent in the batch relative
url has to be sent to
+ // LoansApiResource.retrieveLoan
+ // To use the relative url query parameters
+ // - Parse and fetch the query parameters sent in the relative url
+ // (loans/66?fields=id,principal,annualInterestRate)
+ // - Add them to the UriInfo query parameters list
+ // - Call loansApiResource.retrieveLoan(loanId, false, uriInfo)
+ // - Remove the relative url query parameters from UriInfo in the
finally (after loan details are retrieved)
+ if (relativeUrl.indexOf('?') > 0) {
+ loanId =
Long.parseLong(StringUtils.substringBetween(relativeUrl, "/", "?"));
+ Map<String, String> queryParameters =
getQueryParameters(relativeUrl);
+
+ // Add the query parameters sent in the relative URL to UriInfo
+ addQueryParametersToUriInfo(parameterizedUriInfo,
queryParameters);
+ } else {
+ loanId =
Long.parseLong(StringUtils.substringAfter(relativeUrl, "/"));
+ }
+
+ // Calls 'retrieveLoan' function from 'LoansApiResource' to
+ // get the loan details based on the loan id
+ responseBody = loansApiResource.retrieveLoan(loanId, false,
parameterizedUriInfo);
+
+ response.setStatusCode(HttpStatus.SC_OK);
+
+ // Sets the response after retrieving the loan
+ response.setBody(responseBody);
+
+ } catch (RuntimeException e) {
+
+ // Gets an object of type ErrorInfo, containing information about
+ // raised exception
+ ErrorInfo ex = ErrorHandler.handler(e);
+
+ response.setStatusCode(ex.getStatusCode());
+ response.setBody(ex.getMessage());
+ }
+
+ return response;
+ }
+
+ /**
+ * Get query parameters from relative URL.
+ *
+ * @param relativeUrl
+ * the relative URL
+ * @return the query parameters in a map
+ */
+ private Map<String, String> getQueryParameters(final String relativeUrl) {
+ final String queryParameterStr =
StringUtils.substringAfter(relativeUrl, "?");
+ final String[] queryParametersArray =
StringUtils.split(queryParameterStr, "&");
+ final Map<String, String> queryParametersMap = new HashMap<>();
+ for (String parameterStr : queryParametersArray) {
+ String[] keyValue = StringUtils.split(parameterStr, "=");
+ queryParametersMap.put(keyValue[0], keyValue[1]);
+ }
+ return queryParametersMap;
+ }
+
+ /**
+ * Add query parameters(received in the relative URL) to URI info query
parameters.
+ *
+ * @param uriInfo
+ * the URI info
+ * @param queryParameters
+ * the query parameters
+ */
+ private void addQueryParametersToUriInfo(final MutableUriInfo uriInfo,
final Map<String, String> queryParameters) {
+ for (Map.Entry<String, String> entry : queryParameters.entrySet()) {
+ uriInfo.addAdditionalQueryParameter(entry.getKey(),
entry.getValue());
+ }
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.java
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.java
new file mode 100644
index 000000000..e736c6a54
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategy.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 javax.ws.rs.core.UriInfo;
+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.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
+import
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionsApiResource;
+import org.apache.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link CommandStrategy} to retrieve a transaction by id. 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 GetTransactionByIdCommandStrategy 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());
+
+ // Get the loan and transaction ids for use in
loanTransactionsApiResource
+ final List<String> pathParameters =
Splitter.on('/').splitToList(request.getRelativeUrl());
+ final Long loanId = Long.parseLong(pathParameters.get(1));
+ final Long transactionId = Long.parseLong(pathParameters.get(3));
+
+ // Try-catch blocks to map exceptions to appropriate status codes
+ try {
+
+ // Calls 'retrieveTransaction' function from
'loanTransactionsApiResource'
+ responseBody =
loanTransactionsApiResource.retrieveTransaction(loanId, transactionId, uriInfo);
+
+ response.setStatusCode(HttpStatus.SC_OK);
+
+ // Sets the body of the response after retrieving the transaction
+ response.setBody(responseBody);
+
+ } catch (RuntimeException e) {
+
+ // Gets an object of type ErrorInfo, containing information about
+ // raised exception
+ ErrorInfo ex = ErrorHandler.handler(e);
+
+ response.setStatusCode(ex.getStatusCode());
+ response.setBody(ex.getMessage());
+ }
+
+ return response;
+ }
+
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/AbstractWorkbookPopulator.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/AbstractWorkbookPopulator.java
index 0251a234a..8a9bfe7f3 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/AbstractWorkbookPopulator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/AbstractWorkbookPopulator.java
@@ -46,7 +46,7 @@ public abstract class AbstractWorkbookPopulator implements
WorkbookPopulator {
}
protected void writeLong(int colIndex, Row row, long value) {
- row.createCell(colIndex).setCellValue(value);
+ row.createCell(colIndex).setCellValue((double) value);
}
protected void writeString(int colIndex, Row row, String value) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/MutableUriInfo.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/MutableUriInfo.java
new file mode 100644
index 000000000..cba28fa5d
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/MutableUriInfo.java
@@ -0,0 +1,147 @@
+/**
+ * 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.infrastructure.core.api;
+
+import java.net.URI;
+import java.util.List;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class MutableUriInfo implements UriInfo {
+
+ private final UriInfo delegate;
+
+ @Getter
+ private final MultivaluedMap<String, String> additionalQueryParameters =
new MultivaluedHashMap<>();
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters() {
+ return fillAdditionalQueryParameters(delegate.getQueryParameters());
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+ return
fillAdditionalQueryParameters(delegate.getQueryParameters(decode));
+ }
+
+ private MultivaluedMap<String, String>
fillAdditionalQueryParameters(MultivaluedMap<String, String> queryParameters) {
+ MultivaluedMap<String, String> newQueryParameters = new
MultivaluedHashMap<>(queryParameters);
+ newQueryParameters.putAll(additionalQueryParameters);
+ return newQueryParameters;
+ }
+
+ public void addAdditionalQueryParameter(String key, String value) {
+ additionalQueryParameters.add(key, value);
+ }
+
+ public void putAdditionalQueryParameter(String key, List<String> values) {
+ additionalQueryParameters.put(key, values);
+ }
+
+ @Override
+ public String getPath() {
+ return delegate.getPath();
+ }
+
+ @Override
+ public String getPath(boolean decode) {
+ return delegate.getPath(decode);
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments() {
+ return delegate.getPathSegments();
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(boolean decode) {
+ return delegate.getPathSegments(decode);
+ }
+
+ @Override
+ public URI getRequestUri() {
+ return delegate.getRequestUri();
+ }
+
+ @Override
+ public UriBuilder getRequestUriBuilder() {
+ return delegate.getRequestUriBuilder();
+ }
+
+ @Override
+ public URI getAbsolutePath() {
+ return delegate.getAbsolutePath();
+ }
+
+ @Override
+ public UriBuilder getAbsolutePathBuilder() {
+ return delegate.getAbsolutePathBuilder();
+ }
+
+ @Override
+ public URI getBaseUri() {
+ return delegate.getBaseUri();
+ }
+
+ @Override
+ public UriBuilder getBaseUriBuilder() {
+ return delegate.getBaseUriBuilder();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters() {
+ return delegate.getPathParameters();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+ return delegate.getPathParameters(decode);
+ }
+
+ @Override
+ public List<String> getMatchedURIs() {
+ return delegate.getMatchedURIs();
+ }
+
+ @Override
+ public List<String> getMatchedURIs(boolean decode) {
+ return delegate.getMatchedURIs(decode);
+ }
+
+ @Override
+ public List<Object> getMatchedResources() {
+ return delegate.getMatchedResources();
+ }
+
+ @Override
+ public URI resolve(URI uri) {
+ return delegate.resolve(uri);
+ }
+
+ @Override
+ public URI relativize(URI uri) {
+ return delegate.relativize(uri);
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
index 4918f77d4..41ceaec4d 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
@@ -306,7 +306,7 @@ public class DatatablesApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Entry in Data Table (One to One)",
description = "Updates the row (if it exists) of the data table.")
- @RequestBody(required = true, content = @Content(schema =
@Schema(implementation =
DatatablesApiResourceSwagger.PutDataTablesAppTableIdRequest.class)))
+ @RequestBody(required = true, content = @Content(schema =
@Schema(implementation = String.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
DatatablesApiResourceSwagger.PutDataTablesAppTableIdResponse.class))) })
public String updateDatatableEntryOnetoOne(@PathParam("datatable")
@Parameter(description = "datatable") final String datatable,
@@ -328,7 +328,7 @@ public class DatatablesApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Entry in Data Table (One to Many)",
description = "Updates the row (if it exists) of the data table.")
- @RequestBody(required = true, content = @Content(schema =
@Schema(implementation =
DatatablesApiResourceSwagger.PutDataTablesAppTableIdDatatableIdRequest.class)))
+ @RequestBody(required = true, content = @Content(schema =
@Schema(implementation = String.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
DatatablesApiResourceSwagger.PutDataTablesAppTableIdDatatableIdResponse.class)))
})
public String updateDatatableEntryOneToMany(@PathParam("datatable")
@Parameter(description = "datatable") final String datatable,
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java
index 379d51fe4..7ae68b4c9 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResourceSwagger.java
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.dataqueries.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
+import java.util.Map;
import
org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
@@ -218,17 +219,6 @@ final class DatatablesApiResourceSwagger {
public List<ResultsetRowData> data;
}
- @Schema(description = "PutDataTablesAppTableIdRequest")
- public static final class PutDataTablesAppTableIdRequest {
-
- private PutDataTablesAppTableIdRequest() {
-
- }
-
- @Schema(example = "Livestock sales updated")
- public String BusinessDescription;
- }
-
@Schema(description = "PutDataTablesAppTableIdResponse")
public static final class PutDataTablesAppTableIdResponse {
@@ -236,40 +226,15 @@ final class DatatablesApiResourceSwagger {
}
- static final class PutDataTablesAppTableIdResponseChanges {
-
- private PutDataTablesAppTableIdResponseChanges() {}
-
- @Schema(example = "Livestock sales updated")
- public String BusinessDescription;
- }
-
+ @Schema(example = "1")
+ public Long officeId;
+ @Schema(example = "1")
+ public Long clientId;
+ @Schema(example = "1")
+ public Long loanId;
@Schema(example = "1")
public Long resourceId;
- public PutDataTablesAppTableIdResponseChanges changes;
- }
-
- @Schema(description = "PutDataTablesAppTableIdDatatableIdRequest")
- public static final class PutDataTablesAppTableIdDatatableIdRequest {
-
- private PutDataTablesAppTableIdDatatableIdRequest() {
-
- }
-
- @Schema(example = "01 June 1982")
- public String DateOfBirth;
- @Schema(example = "5")
- public Long Education_cdHighest;
- @Schema(example = "June")
- public String Name;
- @Schema(example = "More notes")
- public String OtherNotes;
- @Schema(example = "20")
- public Long PointsScore;
- @Schema(example = "dd MMMM yyyy")
- public String dateFormat;
- @Schema(example = "en")
- public String locale;
+ public Map<String, Object> changes;
}
@Schema(description = "PutDataTablesAppTableIdDatatableIdResponse ")
@@ -279,8 +244,15 @@ final class DatatablesApiResourceSwagger {
}
+ @Schema(example = "1")
+ public Long officeId;
+ @Schema(example = "1")
+ public Long clientId;
+ @Schema(example = "1")
+ public Long loanId;
@Schema(example = "1")
public Long resourceId;
+ public Map<String, Object> changes;
}
@Schema(description = "DeleteDataTablesDatatableAppTableIdResponse ")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
index 4b2ba30bc..4a59beda4 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java
@@ -51,7 +51,7 @@ final class CurrenciesApiResourceSwagger {
@Schema(example = "[\"KES\",\n" + " \"BND\",\n" + "
\"LBP\",\n" + " \"GHC\",\n" + " \"USD\",\n"
+ " \"XOF\",\n" + " \"AED\",\n" + "
\"AMD\"]")
- public String currencies;
+ public String[] currencies;
}
@@ -64,6 +64,6 @@ final class CurrenciesApiResourceSwagger {
@Schema(example = "[\"KES\",\n" + " \"BND\",\n" + "
\"LBP\",\n" + " \"GHC\",\n" + " \"USD\",\n"
+ " \"XOF\",\n" + " \"AED\",\n" + "
\"AMD\"]")
- public String currencies;
+ public String[] currencies;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResourceSwagger.java
index c832dc0b1..5aece98ff 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiResourceSwagger.java
@@ -135,6 +135,10 @@ final class ChargesApiResourceSwagger {
public Integer chargePaymentMode;
@Schema(example = "true")
public String active;
+ @Schema(example = "dd MMMM")
+ public String monthDayFormat;
+ @Schema(example = "false")
+ public String penalty;
}
@Schema(description = "PostChargesResponse")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java
index 4c9fce88a..2fe5583d3 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResourceSwagger.java
@@ -302,6 +302,8 @@ final class ClientsApiResourceSwagger {
public String externalId;
@Schema(example = "Client_LastName")
public String lastname;
+ @Schema(example = "[2013, 1, 1]")
+ public LocalDate dateOfBirth;
@Schema(example = "1")
public Integer groupId;
@Schema(example = "dd MMMM yyyy")
@@ -312,6 +314,8 @@ final class ClientsApiResourceSwagger {
public Boolean active;
@Schema(example = "04 March 2009")
public String activationDate;
+ @Schema(example = "+353851239876")
+ public String mobileNo;
@Schema(description = "List of PostClientsDatatable")
public List<PostClientsDatatable> datatables;
@Schema(description = "Address requests")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index ea6c42087..b66f1af42 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -133,6 +133,8 @@ final class LoanTransactionsApiResourceSwagger {
public GetLoansType type;
@Schema(example = "[2012, 5, 14]")
public LocalDate date;
+ @Schema(example = "[2012, 5, 14]")
+ public LocalDate submittedOnDate;
@Schema(example = "false")
public Boolean manuallyReversed;
public GetLoansCurrency currency;
@@ -146,6 +148,21 @@ final class LoanTransactionsApiResourceSwagger {
public static final class PostLoansLoanIdTransactionsRequest {
private PostLoansLoanIdTransactionsRequest() {}
+
+ @Schema(example = "en_GB")
+ public String locale;
+ @Schema(example = "dd MMMM yyyy")
+ public String dateFormat;
+ @Schema(example = "[2012, 5, 25]")
+ public LocalDate transactionDate;
+ @Schema(example = "50000.00")
+ public Double transactionAmount;
+ @Schema(example = "An optional note about why your adjusting or
changing the transaction.")
+ public String note;
+ @Schema(example = "3e7791ce-aa10-11ec-b909-0242ac120002")
+ public String externalId;
+ @Schema(example = "3")
+ public Integer paymentTypeId;
}
@Schema(description = "PostLoansLoanIdTransactionsResponse")
@@ -170,8 +187,8 @@ final class LoanTransactionsApiResourceSwagger {
public String locale;
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
- @Schema(example = "25 May 2012")
- public String transactionDate;
+ @Schema(example = "[2012, 5, 25]")
+ public LocalDate transactionDate;
@Schema(example = "50,000.00")
public Double transactionAmount;
@Schema(example = "An optional note about why your adjusting or
changing the transaction.")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index dfc05cbf3..ce10d20ca 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.List;
import java.util.Set;
/**
@@ -230,6 +231,124 @@ final class LoansApiResourceSwagger {
public String disbursedByLastname;
@Schema(example = "[2012, 4, 10]")
public LocalDate expectedMaturityDate;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate closedOnDate;
+ }
+
+ static final class GetLoansLoanIdRepaymentSchedule {
+
+ private GetLoansLoanIdRepaymentSchedule() {}
+
+ public GetLoansLoanIdCurrency currency;
+ @Schema(example = "30")
+ public Long loanTermInDays;
+ @Schema(example = "200.000000")
+ public Double totalPrincipalDisbursed;
+ @Schema(example = "200.00")
+ public Double totalPrincipalExpected;
+ @Schema(example = "200.00")
+ public Double totalPrincipalPaid;
+ @Schema(example = "0.00")
+ public Double totalInterestCharged;
+ @Schema(example = "0.00")
+ public Double totalFeeChargesCharged;
+ @Schema(example = "0.00")
+ public Double totalPenaltyChargesCharged;
+ @Schema(example = "0.00")
+ public Double totalWaived;
+ @Schema(example = "0.00")
+ public Double totalWrittenOff;
+ @Schema(example = "200.00")
+ public Double totalRepaymentExpected;
+ @Schema(example = "200.00")
+ public Double totalPaidInAdvance;
+ @Schema(example = "0.00")
+ public Double totalPaidLate;
+ @Schema(example = "0.00")
+ public Double totalOutstanding;
+ public Set<GetLoansLoanIdRepaymentPeriod> periods;
+ }
+
+ static final class GetLoansLoanIdRepaymentPeriod {
+
+ private GetLoansLoanIdRepaymentPeriod() {}
+
+ @Schema(example = "1")
+ public Integer period;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate fromDate;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate dueDate;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate obligationsMetOnDate;
+ @Schema(example = "true")
+ public Boolean complete;
+ @Schema(example = "30")
+ public Long daysInPeriod;
+ @Schema(example = "200.000000")
+ public Double principalOriginalDue;
+ @Schema(example = "200.000000")
+ public Double principalDue;
+ @Schema(example = "200.000000")
+ public Double principalPaid;
+ @Schema(example = "0")
+ public Long principalWrittenOff;
+ @Schema(example = "20.000000")
+ public Double principalOutstanding;
+ @Schema(example = "20.000000")
+ public Double principalLoanBalanceOutstanding;
+ @Schema(example = "0")
+ public Long interestOriginalDue;
+ @Schema(example = "0")
+ public Long interestDue;
+ @Schema(example = "0")
+ public Long interestPaid;
+ @Schema(example = "0")
+ public Long interestWaived;
+ @Schema(example = "0")
+ public Long interestWrittenOff;
+ @Schema(example = "0")
+ public Long interestOutstanding;
+ @Schema(example = "0")
+ public Long feeChargesDue;
+ @Schema(example = "20")
+ public Long feeChargesPaid;
+ @Schema(example = "20")
+ public Long feeChargesWaived;
+ @Schema(example = "20")
+ public Long feeChargesWrittenOff;
+ @Schema(example = "20")
+ public Long feeChargesOutstanding;
+ @Schema(example = "20")
+ public Long penaltyChargesDue;
+ @Schema(example = "20")
+ public Long penaltyChargesPaid;
+ @Schema(example = "20")
+ public Long penaltyChargesWaived;
+ @Schema(example = "20")
+ public Long penaltyChargesWrittenOff;
+ @Schema(example = "20")
+ public Long penaltyChargesOutstanding;
+ @Schema(example = "20.000000")
+ public Double totalOriginalDueForPeriod;
+ @Schema(example = "20.000000")
+ public Double totalDueForPeriod;
+ @Schema(example = "20.000000")
+ public Double totalPaidForPeriod;
+ @Schema(example = "20.000000")
+ public Double totalPaidInAdvanceForPeriod;
+ @Schema(example = "20")
+ public Long totalPaidLateForPeriod;
+ @Schema(example = "20")
+ public Long totalWaivedForPeriod;
+ @Schema(example = "20")
+ public Long totalWrittenOffForPeriod;
+ @Schema(example = "200.000000")
+ public Double totalOutstandingForPeriod;
+ @Schema(example = "20")
+ public Long totalActualCostOfLoanForPeriod;
+ @Schema(example = "200.000000")
+ public Double totalInstallmentAmountForPeriod;
}
static final class GetLoansLoanIdSummary {
@@ -448,7 +567,11 @@ final class LoansApiResourceSwagger {
public GetLoansLoanIdLoanType loanType;
public GetLoansLoanIdCurrency currency;
@Schema(example = "1000000")
- public Long principal;
+ public BigDecimal principal;
+ @Schema(example = "1000.000000")
+ public Double approvedPrincipal;
+ @Schema(example = "200.000000")
+ public Double netDisbursalAmount;
@Schema(example = "12")
public Integer termFrequency;
public GetLoansLoanIdTermPeriodFrequencyType termPeriodFrequencyType;
@@ -471,6 +594,7 @@ final class LoansApiResourceSwagger {
public Integer transactionProcessingStrategyId;
public GetLoansLoanIdTimeline timeline;
public GetLoansLoanIdSummary summary;
+ public GetLoansLoanIdRepaymentSchedule repaymentSchedule;
}
@Schema(description = "GetLoansResponse")
@@ -486,8 +610,20 @@ final class LoansApiResourceSwagger {
@Schema(description = "PostLoansRequest")
public static final class PostLoansRequest {
+ static final class PostLoansDisbursementData {
+
+ private PostLoansDisbursementData() {}
+
+ @Schema(example = "[2013, 11, 1]")
+ public LocalDate expectedDisbursementDate;
+ @Schema(example = "1000.00")
+ public Double principal;
+ }
+
private PostLoansRequest() {}
+ @Schema(example = "1")
+ public Long clientId;
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
@Schema(example = "en_GB")
@@ -495,7 +631,7 @@ final class LoansApiResourceSwagger {
@Schema(example = "1")
public Integer productId;
@Schema(example = "100,000.00")
- public Double principal;
+ public String principal;
@Schema(example = "12")
public Integer loanTermFrequency;
@Schema(example = "2")
@@ -522,6 +658,14 @@ final class LoansApiResourceSwagger {
public Integer transactionProcessingStrategyId;
@Schema(example = "360", allowableValues = "1, 360, 364, 36")
public Integer daysInYearType;
+ @Schema(example = "individual")
+ public String loanType;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate submittedOnDate;
+ @Schema(example = "786444UUUYYH7")
+ public String externalId;
+ @Schema(description = "List of PostLoansDisbursementData")
+ public List<PostLoansDisbursementData> disbursementData;
}
@Schema(description = "PostLoansResponse")
@@ -583,6 +727,14 @@ final class LoansApiResourceSwagger {
@Schema(example = "0")
public Long totalOutstanding;
public Set<PostLoansRepaymentSchedulePeriods> periods;
+ @Schema(example = "2")
+ public Integer officeId;
+ @Schema(example = "1")
+ public Integer clientId;
+ @Schema(example = "1")
+ public Integer loanId;
+ @Schema(example = "1")
+ public Integer resourceId;
}
@Schema(description = "PutLoansLoanIdRequest")
@@ -670,6 +822,16 @@ final class LoansApiResourceSwagger {
private PostLoansLoanIdRequest() {}
+ static final class PostLoansLoanIdDisbursementData {
+
+ private PostLoansLoanIdDisbursementData() {}
+
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate expectedDisbursementDate;
+ @Schema(example = "22000")
+ public Double principal;
+ }
+
@Schema(example = "2")
public Integer toLoanOfficerId;
@Schema(example = "02 September 2014")
@@ -680,6 +842,24 @@ final class LoansApiResourceSwagger {
public String dateFormat;
@Schema(example = "")
public Integer fromLoanOfficerId;
+ @Schema(example = "3e7791ce-aa10-11ec-b909-0242ac120002")
+ public String externalId;
+ @Schema(example = "5000.33")
+ public String transactionAmount;
+ @Schema(example = "Description of disbursement details.")
+ public String note;
+ @Schema(example = "[2012, 4, 12]")
+ public LocalDate actualDisbursementDate;
+ @Schema(example = "3")
+ public Integer paymentTypeId;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate approvedOnDate;
+ @Schema(example = "1000")
+ public String approvedLoanAmount;
+ @Schema(example = "[2012, 4, 10]")
+ public LocalDate expectedDisbursementDate;
+ @Schema(description = "List of PostLoansLoanIdDisbursementData")
+ public List<PostLoansLoanIdDisbursementData> disbursementData;
}
@Schema(description = "PostLoansLoanIdResponse")
@@ -687,6 +867,50 @@ final class LoansApiResourceSwagger {
private PostLoansLoanIdResponse() {}
+ static final class PostLoansLoanIdStatus {
+
+ private PostLoansLoanIdStatus() {}
+
+ @Schema(example = "300")
+ public Integer id;
+ @Schema(example = "loanStatusType.approved")
+ public String code;
+ @Schema(example = "Approved")
+ public String value;
+ @Schema(example = "false")
+ public Boolean pendingApproval;
+ @Schema(example = "false")
+ public Boolean waitingForDisbursal;
+ @Schema(example = "true")
+ public Boolean active;
+ @Schema(example = "false")
+ public Boolean closedObligationsMet;
+ @Schema(example = "false")
+ public Boolean closedWrittenOff;
+ @Schema(example = "false")
+ public Boolean closedRescheduled;
+ @Schema(example = "false")
+ public Boolean closed;
+ @Schema(example = "false")
+ public Boolean overpaid;
+ }
+
+ static final class PostLoansLoanIdChanges {
+
+ private PostLoansLoanIdChanges() {}
+
+ @Schema(example = "en")
+ public String locale;
+ @Schema(example = "dd MMMM yyyy")
+ public String dateFormat;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate approvedOnDate;
+ @Schema(example = "Loan approval note")
+ public String note;
+ @Schema(description = "PostLoansLoanIdStatus")
+ public PostLoansLoanIdStatus status;
+ }
+
@Schema(example = "2")
public Integer officeId;
@Schema(example = "6")
@@ -695,5 +919,7 @@ final class LoansApiResourceSwagger {
public Integer loanId;
@Schema(example = "3")
public Integer resourceId;
+ @Schema(description = "PostLoansLoanIdChanges")
+ public PostLoansLoanIdChanges changes;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 12a804bd8..222e4a0e9 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -45,6 +45,7 @@ import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@@ -1953,7 +1954,7 @@ public class Loan extends AbstractPersistableCustom {
private void removeChargesByDisbursementID(Long id) {
List<LoanCharge> tempCharges = new ArrayList<>();
- for (LoanCharge charge : this.charges) {
+ for (LoanCharge charge : getCharges()) {
LoanTrancheDisbursementCharge transCharge =
charge.getTrancheDisbursementCharge();
if (transCharge != null &&
id.equals(transCharge.getloanDisbursementDetails().getId())) {
tempCharges.add(charge);
@@ -1966,7 +1967,7 @@ public class Loan extends AbstractPersistableCustom {
private List<Long> fetchLoanTrancheChargeIds() {
List<Long> list = new ArrayList<>();
- for (LoanCharge charge : this.charges) {
+ for (LoanCharge charge : getCharges()) {
if (charge.isTrancheDisbursementCharge() && charge.isActive()) {
list.add(charge.getId());
}
@@ -6887,4 +6888,15 @@ public class Loan extends AbstractPersistableCustom {
this.netDisbursalAmount =
adjustedAmount.subtract(this.deriveSumTotalOfChargesDueAtDisbursement());
}
+ /**
+ * Get the charges.
+ *
+ * @return the charges
+ */
+ public Collection<LoanCharge> getCharges() {
+ // At the time of loan creation, "this.charges" will be null if no
charges found in the request.
+ // In that case, fetch loan (before commit) will return null for the
charges.
+ // Return empty set instead of null to avoid NPE
+ return Optional.ofNullable(this.charges).orElse(new HashSet<>());
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 4759d4b27..49b996356 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -27,6 +27,7 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -53,13 +54,10 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import
org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+@Slf4j
public final class LoanApplicationTerms {
- private static final Logger LOG =
LoggerFactory.getLogger(LoanApplicationTerms.class);
-
private final ApplicationCurrency currency;
private final Calendar loanCalendar;
@@ -675,7 +673,7 @@ public final class LoanApplicationTerms {
case INVALID:
break;
case WHOLE_TERM:
- LOG.error("TODO Implement getPeriodEndDate for WHOLE_TERM");
+ log.error("TODO Implement getPeriodEndDate for WHOLE_TERM");
break;
}
return dueRepaymentPeriodDate;
@@ -1080,7 +1078,7 @@ public final class LoanApplicationTerms {
periodicInterestRate =
oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break;
case WHOLE_TERM:
- LOG.error("TODO Implement periodicInterestRate for
WHOLE_TERM");
+ log.error("TODO Implement periodicInterestRate for
WHOLE_TERM");
break;
}
break;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index c9504b4b2..9afbe449e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -2109,7 +2109,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService {
+ " (SUM(COALESCE(mr.principal_completed_derived, 0)) " +
" + SUM(COALESCE(mr.interest_completed_derived, 0)) "
+ " + SUM(COALESCE(mr.fee_charges_completed_derived, 0)) "
+ "+ SUM(COALESCE(mr.penalty_charges_completed_derived,
0))) > 0";
- BigDecimal bigDecimal = this.jdbcTemplate.queryForObject(sql,
BigDecimal.class, new Object[] { loanId }); // NOSONAR
+ BigDecimal bigDecimal = this.jdbcTemplate.queryForObject(sql,
BigDecimal.class, loanId); // NOSONAR
return new PaidInAdvanceData(bigDecimal);
} catch (DataAccessException e) {
return new PaidInAdvanceData(new BigDecimal(0));
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index ee5772b7a..e1493b608 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -546,7 +546,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
createStandingInstruction(loan);
postJournalEntries(loan, existingTransactionIds,
existingReversedTransactionIds);
-
}
final Set<LoanCharge> loanCharges = loan.charges();
@@ -585,9 +584,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.LOAN_DISBURSAL,
constructEntityMap(BusinessEntity.LOAN, loan));
+
+ Long entityId = loan.getId();
+
+ // During a disbursement, the entityId should be the disbursement
transaction id
+ if (!isAccountTransfer) {
+ entityId =
loan.getLoanTransactions().get(loan.getLoanTransactions().size() - 1).getId();
+ }
+
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
- .withEntityId(loan.getId()) //
+ .withEntityId(entityId) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 6d18aaf66..28ead7a7b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -21,8 +21,11 @@ package org.apache.fineract.portfolio.loanproduct.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
+import
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import org.apache.fineract.portfolio.rate.data.RateData;
/**
* Created by Chirag Gupta on 12/27/17.
@@ -36,6 +39,36 @@ final class LoanProductsApiResourceSwagger {
private PostLoanProductsRequest() {}
+ static final class AllowAttributeOverrides {
+
+ private AllowAttributeOverrides() {}
+
+ @Schema(example = "true")
+ public boolean amortizationType;
+ @Schema(example = "true")
+ public boolean interestType;
+ @Schema(example = "true")
+ public boolean transactionProcessingStrategyId;
+ @Schema(example = "true")
+ public boolean interestCalculationPeriodType;
+ @Schema(example = "true")
+ public boolean inArrearsTolerance;
+ @Schema(example = "true")
+ public boolean repaymentEvery;
+ @Schema(example = "true")
+ public boolean graceOnPrincipalAndInterestPayment;
+ @Schema(example = "true")
+ public boolean graceOnArrearsAgeing;
+ }
+
+ static final class ChargeId {
+
+ private ChargeId() {}
+
+ @Schema(example = "1")
+ public Integer id;
+ }
+
@Schema(example = "LP Accrual Accounting")
public String name;
@Schema(example = "LPAA")
@@ -98,6 +131,66 @@ final class LoanProductsApiResourceSwagger {
public Integer overpaymentLiabilityAccountId;
@Schema(example = "41")
public Integer writeOffAccountId;
+ @Schema(example = "false")
+ public Boolean includeInBorrowerCycle;
+ @Schema(example = "false")
+ public Boolean useBorrowerCycle;
+ @Schema(example = "[]")
+ public List<Integer> principalVariationsForBorrowerCycle;
+ @Schema(example = "[]")
+ public List<Integer> interestRateVariationsForBorrowerCycle;
+ @Schema(example = "[]")
+ public List<Integer> numberOfRepaymentVariationsForBorrowerCycle;
+ @Schema(example = "true")
+ public Boolean multiDisburseLoan;
+ @Schema(example = "0")
+ public Integer interestRecalculationCompoundingMethod;
+ @Schema(example = "2")
+ public Integer rescheduleStrategyMethod;
+ @Schema(example = "1")
+ public Integer preClosureInterestCalculationStrategy;
+ @Schema(example = "false")
+ public Boolean isLinkedToFloatingInterestRates;
+ @Schema(example = "false")
+ public Boolean allowVariableInstallments;
+ @Schema(example = "non-interest bearing product")
+ public String description;
+ @Schema(example = "1")
+ public Integer installmentAmountInMultiplesOf;
+ @Schema(example = "5000.000000")
+ public Double minPrincipal;
+ @Schema(example = "15000.000000")
+ public Double maxPrincipal;
+ @Schema(example = "30")
+ public Integer minimumDaysBetweenDisbursalAndFirstRepayment;
+ @Schema(example = "true")
+ public Boolean allowPartialPeriodInterestCalcualtion;
+ @Schema(example = "true")
+ public Boolean disallowExpectedDisbursements;
+ @Schema(example = "true")
+ public Boolean allowApprovedDisbursedAmountsOverApplied;
+ @Schema(example = "percentage")
+ public String overAppliedCalculationType;
+ @Schema(example = "50")
+ public Integer overAppliedNumber;
+ @Schema(example = "true")
+ public Boolean canDefineInstallmentAmount;
+ @Schema(example = "50")
+ public Integer principalThresholdForLastInstallment;
+ @Schema(example = "50")
+ public Integer recalculationRestFrequencyType;
+ @Schema(example = "3")
+ public Integer maxTrancheCount;
+ @Schema(example = "36000.000000")
+ public Double outstandingLoanBalance;
+ @Schema(example = "dd MMMM yyyy")
+ public String dateFormat;
+ public
Set<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
paymentChannelToFundSourceMappings;
+ public
Set<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings>
feeToIncomeAccountMappings;
+ public Collection<ChargeToGLAccountMapper>
penaltyToIncomeAccountMappings;
+ public AllowAttributeOverrides allowAttributeOverrides;
+ public List<ChargeId> charges;
+ public List<RateData> rates;
}
@Schema(description = "PostLoanProductsResponse")
@@ -313,11 +406,11 @@ final class LoanProductsApiResourceSwagger {
public String status;
public GetLoanProductsCurrency currency;
@Schema(example = "10000.000000")
- public Float principal;
+ public Double principal;
@Schema(example = "5000.000000")
- public Float minPrincipal;
+ public Double minPrincipal;
@Schema(example = "15000.000000")
- public Float maxPrincipal;
+ public Double maxPrincipal;
@Schema(example = "10")
public Integer numberOfRepayments;
@Schema(example = "5")
@@ -328,10 +421,10 @@ final class LoanProductsApiResourceSwagger {
public Integer repaymentEvery;
public GetLoanProductsRepaymentFrequencyType repaymentFrequencyType;
@Schema(example = "15.000000")
- public Float interestRatePerPeriod;
+ public Double interestRatePerPeriod;
public
GetLoanProductsResponse.GetLoanProductsInterestRateFrequencyType
interestRateFrequencyType;
@Schema(example = "15.000000")
- public Float annualInterestRate;
+ public Double annualInterestRate;
public GetLoanProductsAmortizationType amortizationType;
@Schema(example = "5.5")
public BigDecimal fixedPrincipalPercentagePerInstallment;
@@ -862,11 +955,11 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsParamType paramType;
public GetLoanProductsValueConditionType valueConditionType;
@Schema(example = "2000.000000")
- public Float minValue;
+ public Double minValue;
@Schema(example = "20000.000000")
- public Float maxValue;
+ public Double maxValue;
@Schema(example = "15000.000000")
- public Float defaultValue;
+ public Double defaultValue;
}
static final class GetLoanAccountingMappings {
@@ -1033,21 +1126,21 @@ final class LoanProductsApiResourceSwagger {
public String status;
public GetLoanProductsResponse.GetLoanProductsCurrency currency;
@Schema(example = "10000.000000")
- public Float principal;
+ public Double principal;
@Schema(example = "2000.000000")
- public Float minPrincipal;
+ public Double minPrincipal;
@Schema(example = "15000.000000")
- public Float maxPrincipal;
+ public Double maxPrincipal;
@Schema(example = "7")
public Integer numberOfRepayments;
@Schema(example = "7")
public Integer repaymentEvery;
public GetLoanProductsResponse.GetLoanProductsRepaymentFrequencyType
repaymentFrequencyType;
@Schema(example = "5.000000")
- public Float interestRatePerPeriod;
+ public Double interestRatePerPeriod;
public
GetLoanProductsProductIdResponse.GetLoanProductsInterestRateFrequencyType
interestRateFrequencyType;
@Schema(example = "60.000000")
- public Float annualInterestRate;
+ public Double annualInterestRate;
public GetLoanProductsResponse.GetLoanProductsAmortizationType
amortizationType;
@Schema(example = "5.5")
public BigDecimal fixedPrincipalPercentagePerInstallment;
@@ -1073,7 +1166,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "3")
public Integer maxTrancheCount;
@Schema(example = "36000.000000")
- public Float outstandingLoanBalance;
+ public Double outstandingLoanBalance;
@Schema(example = "2")
public Integer overdueDaysForNPA;
@Schema(example = "50")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java
index 2a74cfada..2f3091c58 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/loanaccount/api/SelfLoansApiResourceSwagger.java
@@ -197,6 +197,8 @@ final class SelfLoansApiResourceSwagger {
public String disbursedByLastname;
@Schema(example = "[2012, 4, 10]")
public LocalDate expectedMaturityDate;
+ @Schema(example = "[2012, 4, 3]")
+ public LocalDate closedOnDate;
}
static final class GetLoansLoanIdSummary {
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
new file mode 100644
index 000000000..7ce584cd1
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+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.ApplyLoanCommandStrategy;
+import org.apache.fineract.batch.command.internal.ApplySavingsCommandStrategy;
+import org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy;
+import
org.apache.fineract.batch.command.internal.ApproveLoanRescheduleCommandStrategy;
+import
org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy;
+import org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy;
+import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy;
+import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy;
+import org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy;
+import
org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy;
+import org.apache.fineract.batch.command.internal.RepayLoanCommandStrategy;
+import org.apache.fineract.batch.command.internal.UnknownCommandStrategy;
+import org.apache.fineract.batch.command.internal.UpdateClientCommandStrategy;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Tests {@link CommandStrategyProvider}.
+ */
+public class CommandStrategyProviderTest {
+
+ /**
+ * Command strategy provider.
+ *
+ * @return the test data stream
+ */
+ private static Stream<Arguments> provideCommandStrategies() {
+ return Stream.of(Arguments.of("clients", HttpMethod.POST,
"createClientCommandStrategy", mock(CreateClientCommandStrategy.class)),
+ Arguments.of("clients/123", HttpMethod.PUT,
"updateClientCommandStrategy", mock(UpdateClientCommandStrategy.class)),
+ Arguments.of("loans", HttpMethod.POST,
"applyLoanCommandStrategy", mock(ApplyLoanCommandStrategy.class)),
+ Arguments.of("loans/123", HttpMethod.GET,
"getLoanByIdCommandStrategy", mock(GetLoanByIdCommandStrategy.class)),
+ Arguments.of("loans/123?associations=all", HttpMethod.GET,
"getLoanByIdCommandStrategy",
+ mock(GetLoanByIdCommandStrategy.class)),
+ Arguments.of("loans/123?associations=all&exclude=guarantors",
HttpMethod.GET, "getLoanByIdCommandStrategy",
+ mock(GetLoanByIdCommandStrategy.class)),
+ Arguments.of("savingsaccounts", HttpMethod.POST,
"applySavingsCommandStrategy", mock(ApplySavingsCommandStrategy.class)),
+ 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/transactions?command=repayment",
HttpMethod.POST, "repayLoanCommandStrategy",
+ mock(RepayLoanCommandStrategy.class)),
+ Arguments.of("clients/456?command=activate", HttpMethod.POST,
"activateClientCommandStrategy",
+ mock(ActivateClientCommandStrategy.class)),
+ Arguments.of("loans/123?command=approve", HttpMethod.POST,
"approveLoanCommandStrategy",
+ mock(ApproveLoanCommandStrategy.class)),
+ Arguments.of("loans/123?command=disburse", HttpMethod.POST,
"disburseLoanCommandStrategy",
+ mock(DisburseLoanCommandStrategy.class)),
+ Arguments.of("rescheduleloans/123?command=approve",
HttpMethod.POST, "approveLoanRescheduleCommandStrategy",
+ mock(ApproveLoanRescheduleCommandStrategy.class)),
+ Arguments.of("loans/123/transactions/123", HttpMethod.GET,
"getTransactionByIdCommandStrategy",
+ mock(GetTransactionByIdCommandStrategy.class)));
+ }
+
+ /**
+ * Tests {@link CommandStrategyProvider#getCommandStrategy} for success
scenarios.
+ *
+ * @param url
+ * the resource URL
+ * @param httpMethod
+ * the resource HTTP method
+ * @param beanName
+ * the context bean name
+ * @param commandStrategy
+ * the command strategy
+ */
+ @ParameterizedTest
+ @MethodSource("provideCommandStrategies")
+ public void testGetCommandStrategySuccess(final String url, final String
httpMethod, final String beanName,
+ final CommandStrategy commandStrategy) {
+ final ApplicationContext applicationContext =
mock(ApplicationContext.class);
+ final CommandStrategyProvider commandStrategyProvider = new
CommandStrategyProvider(applicationContext);
+ when(applicationContext.getBean(beanName)).thenReturn(commandStrategy);
+
+ final CommandStrategy result =
commandStrategyProvider.getCommandStrategy(CommandContext.resource(url).method(httpMethod).build());
+ assertEquals(commandStrategy, result);
+ }
+
+ /**
+ * Command strategy provider for error scenarioss.
+ *
+ * @return the test data stream
+ */
+ private static Stream<Arguments>
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));
+ }
+
+ /**
+ * Tests {@link CommandStrategyProvider#getCommandStrategy} for error
scenarios.
+ *
+ * @param url
+ * the resource URL
+ * @param httpMethod
+ * the resource HTTP method
+ */
+ @ParameterizedTest
+ @MethodSource("provideCommandStrategyResourceDetailsForErrors")
+ public void testGetCommandStrategyForError(final String url, final String
httpMethod) {
+ final ApplicationContext applicationContext =
mock(ApplicationContext.class);
+ final CommandStrategyProvider commandStrategyProvider = new
CommandStrategyProvider(applicationContext);
+
+ final CommandStrategy result =
commandStrategyProvider.getCommandStrategy(CommandContext.resource(url).method(httpMethod).build());
+ assertEquals(UnknownCommandStrategy.class, result.getClass());
+ }
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategyTest.java
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategyTest.java
new file mode 100644
index 000000000..a16cea7a2
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetLoanByIdCommandStrategyTest.java
@@ -0,0 +1,186 @@
+/**
+ * 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.LoansApiResource;
+import
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+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;
+
+public class GetLoanByIdCommandStrategyTest {
+
+ private static Stream<Arguments> provideQueryParameters() {
+ return Stream.of(Arguments.of(null, 0),
Arguments.of("associations=all", 1),
+
Arguments.of("fields=id,principal,annualInterestRate&associations=repaymentSchedule,transactions",
2));
+ }
+
+ /**
+ * Test {@link GetLoanByIdCommandStrategy#execute} happy path scenario.
+ *
+ */
+ @ParameterizedTest
+ @MethodSource("provideQueryParameters")
+ public void testExecuteSuccessScenario(final String queryParameter, final
int noOfQueryParams) {
+ // given
+ final TestContext testContext = new TestContext();
+
+ final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
+ final BatchRequest request = getBatchRequest(loanId, queryParameter);
+ final String responseBody =
"{\\\"id\\\":2,\\\"accountNo\\\":\\\"000000002\\\"}";
+
+ given(testContext.loansApiResource.retrieveLoan(eq(loanId), eq(false),
any(UriInfo.class))).willReturn(responseBody);
+
+ // when
+ final BatchResponse response = testContext.underTest.execute(request,
testContext.uriInfo);
+
+ // then
+ 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.loansApiResource).retrieveLoan(eq(loanId),
eq(false), testContext.uriInfoCaptor.capture());
+ MutableUriInfo mutableUriInfo = testContext.uriInfoCaptor.getValue();
+
assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(noOfQueryParams);
+ }
+
+ /**
+ * Test {@link GetLoanByIdCommandStrategy#execute} for internal server
error.
+ */
+ @Test
+ public void testExecuteForInternalServerError() {
+ // given
+ final TestContext testContext = new TestContext();
+ final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
+ final BatchRequest request = getBatchRequest(loanId, null);
+
+ given(testContext.loansApiResource.retrieveLoan(eq(loanId), eq(false),
any(UriInfo.class)))
+ .willThrow(new RuntimeException("Some error"));
+
+ // when
+ final BatchResponse response = testContext.underTest.execute(request,
testContext.uriInfo);
+
+ // then
+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);
+ assertThat(response.getBody()).isEqualTo("{\"Exception\":
java.lang.RuntimeException: Some error}");
+ assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+ assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+ }
+
+ /**
+ * Test {@link GetLoanByIdCommandStrategy#execute} for loan not found
exception.
+ */
+ @Test
+ public void testExecuteForLoanNotFoundException() {
+ // given
+ final TestContext testContext = new TestContext();
+ final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
+ final BatchRequest request = getBatchRequest(loanId, null);
+
+ given(testContext.loansApiResource.retrieveLoan(eq(loanId), eq(false),
any(UriInfo.class)))
+ .willThrow(new LoanNotFoundException(loanId));
+
+ // when
+ final BatchResponse response = testContext.underTest.execute(request,
testContext.uriInfo);
+
+ // then
+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
+
assertThat(response.getBody()).isEqualTo(buildLoanNotFoundError(loanId));
+ assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+ assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+ }
+
+ private String buildLoanNotFoundError(final Long loanId) {
+ return String.format(
+ "{\n \"developerMessage\": \"The requested resource is not
available.\",\n"
+ + " \"httpStatusCode\": \"404\",\n
\"defaultUserMessage\": \"The requested resource is not available.\",\n"
+ + " \"userMessageGlobalisationCode\":
\"error.msg.resource.not.found\",\n \"errors\": [\n {\n"
+ + " \"developerMessage\": \"Loan with identifier
%s does not exist\",\n"
+ + " \"defaultUserMessage\": \"Loan with
identifier %s does not exist\",\n"
+ + " \"userMessageGlobalisationCode\":
\"error.msg.loan.id.invalid\",\n \"parameterName\": \"id\",\n"
+ + " \"args\": [\n {\n \"value\":
%s\n }\n ]\n }\n ]\n" + "}",
+ loanId, loanId, loanId);
+ }
+
+ /**
+ * Creates and returns a request with the given loan id.
+ *
+ * @param loanId
+ * the loan id
+ * @return BatchRequest
+ */
+ private BatchRequest getBatchRequest(final Long loanId, final String
queryParamStr) {
+
+ final BatchRequest br = new BatchRequest();
+ String relativeUrl = "loans/" + loanId;
+ if (queryParamStr != null) {
+ relativeUrl = relativeUrl + "?" + queryParamStr;
+ }
+
+ 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 {
+
+ @Mock
+ private UriInfo uriInfo;
+
+ @Mock
+ private LoansApiResource loansApiResource;
+
+ @Captor
+ private ArgumentCaptor<MutableUriInfo> uriInfoCaptor;
+
+ private final GetLoanByIdCommandStrategy underTest;
+
+ TestContext() {
+ MockitoAnnotations.openMocks(this);
+ underTest = new GetLoanByIdCommandStrategy(loansApiResource);
+ }
+ }
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategyTest.java
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategyTest.java
new file mode 100644
index 000000000..bec2cfdc0
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetTransactionByIdCommandStrategyTest.java
@@ -0,0 +1,205 @@
+/**
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.when;
+
+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.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import
org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class GetTransactionByIdCommandStrategyTest {
+
+ /**
+ * Test {@link GetTransactionByIdCommandStrategy#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 =
"{\"id\":12,\"officeId\":1,\"officeName\":\"Head
Office\",\"type\":{\"id\":10,\"code\":"
+ +
"\"loanTransactionType.accrual\",\"value\":\"Accrual\",\"disbursement\":false,\"repaymentAtDisbursement\":false,"
+ +
"\"repayment\":false,\"contra\":false,\"waiveInterest\":false,\"waiveCharges\":false,\"accrual\":true,\"writeOff\":false,"
+ +
"\"recoveryRepayment\":false,\"initiateTransfer\":false,\"approveTransfer\":false,\"withdrawTransfer\":false,"
+ +
"\"rejectTransfer\":false,\"chargePayment\":false,\"refund\":false,\"refundForActiveLoans\":false},\"date\":[2022,3,29],"
+ +
"\"currency\":{\"code\":\"EUR\",\"name\":\"Euro\",\"decimalPlaces\":2,\"inMultiplesOf\":0,\"displaySymbol\":\"€\","
+ + "\"nameCode\":\"currency.EUR\",\"displayLabel\":\"Euro
(€)\"},\"amount\":0.000000,\"netDisbursalAmount\":200.000000,"
+ +
"\"principalPortion\":0,\"interestPortion\":0.000000,\"feeChargesPortion\":0,\"penaltyChargesPortion\":0,\"overpaymentPortion\":0,"
+ +
"\"unrecognizedIncomePortion\":0,\"outstandingLoanBalance\":0,\"submittedOnDate\":[2022,3,29],\"manuallyReversed\":false,"
+ + "\"loanChargePaidByList\":[],\"numberOfRepayments\":0}";
+
+
given(testContext.loanTransactionsApiResource.retrieveTransaction(loanId,
transactionId, testContext.uriInfo))
+ .willReturn(responseBody);
+
+ // when
+ final BatchResponse response =
testContext.subjectToTest.execute(request, testContext.uriInfo);
+
+ // then
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+ assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+ assertThat(response.getBody()).isEqualTo(responseBody);
+ }
+
+ /**
+ * Test {@link GetTransactionByIdCommandStrategy#execute} for internal
server error.
+ */
+ @Test
+ public void testExecuteForInternalServerError() {
+ // 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);
+
+
given(testContext.loanTransactionsApiResource.retrieveTransaction(loanId,
transactionId, testContext.uriInfo))
+ .willThrow(new RuntimeException("Some error"));
+
+ // when
+ final BatchResponse response =
testContext.subjectToTest.execute(request, testContext.uriInfo);
+
+ // then
+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);
+ assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+ assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+ assertThat(response.getBody()).isEqualTo("{\"Exception\":
java.lang.RuntimeException: Some error}");
+ }
+
+ /**
+ * Test {@link GetTransactionByIdCommandStrategy#execute} for loan not
found exception.
+ */
+ @Test
+ public void testExecuteForLoanNotFoundException() {
+ // 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);
+
+
given(testContext.loanTransactionsApiResource.retrieveTransaction(loanId,
transactionId, testContext.uriInfo))
+ .willThrow(new LoanNotFoundException(loanId));
+
+ // when
+ final BatchResponse response =
testContext.subjectToTest.execute(request, testContext.uriInfo);
+
+ // then
+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
+ assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+ assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+ assertThat(response.getBody()).isEqualTo(build404NotFoundError("Loan",
loanId));
+ }
+
+ /**
+ * Test {@link GetTransactionByIdCommandStrategy#execute} for transaction
not found exception.
+ */
+ @Test
+ public void testExecuteForTransactionNotFoundException() {
+ 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);
+
+
when(testContext.loanTransactionsApiResource.retrieveTransaction(loanId,
transactionId, testContext.uriInfo))
+ .thenThrow(new
LoanTransactionNotFoundException(transactionId));
+
+ final BatchResponse response =
testContext.subjectToTest.execute(request, testContext.uriInfo);
+
+ assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode());
+ assertEquals(build404NotFoundError("Transaction", transactionId),
response.getBody());
+ assertEquals(request.getRequestId(), response.getRequestId());
+ assertEquals(request.getHeaders(), response.getHeaders());
+ }
+
+ /**
+ * 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 = "loans/" + loanId + "/transactions/" +
transactionId;
+
+ 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;
+ }
+
+ /**
+ * Builds the 404 not found error.
+ *
+ * @param field
+ * the field name
+ * @param id
+ * the id
+ */
+ private String build404NotFoundError(final String field, final Long id) {
+ return String.format("{\n" + " \"developerMessage\": \"The requested
resource is not available.\",\n"
+ + " \"httpStatusCode\": \"404\",\n" + "
\"defaultUserMessage\": \"The requested resource is not available.\",\n"
+ + " \"userMessageGlobalisationCode\":
\"error.msg.resource.not.found\",\n" + " \"errors\": [\n" + " {\n"
+ + " \"developerMessage\": \"%s with identifier %s does
not exist\",\n"
+ + " \"defaultUserMessage\": \"%s with identifier %s does
not exist\",\n"
+ + " \"userMessageGlobalisationCode\":
\"error.msg.loan.id.invalid\",\n" + " \"parameterName\": \"id\",\n"
+ + " \"args\": [\n" + " {\n" + "
\"value\": %s\n" + " }\n" + " ]\n" + " }\n" + " ]\n"
+ + "}", field, id, field, id, id);
+ }
+
+ /**
+ * 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 GetTransactionByIdCommandStrategy subjectToTest;
+
+ TestContext() {
+ MockitoAnnotations.openMocks(this);
+ subjectToTest = new
GetTransactionByIdCommandStrategy(loanTransactionsApiResource);
+ }
+ }
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
new file mode 100644
index 000000000..8b55f176c
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
@@ -0,0 +1,88 @@
+/**
+ * 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.portfolio.loanaccount.domain;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
+import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ * Tests {@link Loan}.
+ */
+public class LoanTest {
+
+ /**
+ * Tests {@link Loan#getCharges()} with charges.
+ */
+ @Test
+ public void testGetChargesWithCharges() {
+ Loan loan = new Loan();
+ ReflectionTestUtils.setField(loan, "charges",
Collections.singleton(buildLoanCharge()));
+
+ final Collection<LoanCharge> chargeIds = loan.getCharges();
+
+ assertEquals(1, chargeIds.size());
+ }
+
+ /**
+ * Tests {@link Loan#getCharges()} with no charges.
+ */
+ @Test
+ public void testGetChargesWithNoCharges() {
+ final Loan loan = new Loan();
+
+ final Collection<LoanCharge> chargeIds = loan.getCharges();
+
+ assertEquals(0, chargeIds.size());
+ }
+
+ /**
+ * Tests {@link Loan#getCharges()} with null to make sure NPE is not
thrown.
+ */
+ @Test
+ public void testGetChargesWithNull() {
+ final Loan loan = new Loan();
+ ReflectionTestUtils.setField(loan, "charges", null);
+
+ final Collection<LoanCharge> chargeIds = loan.getCharges();
+
+ assertEquals(0, chargeIds.size());
+ }
+
+ /**
+ * Builds a new loan charge.
+ *
+ * @return the {@link LoanCharge}
+ */
+ private LoanCharge buildLoanCharge() {
+ return new LoanCharge(mock(Loan.class), mock(Charge.class), new
BigDecimal(100), new BigDecimal(100),
+ ChargeTimeType.TRANCHE_DISBURSEMENT,
ChargeCalculationType.FLAT, DateUtils.getLocalDateOfTenant(),
+ ChargePaymentMode.REGULAR, 1, new BigDecimal(100));
+ }
+}
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 022c1e8a9..59d0ce052 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
@@ -38,6 +38,8 @@ import org.apache.fineract.integrationtests.common.Utils;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import
org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -115,7 +117,7 @@ public class BatchApiTest {
* rolled back.
*
* @see
org.apache.fineract.batch.command.internal.CreateClientCommandStrategy
- * @see org.apache.fineract.batch.api.BatchApiResource
+ * @see org.apache.fineract.batch.api.BatchesApiResource
* @see org.apache.fineract.batch.service.BatchApiService
*/
@Test
@@ -426,7 +428,7 @@ public class BatchApiTest {
// Create a createClient Request
final BatchRequest br1 = BatchHelper.createClientRequest(4726L, "");
- // Create a activateClient Request
+ // Create an activateClient Request
final BatchRequest br2 = BatchHelper.activateClientRequest(4727L,
4726L);
final List<BatchRequest> batchRequests = new ArrayList<>();
@@ -480,13 +482,13 @@ public class BatchApiTest {
// Create a activateClient Request
final BatchRequest br2 = BatchHelper.activateClientRequest(4731L,
4730L);
- // Create a ApplyLoan Request
+ // Create an ApplyLoan Request
final BatchRequest br3 = BatchHelper.applyLoanRequest(4732L, 4731L,
productId, clientCollateralId);
- // Create a approveLoan Request
+ // Create an approveLoan Request
final BatchRequest br4 = BatchHelper.approveLoanRequest(4733L, 4732L);
- // Create a disburseLoan Request
+ // Create an disburseLoan Request
final BatchRequest br5 = BatchHelper.disburseLoanRequest(4734L, 4733L);
final List<BatchRequest> batchRequests = new ArrayList<>();
@@ -540,10 +542,10 @@ public class BatchApiTest {
// Create a createClient Request
final BatchRequest br1 = BatchHelper.createActiveClientRequest(4740L,
"");
- // Create a ApplyLoan Request
+ // Create an ApplyLoan Request
final BatchRequest br2 = BatchHelper.applyLoanRequest(4742L, 4740L,
productId, clientCollateralId);
- // Create a approveLoan Request
+ // Create an approveLoan Request
final BatchRequest br3 = BatchHelper.approveLoanRequest(4743L, 4742L);
// Create a disburseLoan Request
@@ -561,4 +563,195 @@ public class BatchApiTest {
Assertions.assertEquals(200L, (long) response.get(2).getStatusCode(),
"Verify Status Code 200 for approve Loan");
Assertions.assertEquals(200L, (long) response.get(3).getStatusCode(),
"Verify Status Code 200 for disburse Loan");
}
+
+ /**
+ * Test for the successful disbursement and get loan. A '200' status code
is expected on successful responses.
+ *
+ * @see
org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy
+ * @see
org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy
+ */
+ @Test
+ public void
shouldReturnOkStatusOnSuccessfulDisbursementAndGetTransaction() {
+ final String loanProductJSON = new LoanProductTestBuilder() //
+ .withPrincipal("10000000.00") //
+ .withNumberOfRepayments("24") //
+ .withRepaymentAfterEvery("1") //
+ .withRepaymentTypeAsMonth() //
+ .withinterestRatePerPeriod("2") //
+ .withInterestRateFrequencyTypeAsMonths() //
+ .withAmortizationTypeAsEqualPrincipalPayment() //
+ .withInterestTypeAsDecliningBalance() //
+ .currencyDetails("0", "100").build(null);
+
+ final Long applyLoanRequestId = 6730L;
+ final Long approveLoanRequestId = 6731L;
+ final Long disburseLoanRequestId = 6732L;
+ final Long getTransactionRequestId = 6733L;
+
+ // 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 ApplyLoan Request
+ final BatchRequest batchRequest1 =
BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId,
productId);
+
+ // Create an approveLoan Request
+ final BatchRequest batchRequest2 =
BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId);
+
+ // Create a disbursement Request
+ final BatchRequest batchRequest3 =
BatchHelper.disburseLoanRequest(disburseLoanRequestId, approveLoanRequestId);
+
+ // Create a getTransaction Request
+ final BatchRequest batchRequest4 =
BatchHelper.getTransactionByIdRequest(getTransactionRequestId,
disburseLoanRequestId);
+
+ final List<BatchRequest> batchRequests = Arrays.asList(batchRequest1,
batchRequest2, batchRequest3, batchRequest4);
+
+ final List<BatchResponse> 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 Get Transaction
By Id");
+ }
+
+ /**
+ * Test for the successful create client, creat, approve and get loan. A
'200' status code is expected on successful
+ * responses.
+ *
+ * @see
org.apache.fineract.batch.command.internal.CreateClientCommandStrategy
+ * @see org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy
+ * @see
org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy
+ * @see
org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy
+ */
+ @Test
+ public void
shouldReturnOkStatusOnSuccessfulCreateClientCreateApproveAndGetLoan() {
+ final String loanProductJSON = new LoanProductTestBuilder() //
+ .withPrincipal("10000000.00") //
+ .withNumberOfRepayments("24") //
+ .withRepaymentAfterEvery("1") //
+ .withRepaymentTypeAsMonth() //
+ .withinterestRatePerPeriod("2") //
+ .withInterestRateFrequencyTypeAsMonths() //
+ .withAmortizationTypeAsEqualPrincipalPayment() //
+ .withInterestTypeAsDecliningBalance() //
+ .currencyDetails("0", "100").build(null);
+
+ final Long createActiveClientRequestId = 4730L;
+ final Long applyLoanRequestId = 4731L;
+ final Long approveLoanRequestId = 4732L;
+ final Long getLoanByIdRequestId = 4733L;
+
+ // Create product
+ final Integer productId = new LoanTransactionHelper(this.requestSpec,
this.responseSpec).getLoanProductId(loanProductJSON);
+
+ // Create createClient Request
+ final BatchRequest batchRequest1 =
BatchHelper.createActiveClientRequest(createActiveClientRequestId, "");
+
+ // Create an ApplyLoan Request
+ final BatchRequest batchRequest2 =
BatchHelper.applyLoanRequest(applyLoanRequestId, createActiveClientRequestId,
productId, null);
+
+ // Create an approveLoan Request
+ final BatchRequest batchRequest3 =
BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId);
+
+ // Get loan by id Request
+ final BatchRequest batchRequest4 =
BatchHelper.getLoanByIdRequest(getLoanByIdRequestId, applyLoanRequestId, null);
+
+ final List<BatchRequest> batchRequests = Arrays.asList(batchRequest1,
batchRequest2, batchRequest3, batchRequest4);
+
+ final List<BatchResponse> responses =
BatchHelper.postBatchRequestsWithEnclosingTransaction(this.requestSpec,
this.responseSpec,
+ BatchHelper.toJsonString(batchRequests));
+
+ Assertions.assertEquals(HttpStatus.SC_OK,
responses.get(0).getStatusCode(), "Verify Status Code 200 for Create Client");
+ Assertions.assertEquals(HttpStatus.SC_OK,
responses.get(1).getStatusCode(), "Verify Status Code 200 for Apply Loan");
+ Assertions.assertEquals(HttpStatus.SC_OK,
responses.get(2).getStatusCode(), "Verify Status Code 200 for Approve Loan");
+ Assertions.assertEquals(HttpStatus.SC_OK,
responses.get(3).getStatusCode(), "Verify Status Code 200 for Get Loan By Id");
+
+ final FromJsonHelper jsonHelper = new FromJsonHelper();
+ final Long loanId = jsonHelper.extractLongNamed("loanId",
jsonHelper.parse(responses.get(1).getBody()).getAsJsonObject());
+ final Long loanIdInGetResponse = jsonHelper.extractLongNamed("id",
jsonHelper.parse(responses.get(3).getBody()).getAsJsonObject());
+ final JsonObject statusInGetResponse =
jsonHelper.parse(responses.get(3).getBody()).getAsJsonObject().get("status")
+ .getAsJsonObject();
+
+ Assertions.assertEquals(loanId, loanIdInGetResponse);
+ Assertions.assertEquals(LoanStatus.APPROVED.getCode(),
jsonHelper.extractStringNamed("code", statusInGetResponse));
+ Assertions.assertEquals("Approved",
jsonHelper.extractStringNamed("value", statusInGetResponse));
+ }
+
+ /**
+ * Test for the successful creat, approve and get loan. A '200' status
code is expected on successful responses.
+ *
+ * @see org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy
+ * @see
org.apache.fineract.batch.command.internal.ApproveLoanCommandStrategy
+ * @see
org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy
+ */
+ @Test
+ public void shouldReturnOkStatusOnSuccessfulCreateApproveAndGetLoan() {
+ final String loanProductJSON = new LoanProductTestBuilder() //
+ .withPrincipal("10000000.00") //
+ .withNumberOfRepayments("24") //
+ .withRepaymentAfterEvery("1") //
+ .withRepaymentTypeAsMonth() //
+ .withinterestRatePerPeriod("2") //
+ .withInterestRateFrequencyTypeAsMonths() //
+ .withAmortizationTypeAsEqualPrincipalPayment() //
+ .withInterestTypeAsDecliningBalance() //
+ .currencyDetails("0", "100").build(null);
+
+ final Long applyLoanRequestId = 5730L;
+ final Long approveLoanRequestId = 5731L;
+ final Long getLoanByIdRequestId = 5732L;
+ final Long getLoanByIdWithQueryParametersRequestId = 5733L;
+
+ // 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 ApplyLoan Request
+ final BatchRequest batchRequest1 =
BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId,
productId);
+
+ // Create an approveLoan Request
+ final BatchRequest batchRequest2 =
BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId);
+
+ // Get loan by id Request without query param
+ final BatchRequest batchRequest3 =
BatchHelper.getLoanByIdRequest(getLoanByIdRequestId, applyLoanRequestId, null);
+
+ // Get loan by id Request with query param
+ final BatchRequest batchRequest4 =
BatchHelper.getLoanByIdRequest(getLoanByIdWithQueryParametersRequestId,
applyLoanRequestId,
+ "associations=repaymentSchedule,transactions");
+
+ final List<BatchRequest> batchRequests = Arrays.asList(batchRequest1,
batchRequest2, batchRequest3, batchRequest4);
+
+ final List<BatchResponse> responses =
BatchHelper.postBatchRequestsWithEnclosingTransaction(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 Get Loan By Id without query
parameter");
+ Assertions.assertEquals(HttpStatus.SC_OK,
responses.get(3).getStatusCode(),
+ "Verify Status Code 200 for Get Loan By Id with query
parameter");
+
+ final FromJsonHelper jsonHelper = new FromJsonHelper();
+ final Long loanId = jsonHelper.extractLongNamed("loanId",
jsonHelper.parse(responses.get(0).getBody()).getAsJsonObject());
+ final Long loanIdInGetResponse = jsonHelper.extractLongNamed("id",
jsonHelper.parse(responses.get(2).getBody()).getAsJsonObject());
+ final JsonObject statusInGetResponse =
jsonHelper.parse(responses.get(2).getBody()).getAsJsonObject().get("status")
+ .getAsJsonObject();
+
+ Assertions.assertEquals(loanId, loanIdInGetResponse);
+ Assertions.assertEquals(LoanStatus.APPROVED.getCode(),
jsonHelper.extractStringNamed("code", statusInGetResponse));
+ Assertions.assertEquals("Approved",
jsonHelper.extractStringNamed("value", statusInGetResponse));
+
+ // Repayment schedule will not be available in the response
+
Assertions.assertFalse(responses.get(2).getBody().contains("repaymentSchedule"));
+
+ // Repayment schedule information will be available in the response
based on the query parameter
+
Assertions.assertTrue(responses.get(3).getBody().contains("repaymentSchedule"));
+ }
}
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 7fca25dfa..5b37a620d 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
@@ -26,6 +26,7 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import javax.ws.rs.HttpMethod;
import org.apache.fineract.batch.domain.BatchRequest;
import org.apache.fineract.batch.domain.BatchResponse;
import org.junit.jupiter.api.Assertions;
@@ -236,14 +237,52 @@ public final class BatchHelper {
br.setMethod("POST");
br.setReference(reference);
- final String body = "{\"dateFormat\": \"dd MMMM yyyy\", \"locale\":
\"en_GB\", \"clientId\": \"$.clientId\"," + "\"productId\": "
+ String body = "{\"dateFormat\": \"dd MMMM yyyy\", \"locale\":
\"en_GB\", \"clientId\": \"$.clientId\"," + "\"productId\": "
+ productId + ", \"principal\": \"10,000.00\",
\"loanTermFrequency\": 10,"
+ "\"loanTermFrequencyType\": 2, \"loanType\": \"individual\",
\"numberOfRepayments\": 10,"
+ "\"repaymentEvery\": 1, \"repaymentFrequencyType\": 2,
\"interestRatePerPeriod\": 10,"
+ "\"amortizationType\": 1, \"interestType\": 0,
\"interestCalculationPeriodType\": 1,"
+ + "\"transactionProcessingStrategyId\": 1,
\"expectedDisbursementDate\": \"10 Jun 2013\",";
+
+ if (clientCollateralId != null) {
+ body = body + "\"collateral\": [{\"clientCollateralId\": \"" +
clientCollateralId.toString() + "\", \"quantity\": \"1\"}],";
+ }
+
+ body = body + "\"submittedOnDate\": \"10 Jun 2013\"}";
+
+ br.setBody(body);
+
+ return br;
+ }
+
+ /**
+ * Creates and returns a {@link
org.apache.fineract.batch.command.internal.ApplyLoanCommandStrategy} request
with
+ * given clientId and product id.
+ *
+ * @param requestId
+ * the request id
+ * @param clientId
+ * the client id
+ * @param productId
+ * the product id
+ * @return {@link BatchRequest}
+ */
+ public static BatchRequest applyLoanRequestWithClientId(final Long
requestId, final Integer clientId, final Integer productId) {
+
+ final BatchRequest br = new BatchRequest();
+
+ br.setRequestId(requestId);
+ br.setRelativeUrl("loans");
+ br.setMethod("POST");
+
+ String body = String.format("{\"dateFormat\": \"dd MMMM yyyy\",
\"locale\": \"en_GB\", \"clientId\": %s, "
+ + "\"productId\": %s, \"principal\": \"10,000.00\",
\"loanTermFrequency\": 10,"
+ + "\"loanTermFrequencyType\": 2, \"loanType\": \"individual\",
\"numberOfRepayments\": 10,"
+ + "\"repaymentEvery\": 1, \"repaymentFrequencyType\": 2,
\"interestRatePerPeriod\": 10,"
+ + "\"amortizationType\": 1, \"interestType\": 0,
\"interestCalculationPeriodType\": 1,"
+ "\"transactionProcessingStrategyId\": 1,
\"expectedDisbursementDate\": \"10 Jun 2013\","
- + "\"collateral\": [{\"clientCollateralId\": \"" +
clientCollateralId.toString() + "\", \"quantity\": \"1\"}],"
- + "\"submittedOnDate\": \"10 Jun 2013\"}";
+ + "\"submittedOnDate\": \"10 Jun 2013\"}", clientId,
productId);
+
br.setBody(body);
return br;
@@ -419,4 +458,57 @@ public final class BatchHelper {
final Integer responseRecords = Utils.performServerGet(requestSpec,
responseSpec, CLIENT_URL, "totalFilteredRecords");
Assertions.assertEquals((long) 0, (long) responseRecords, "No records
found with given externalId");
}
+
+ /**
+ * Creates and returns a {@link
org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy}
+ * request with given requestId and reference.
+ *
+ * @param requestId
+ * the request id
+ * @param reference
+ * the reference
+ * @return the {@link BatchRequest}
+ */
+ public static BatchRequest getTransactionByIdRequest(final Long requestId,
final Long reference) {
+
+ final BatchRequest br = new BatchRequest();
+ String relativeUrl = "loans/$.loanId/transactions/$.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.GetLoanByIdCommandStrategy} request
with
+ * given requestId and reference.
+ *
+ * @param requestId
+ * the request id
+ * @param reference
+ * the reference
+ * @param queryParameter
+ * the query parameters
+ * @return the {@link BatchRequest}
+ */
+ public static BatchRequest getLoanByIdRequest(final Long requestId, final
Long reference, final String queryParameter) {
+
+ final BatchRequest br = new BatchRequest();
+ String relativeUrl = "loans/$.loanId";
+ if (queryParameter != null) {
+ relativeUrl = relativeUrl + "?" + queryParameter;
+ }
+
+ br.setRequestId(requestId);
+ br.setRelativeUrl(relativeUrl);
+ br.setMethod(HttpMethod.GET);
+ br.setReference(reference);
+ br.setBody("{}");
+
+ return br;
+ }
}