From 4a2aeff350dda04b4643f7e69748b295718e1ddc Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Thu, 27 Jul 2023 21:31:18 -0600 Subject: [PATCH] FINERACT-1926: Asset Externalization on Fineract --- ...HttpMessageNotReadableErrorController.java | 54 +++ .../api/ExternalAssetOwnersApiResource.java | 15 + ...ExternalAssetOwnersApiResourceSwagger.java | 69 +-- .../search/ExternalAssetOwnersSearchApi.java | 30 ++ .../ExternalAssetOwnersSearchApiDelegate.java | 40 ++ .../data/ExternalTransferDataDetails.java | 4 + .../search/SearchedExternalAssetOwner.java | 55 +++ ...SearchingExternalAssetOwnerRepository.java | 29 ++ ...chingExternalAssetOwnerRepositoryImpl.java | 106 +++++ .../ExternalAssetOwnerSearchService.java | 53 +++ .../ExternalAssetOwnerSearchRequest.java | 32 ++ .../ExternalAssetOwnerSearchDataMapper.java | 77 ++++ .../common/ExternalAssetOwnerHelper.java | 42 ++ .../integrationtests/common/Utils.java | 4 + .../ExternalAssetOwnerTransferTest.java | 410 ++++++++++++++++++ .../SearchExternalAssetOwnerTransferTest.java | 208 +++++++++ 16 files changed, 1194 insertions(+), 34 deletions(-) create mode 100644 fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/HttpMessageNotReadableErrorController.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApiDelegate.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchedExternalAssetOwner.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepositoryImpl.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/search/ExternalAssetOwnerSearchService.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/search/mapper/ExternalAssetOwnerSearchDataMapper.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/HttpMessageNotReadableErrorController.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/HttpMessageNotReadableErrorController.java new file mode 100644 index 00000000000..2449ad4caa0 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/HttpMessageNotReadableErrorController.java @@ -0,0 +1,54 @@ +/** + * 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.exception; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper; +import org.springframework.context.annotation.Scope; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.stereotype.Component; + +@Provider +@Component +@Scope("singleton") +@Slf4j +public class HttpMessageNotReadableErrorController implements ExceptionMapper, FineractExceptionMapper { + + @Override + public Response toResponse(HttpMessageNotReadableException exception) { + final String globalisationMessageCode = "error.msg.invalid.json.data"; + final String defaultUserMessage = "The referenced JSON data is invalid, validate date format as yyyy-MM-dd or other cases like String instead of Number"; + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultUserMessage); + + final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage); + + return Response.status(Response.Status.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON).build(); + } + + @Override + public int errorCode() { + return 4001; + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java index 2a773e939ec..3f92519b09d 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java @@ -45,13 +45,16 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.service.CommandParameterUtil; +import org.apache.fineract.infrastructure.core.service.PagedRequest; import org.apache.fineract.infrastructure.security.service.PlatformUserRightsContext; +import org.apache.fineract.investor.api.search.ExternalAssetOwnersSearchApiDelegate; import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition; import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData; import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData; import org.apache.fineract.investor.data.ExternalTransferData; import org.apache.fineract.investor.data.ExternalTransferResponseData; import org.apache.fineract.investor.service.ExternalAssetOwnersReadService; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceCommon; import org.springframework.context.annotation.Conditional; import org.springframework.data.domain.Page; @@ -69,6 +72,7 @@ public class ExternalAssetOwnersApiResource { private final DefaultToApiJsonSerializer postApiJsonSerializerService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final LoanReadPlatformServiceCommon loanReadPlatformService; + private final ExternalAssetOwnersSearchApiDelegate delegate; @POST @Path("/transfers/loans/{loanId}") @@ -190,6 +194,17 @@ public ExternalOwnerJournalEntryData getJournalEntriesOfOwner( } + @POST + @Path("/search") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Search External Asset Owner Transfers by text or date ranges to settlement or effective dates") + public Page searchInvestorData(@Parameter PagedRequest request) { + platformUserRightsContext.isAuthenticated(); + return delegate.searchInvestorData(request); + + } + private String getResultByTransferId(Long id, String command) { final CommandWrapperBuilder builder = new CommandWrapperBuilder(); CommandWrapper commandRequest; diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResourceSwagger.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResourceSwagger.java index 6648770ba59..e8c032d1cd2 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResourceSwagger.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResourceSwagger.java @@ -28,59 +28,59 @@ final class ExternalAssetOwnersApiResourceSwagger { private ExternalAssetOwnersApiResourceSwagger() {} - @Schema(description = "ExternalTransferResponse") - public static final class GetExternalTransferResponse { + static final class GetExternalTransferPageItems { - private GetExternalTransferResponse() {} + private GetExternalTransferPageItems() {} - static final class GetExternalTransferPageItems { + static final class GetExternalTransferOwner { - private GetExternalTransferPageItems() {} + private GetExternalTransferOwner() {} - static final class GetExternalTransferOwner { + @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") + public String externalId; + } - private GetExternalTransferOwner() {} + static final class GetExternalTransferLoan { - @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") - public String externalId; - } + private GetExternalTransferLoan() {} - static final class GetExternalTransferLoan { + @Schema(example = "1") + public Long loanId; - private GetExternalTransferLoan() {} + @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") + public String externalId; + } - @Schema(example = "1") - public Long loanId; + @Schema(example = "1") + public Long transferId; - @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") - public String externalId; - } + public GetExternalTransferOwner owner; - @Schema(example = "1") - public Long transferId; + public GetExternalTransferLoan loan; - public GetExternalTransferOwner owner; + @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") + public String transferExternalId; - public GetExternalTransferLoan loan; + @Schema(example = "1") + public String purchasePriceRatio; - @Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e") - public String transferExternalId; + @Schema(example = "[2023, 5, 23]") + public LocalDate settlementDate; - @Schema(example = "1") - public String purchasePriceRatio; + @Schema(example = "PENDING") + public ExternalTransferStatus status; - @Schema(example = "[2023, 5, 23]") - public LocalDate settlementDate; + @Schema(example = "[2023, 5, 1]") + public LocalDate effectiveFrom; - @Schema(example = "PENDING") - public ExternalTransferStatus status; + @Schema(example = "[2023, 5, 23]") + public LocalDate effectiveTo; + } - @Schema(example = "[2023, 5, 1]") - public LocalDate effectiveFrom; + @Schema(description = "ExternalTransferResponse") + public static final class GetExternalTransferResponse { - @Schema(example = "[2023, 5, 23]") - public LocalDate effectiveTo; - } + private GetExternalTransferResponse() {} @Schema(example = "20") public Integer totalFilteredRecords; @@ -152,4 +152,5 @@ static final class ExternalAssetOwnerTransferChangesData { public String purchasePriceRatio; } } + } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java new file mode 100644 index 00000000000..dde0dc564f9 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java @@ -0,0 +1,30 @@ +/** + * 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.investor.api.search; + +import org.apache.fineract.infrastructure.core.service.PagedRequest; +import org.apache.fineract.investor.data.ExternalTransferData; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; +import org.springframework.data.domain.Page; + +public interface ExternalAssetOwnersSearchApi { + + Page searchInvestorData(PagedRequest request); + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApiDelegate.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApiDelegate.java new file mode 100644 index 00000000000..861404dc1a2 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApiDelegate.java @@ -0,0 +1,40 @@ +/** + * 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.investor.api.search; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.PagedRequest; +import org.apache.fineract.investor.data.ExternalTransferData; +import org.apache.fineract.investor.service.search.ExternalAssetOwnerSearchService; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ExternalAssetOwnersSearchApiDelegate implements ExternalAssetOwnersSearchApi { + + private final ExternalAssetOwnerSearchService externalAssetOwnerSearchService; + + @Override + public Page searchInvestorData(PagedRequest request) { + return externalAssetOwnerSearchService.searchInvestorData(request); + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java index 4dd5d551c52..2e8d509dae6 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java @@ -19,9 +19,13 @@ package org.apache.fineract.investor.data; import java.math.BigDecimal; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class ExternalTransferDataDetails { private Long detailsId; diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchedExternalAssetOwner.java b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchedExternalAssetOwner.java new file mode 100644 index 00000000000..47ac2732184 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchedExternalAssetOwner.java @@ -0,0 +1,55 @@ +/** + * 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.investor.domain.search; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.investor.data.ExternalTransferStatus; +import org.apache.fineract.investor.data.ExternalTransferSubStatus; + +@Getter +@RequiredArgsConstructor +public class SearchedExternalAssetOwner { + + private final Long transferId; + private final Long loanId; + private final ExternalId externalLoanId; + + private final ExternalId owner; + private final ExternalId transferExternalId; + + private final ExternalTransferStatus status; + private final ExternalTransferSubStatus subStatus; + + private final String purchasePriceRatio; + private final LocalDate settlementDate; + private final LocalDate effectiveFrom; + private final LocalDate effectiveTo; + + private final Long detailsId; + private final BigDecimal totalOutstanding; + private final BigDecimal principalOutstanding; + private final BigDecimal interestOutstanding; + private final BigDecimal feeOutstanding; + private final BigDecimal penaltyOutstanding; + private final BigDecimal totalOverpaid; +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java new file mode 100644 index 00000000000..44683acb68e --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java @@ -0,0 +1,29 @@ +/** + * 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.investor.domain.search; + +import org.apache.fineract.infrastructure.core.service.PagedRequest; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; +import org.springframework.data.domain.Page; + +public interface SearchingExternalAssetOwnerRepository { + + Page searchInvestorData(PagedRequest searchRequest); + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepositoryImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepositoryImpl.java new file mode 100644 index 00000000000..25566052fb0 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepositoryImpl.java @@ -0,0 +1,106 @@ +/** + * 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.investor.domain.search; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory; +import org.apache.fineract.infrastructure.core.service.PagedRequest; +import org.apache.fineract.investor.domain.ExternalAssetOwner; +import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer; +import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferDetails; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class SearchingExternalAssetOwnerRepositoryImpl implements SearchingExternalAssetOwnerRepository { + + private final EntityManager entityManager; + private final CriteriaQueryFactory criteriaQueryFactory; + + @Override + public Page searchInvestorData(PagedRequest searchRequest) { + final ExternalAssetOwnerSearchRequest request = searchRequest.getRequest().get(); + final Pageable pageable = searchRequest.toPageable(); + + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(SearchedExternalAssetOwner.class); + + Root root = query.from(ExternalAssetOwnerTransfer.class); + Path details = root.join("externalAssetOwnerTransferDetails", JoinType.LEFT); + Path owner = root.get("owner"); + + Specification spec = (r, q, builder) -> { + Path o = r.get("owner"); + + List predicates = new ArrayList<>(); + + if (StringUtils.isNotBlank(request.getText())) { + String searchLikeValue = "%" + request.getText() + "%"; + predicates.add(cb.or(cb.like(r.get("externalId"), searchLikeValue), cb.like(o.get("externalId"), searchLikeValue), + cb.like(r.get("externalLoanId"), searchLikeValue))); + } + + if (request.getSubmittedFromDate() != null) { + predicates.add(cb.greaterThanOrEqualTo(r.get("settlementDate"), request.getSubmittedFromDate())); + } + if (request.getSubmittedToDate() != null) { + predicates.add(cb.lessThanOrEqualTo(r.get("settlementDate"), request.getSubmittedToDate())); + } + + if (request.getEffectiveFromDate() != null) { + predicates.add(cb.greaterThanOrEqualTo(r.get("effectiveDateFrom"), request.getEffectiveFromDate())); + } + if (request.getEffectiveToDate() != null) { + predicates.add(cb.lessThanOrEqualTo(r.get("effectiveDateTo"), request.getEffectiveToDate())); + } + + return cb.and(predicates.toArray(new Predicate[0])); + }; + criteriaQueryFactory.applySpecificationToCriteria(root, spec, query); + + List orders = criteriaQueryFactory.ordersFromPageable(pageable, cb, root, () -> cb.desc(root.get("settlementDate"))); + query.orderBy(orders); + + query.select(cb.construct(SearchedExternalAssetOwner.class, root.get("id"), root.get("loanId"), root.get("externalLoanId"), + owner.get("externalId"), root.get("externalId"), root.get("status"), root.get("subStatus"), root.get("purchasePriceRatio"), + root.get("settlementDate"), root.get("effectiveDateFrom"), root.get("effectiveDateTo"), details.get("id"), + details.get("totalOutstanding"), details.get("totalPrincipalOutstanding"), details.get("totalInterestOutstanding"), + details.get("totalFeeChargesOutstanding"), details.get("totalPenaltyChargesOutstanding"), details.get("totalOverpaid"))); + + TypedQuery queryToExecute = entityManager.createQuery(query); + return criteriaQueryFactory.readPage(queryToExecute, ExternalAssetOwnerTransfer.class, pageable, spec); + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/ExternalAssetOwnerSearchService.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/ExternalAssetOwnerSearchService.java new file mode 100644 index 00000000000..10f92e0659d --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/ExternalAssetOwnerSearchService.java @@ -0,0 +1,53 @@ +/** + * 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.investor.service.search; + +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.PagedRequest; +import org.apache.fineract.investor.data.ExternalTransferData; +import org.apache.fineract.investor.domain.search.SearchingExternalAssetOwnerRepository; +import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest; +import org.apache.fineract.investor.service.search.mapper.ExternalAssetOwnerSearchDataMapper; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ExternalAssetOwnerSearchService { + + private final SearchingExternalAssetOwnerRepository externalAssetOwnerRepository; + private final ExternalAssetOwnerSearchDataMapper externalAssetOwnerSearchDataMapper; + + public Page searchInvestorData(PagedRequest searchRequest) { + validateTextSearchRequest(searchRequest); + return executeSearch(searchRequest); + } + + private void validateTextSearchRequest(PagedRequest searchRequest) { + Objects.requireNonNull(searchRequest, "searchRequest must not be null"); + } + + private Page executeSearch(PagedRequest searchRequest) { + return externalAssetOwnerRepository.searchInvestorData(searchRequest).map(externalAssetOwnerSearchDataMapper::map); + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java new file mode 100644 index 00000000000..07acd793e34 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java @@ -0,0 +1,32 @@ +/** + * 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.investor.service.search.domain; + +import java.time.LocalDate; +import lombok.Data; + +@Data +public class ExternalAssetOwnerSearchRequest { + + private String text; + private LocalDate submittedFromDate; + private LocalDate submittedToDate; + private LocalDate effectiveFromDate; + private LocalDate effectiveToDate; +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/mapper/ExternalAssetOwnerSearchDataMapper.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/mapper/ExternalAssetOwnerSearchDataMapper.java new file mode 100644 index 00000000000..1a171b4ef5e --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/mapper/ExternalAssetOwnerSearchDataMapper.java @@ -0,0 +1,77 @@ +/** + * 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.investor.service.search.mapper; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.investor.data.ExternalTransferData; +import org.apache.fineract.investor.data.ExternalTransferDataDetails; +import org.apache.fineract.investor.data.ExternalTransferLoanData; +import org.apache.fineract.investor.data.ExternalTransferOwnerData; +import org.apache.fineract.investor.data.ExternalTransferStatus; +import org.apache.fineract.investor.data.ExternalTransferSubStatus; +import org.apache.fineract.investor.domain.search.SearchedExternalAssetOwner; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class) +public interface ExternalAssetOwnerSearchDataMapper { + + @Mapping(target = "owner", source = "source", qualifiedByName = "toOwner") + @Mapping(target = "loan", source = "source", qualifiedByName = "toLoanExternalId") + @Mapping(target = "transferExternalId", source = "source", qualifiedByName = "toTransferExternalId") + @Mapping(target = "status", source = "source", qualifiedByName = "toStatus") + @Mapping(target = "subStatus", source = "source", qualifiedByName = "toSubStatus") + @Mapping(target = "details", source = "source", qualifiedByName = "toDetails") + ExternalTransferData map(SearchedExternalAssetOwner source); + + @Named("toTransferExternalId") + default String toTransferExternalId(SearchedExternalAssetOwner source) { + return source.getTransferExternalId().getValue(); + } + + @Named("toLoanExternalId") + default ExternalTransferLoanData toLoanExternalId(SearchedExternalAssetOwner source) { + return new ExternalTransferLoanData(source.getLoanId(), source.getExternalLoanId().getValue()); + } + + @Named("toOwner") + default ExternalTransferOwnerData toOwner(SearchedExternalAssetOwner source) { + return new ExternalTransferOwnerData(source.getOwner().getValue()); + } + + @Named("toStatus") + default ExternalTransferStatus toStatus(SearchedExternalAssetOwner source) { + return source.getStatus(); + } + + @Named("toSubStatus") + default ExternalTransferSubStatus toSubStatus(SearchedExternalAssetOwner source) { + return source.getSubStatus(); + } + + @Named("toDetails") + default ExternalTransferDataDetails toDetails(SearchedExternalAssetOwner source) { + if (source.getDetailsId() == null) { + return null; + } + return new ExternalTransferDataDetails(source.getDetailsId(), source.getTotalOutstanding(), source.getPrincipalOutstanding(), + source.getInterestOutstanding(), source.getFeeOutstanding(), source.getPenaltyOutstanding(), source.getTotalOverpaid()); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java index ea7831136f5..2edba122e70 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java @@ -18,14 +18,24 @@ */ package org.apache.fineract.integrationtests.common; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import org.apache.fineract.accounting.common.AccountingConstants; +import org.apache.fineract.client.models.ExternalAssetOwnerSearchRequest; import org.apache.fineract.client.models.ExternalOwnerJournalEntryData; import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData; import org.apache.fineract.client.models.ExternalTransferData; +import org.apache.fineract.client.models.GetFinancialActivityAccountsResponse; import org.apache.fineract.client.models.PageExternalTransferData; +import org.apache.fineract.client.models.PagedRequestExternalAssetOwnerSearchRequest; +import org.apache.fineract.client.models.PostFinancialActivityAccountsRequest; import org.apache.fineract.client.models.PostInitiateTransferRequest; import org.apache.fineract.client.models.PostInitiateTransferResponse; import org.apache.fineract.client.util.Calls; import org.apache.fineract.integrationtests.client.IntegrationTest; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper; import retrofit2.Response; public class ExternalAssetOwnerHelper extends IntegrationTest { @@ -82,4 +92,36 @@ public ExternalOwnerJournalEntryData retrieveJournalEntriesOfOwner(String ownerE return ok(fineract().externalAssetOwners.getJournalEntriesOfOwner(ownerExternalId, 0, 100)); } + public PageExternalTransferData searchExternalAssetOwnerTransfer(PagedRequestExternalAssetOwnerSearchRequest request) { + return ok(fineract().externalAssetOwners.searchInvestorData(request)); + } + + public PagedRequestExternalAssetOwnerSearchRequest buildExternalAssetOwnerSearchRequest(String text, String attribute, + LocalDate fromDate, LocalDate toDate, Integer page, Integer size) { + final Integer DEFAULT_PAGE_SIZE = 50; + PagedRequestExternalAssetOwnerSearchRequest pagedRequest = new PagedRequestExternalAssetOwnerSearchRequest(); + ExternalAssetOwnerSearchRequest searchRequest = new ExternalAssetOwnerSearchRequest(); + searchRequest.text(text); + if (attribute.equals("effective")) { + searchRequest.setEffectiveFromDate(fromDate); + searchRequest.setEffectiveToDate(toDate); + } else if (attribute.equals("settlement")) { + searchRequest.setSubmittedFromDate(fromDate); + searchRequest.setSubmittedToDate(toDate); + } + pagedRequest.setRequest(searchRequest); + pagedRequest.setSorts(new ArrayList<>()); + pagedRequest.setPage(page != null ? page : 0); + pagedRequest.setSize(size != null ? size : DEFAULT_PAGE_SIZE); + return pagedRequest; + } + + public void setProperFinancialActivity(FinancialActivityAccountHelper financialActivityAccountHelper, Account transferAccount) { + List financialMappings = financialActivityAccountHelper.getAllFinancialActivityAccounts(); + financialMappings.forEach(mapping -> financialActivityAccountHelper.deleteFinancialActivityAccount(mapping.getId())); + financialActivityAccountHelper.createFinancialActivityAccount(new PostFinancialActivityAccountsRequest() + .financialActivityId((long) AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue()) + .glAccountId((long) transferAccount.getAccountID())); + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java index 778575c21bf..3a52a10abac 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java @@ -491,6 +491,10 @@ public static String convertToJson(HashMap map) { return new Gson().toJson(map); } + public static String convertToJson(Object o) { + return gson.toJson(o); + } + public static LocalDate getDateAsLocalDate(String dateAsString) { return LocalDate.parse(dateAsString, dateFormatter); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferTest.java new file mode 100644 index 00000000000..132f28cb7bf --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferTest.java @@ -0,0 +1,410 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.investor.externalassetowner; + +import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.path.json.JsonPath; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.models.ExternalOwnerJournalEntryData; +import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData; +import org.apache.fineract.client.models.ExternalTransferData; +import org.apache.fineract.client.models.PageExternalTransferData; +import org.apache.fineract.client.models.PostInitiateTransferRequest; +import org.apache.fineract.client.models.PostInitiateTransferResponse; +import org.apache.fineract.integrationtests.common.BusinessDateHelper; +import org.apache.fineract.integrationtests.common.BusinessStepHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CollateralManagementHelper; +import org.apache.fineract.integrationtests.common.ExternalAssetOwnerHelper; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.SchedulerJobHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.AccountHelper; +import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; + +@Slf4j +public class ExternalAssetOwnerTransferTest { + + protected static ResponseSpecification RESPONSE_SPEC; + protected static RequestSpecification REQUEST_SPEC; + protected static Account ASSET_ACCOUNT; + protected static Account FEE_PENALTY_ACCOUNT; + protected static Account TRANSFER_ACCOUNT; + protected static Account EXPENSE_ACCOUNT; + protected static Account INCOME_ACCOUNT; + protected static Account OVERPAYMENT_ACCOUNT; + protected static FinancialActivityAccountHelper FINANCIAL_ACTIVITY_ACCOUNT_HELPER; + protected static ExternalAssetOwnerHelper EXTERNAL_ASSET_OWNER_HELPER; + protected static LoanTransactionHelper LOAN_TRANSACTION_HELPER; + protected static SchedulerJobHelper SCHEDULER_JOB_HELPER; + protected static LocalDate TODAYS_DATE; + public String ownerExternalId; + protected DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + + @BeforeAll + public static void setupInvestorBusinessStep() { + Utils.initializeRESTAssured(); + REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); + AccountHelper accountHelper = new AccountHelper(REQUEST_SPEC, RESPONSE_SPEC); + EXTERNAL_ASSET_OWNER_HELPER = new ExternalAssetOwnerHelper(); + SCHEDULER_JOB_HELPER = new SchedulerJobHelper(REQUEST_SPEC); + FINANCIAL_ACTIVITY_ACCOUNT_HELPER = new FinancialActivityAccountHelper(REQUEST_SPEC); + LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); + + TODAYS_DATE = Utils.getLocalDateOfTenant(); + new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", + "CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES", + "EXTERNAL_ASSET_OWNER_TRANSFER"); + + ASSET_ACCOUNT = accountHelper.createAssetAccount(); + FEE_PENALTY_ACCOUNT = accountHelper.createAssetAccount(); + TRANSFER_ACCOUNT = accountHelper.createAssetAccount(); + EXPENSE_ACCOUNT = accountHelper.createExpenseAccount(); + INCOME_ACCOUNT = accountHelper.createIncomeAccount(); + OVERPAYMENT_ACCOUNT = accountHelper.createLiabilityAccount(); + + EXTERNAL_ASSET_OWNER_HELPER.setProperFinancialActivity(FINANCIAL_ACTIVITY_ACCOUNT_HELPER, TRANSFER_ACCOUNT); + } + + protected void updateBusinessDateAndExecuteCOBJob(String date) { + BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, LocalDate.parse(date)); + SCHEDULER_JOB_HELPER.executeAndAwaitJob("Loan COB"); + } + + protected PostInitiateTransferResponse createSaleTransfer(Integer loanID, String settlementDate) { + String transferExternalId = UUID.randomUUID().toString(); + ownerExternalId = UUID.randomUUID().toString(); + return createSaleTransfer(loanID, settlementDate, transferExternalId, ownerExternalId, "1.0"); + } + + protected PostInitiateTransferResponse createSaleTransfer(Integer loanID, String settlementDate, String transferExternalId, + String ownerExternalId, String purchasePriceRatio) { + PostInitiateTransferResponse saleResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanID.longValue(), "sale", + new PostInitiateTransferRequest().settlementDate(settlementDate).dateFormat("yyyy-MM-dd").locale("en") + .transferExternalId(transferExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio(purchasePriceRatio)); + assertEquals(transferExternalId, saleResponse.getResourceExternalId()); + return saleResponse; + } + + protected PostInitiateTransferResponse createBuybackTransfer(Integer loanID, String settlementDate) { + String transferExternalId = UUID.randomUUID().toString(); + return createBuybackTransfer(loanID, settlementDate, transferExternalId); + } + + protected PostInitiateTransferResponse createBuybackTransfer(Integer loanID, String settlementDate, String transferExternalId) { + PostInitiateTransferResponse saleResponse = EXTERNAL_ASSET_OWNER_HELPER.initiateTransferByLoanId(loanID.longValue(), "buyback", + new PostInitiateTransferRequest().settlementDate(settlementDate).dateFormat("yyyy-MM-dd").locale("en") + .transferExternalId(transferExternalId)); + assertEquals(transferExternalId, saleResponse.getResourceExternalId()); + return saleResponse; + } + + protected void addPenaltyForLoan(Integer loanID, String amount) { + // Add Charge Penalty + Integer penalty = ChargesHelper.createCharges(REQUEST_SPEC, RESPONSE_SPEC, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, amount, true)); + Integer penalty1LoanChargeId = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "02 March 2020", amount)); + assertNotNull(penalty1LoanChargeId); + } + + protected void setInitialBusinessDate(String date) { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, LocalDate.parse(date)); + GlobalConfigurationHelper.updateValueForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "10", "0"); + } + + protected void cleanUpAndRestoreBusinessDate() { + REQUEST_SPEC = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + REQUEST_SPEC.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + REQUEST_SPEC.header("Fineract-Platform-TenantId", "default"); + RESPONSE_SPEC = new ResponseSpecBuilder().expectStatusCode(200).build(); + BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, TODAYS_DATE); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC, RESPONSE_SPEC, + GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, false); + } + + @NotNull + protected Integer createClient() { + final Integer clientID = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC); + Assertions.assertNotNull(clientID); + return clientID; + } + + @NotNull + protected Integer createLoanForClient(Integer clientID, String transactionDate) { + Integer overdueFeeChargeId = ChargesHelper.createCharges(REQUEST_SPEC, RESPONSE_SPEC, + ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1")); + Assertions.assertNotNull(overdueFeeChargeId); + + Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString()); + Assertions.assertNotNull(loanProductID); + HashMap loanStatusHashMap; + + Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), transactionDate); + + Assertions.assertNotNull(loanID); + + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(REQUEST_SPEC, RESPONSE_SPEC, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = LOAN_TRANSACTION_HELPER.approveLoan(transactionDate, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + String loanDetails = LOAN_TRANSACTION_HELPER.getLoanDetails(REQUEST_SPEC, RESPONSE_SPEC, loanID); + loanStatusHashMap = LOAN_TRANSACTION_HELPER.disburseLoanWithNetDisbursalAmount(transactionDate, loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + return loanID; + } + + protected Integer createLoanProduct(final String chargeId) { + + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4") + .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1") + .withAccountingRulePeriodicAccrual(new Account[] { ASSET_ACCOUNT, EXPENSE_ACCOUNT, INCOME_ACCOUNT, OVERPAYMENT_ACCOUNT }) + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .withFeeAndPenaltyAssetAccount(FEE_PENALTY_ACCOUNT).build(chargeId); + return LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON); + } + + protected Integer applyForLoanApplication(final String clientID, final String loanProductID, final String date) { + List collaterals = new ArrayList<>(); + Integer collateralId = CollateralManagementHelper.createCollateralProduct(REQUEST_SPEC, RESPONSE_SPEC); + Assertions.assertNotNull(collateralId); + Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(REQUEST_SPEC, RESPONSE_SPEC, clientID, collateralId); + Assertions.assertNotNull(clientCollateralId); + addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("15,000.00").withLoanTermFrequency("4") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("4").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments() + .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeSameAsRepaymentPeriod() + .withExpectedDisbursementDate(date).withSubmittedOnDate(date).withCollaterals(collaterals) + .build(clientID, loanProductID, null); + return LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON); + } + + protected void addCollaterals(List collaterals, Integer collateralId, BigDecimal quantity) { + collaterals.add(collaterals(collateralId, quantity)); + } + + protected HashMap collaterals(Integer collateralId, BigDecimal quantity) { + HashMap collateral = new HashMap<>(2); + collateral.put("clientCollateralId", collateralId.toString()); + collateral.put("quantity", quantity.toString()); + return collateral; + } + + protected void getAndValidateExternalAssetOwnerTransferByLoan(Integer loanID, ExpectedExternalTransferData... expectedItems) { + PageExternalTransferData retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + assertEquals(expectedItems.length, retrieveResponse.getNumberOfElements()); + validateExternalAssetOwnerTransfer(retrieveResponse, expectedItems); + } + + protected void validateExternalAssetOwnerTransfer(PageExternalTransferData response, ExpectedExternalTransferData... expectedItems) { + for (ExpectedExternalTransferData expected : expectedItems) { + assertNotNull(response.getContent()); + Optional first = response.getContent().stream() + .filter(e -> Objects.equals(e.getTransferExternalId(), expected.transferExternalId) + && Objects.equals(e.getStatus(), expected.status)) + .findFirst(); + assertTrue(first.isPresent()); + ExternalTransferData etd = first.get(); + assertEquals(expected.transferExternalId, etd.getTransferExternalId()); + assertEquals(expected.status, etd.getStatus()); + assertEquals(LocalDate.parse(expected.settlementDate), etd.getSettlementDate()); + assertEquals(LocalDate.parse(expected.effectiveFrom), etd.getEffectiveFrom()); + assertEquals(LocalDate.parse(expected.effectiveTo), etd.getEffectiveTo()); + if (!expected.detailsExpected) { + assertNull(etd.getDetails()); + } else { + assertNotNull(etd.getDetails()); + assertEquals(expected.totalOutstanding, etd.getDetails().getTotalOutstanding()); + assertEquals(expected.totalPrincipalOutstanding, etd.getDetails().getTotalPrincipalOutstanding()); + assertEquals(expected.totalInterestOutstanding, etd.getDetails().getTotalInterestOutstanding()); + assertEquals(expected.totalPenaltyOutstanding, etd.getDetails().getTotalPenaltyChargesOutstanding()); + assertEquals(expected.totalFeeOutstanding, etd.getDetails().getTotalFeeChargesOutstanding()); + assertEquals(expected.totalOverpaid, etd.getDetails().getTotalOverpaid()); + } + if (expected.subStatus != null) { + assertEquals(expected.subStatus, etd.getSubStatus()); + } + } + } + + protected void getAndValidateThereIsActiveMapping(Integer loanID) { + ExternalTransferData activeTransfer = EXTERNAL_ASSET_OWNER_HELPER.retrieveActiveTransferByLoanId((long) loanID); + assertNotNull(activeTransfer); + ExternalTransferData retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()).getContent() + .stream().filter(transfer -> ExternalTransferData.StatusEnum.ACTIVE.equals(transfer.getStatus())).findFirst().get(); + assertEquals(retrieveResponse.getTransferId(), activeTransfer.getTransferId()); + } + + protected void getAndValidateThereIsNoActiveMapping(Long loanId) { + ExternalTransferData activeTransfer = EXTERNAL_ASSET_OWNER_HELPER.retrieveActiveTransferByLoanId(loanId); + assertNull(activeTransfer); + } + + protected void getAndValidateThereIsNoActiveMapping(String transferExternalId) { + ExternalTransferData activeTransfer = EXTERNAL_ASSET_OWNER_HELPER.retrieveActiveTransferByTransferExternalId(transferExternalId); + assertNull(activeTransfer); + } + + protected void validateResponse(PostInitiateTransferResponse transferResponse, Integer loanID) { + assertNotNull(transferResponse); + assertNotNull(transferResponse.getResourceId()); + assertNotNull(transferResponse.getResourceExternalId()); + assertNotNull(transferResponse.getSubResourceId()); + assertEquals((long) loanID, transferResponse.getSubResourceId()); + assertNotNull(transferResponse.getSubResourceExternalId()); + assertNull(transferResponse.getChanges()); + } + + protected void getAndValidateOwnerJournalEntries(String ownerExternalId, ExpectedJournalEntryData... expectedItems) { + ExternalOwnerJournalEntryData result = EXTERNAL_ASSET_OWNER_HELPER.retrieveJournalEntriesOfOwner(ownerExternalId); + assertNotNull(result); + assertEquals(expectedItems.length, result.getJournalEntryData().getTotalElements()); + int i = 0; + assertEquals(ownerExternalId, result.getOwnerData().getExternalId()); + for (ExpectedJournalEntryData expectedJournalEntryData : expectedItems) { + assertTrue(expectedJournalEntryData.amount.compareTo(result.getJournalEntryData().getContent().get(i).getAmount()) == 0); + assertEquals(expectedJournalEntryData.entryTypeId, result.getJournalEntryData().getContent().get(i).getEntryType().getId()); + assertEquals(expectedJournalEntryData.glAccountId, result.getJournalEntryData().getContent().get(i).getGlAccountId()); + assertEquals(expectedJournalEntryData.transactionDate, result.getJournalEntryData().getContent().get(i).getTransactionDate()); + assertEquals(expectedJournalEntryData.submittedOnDate, result.getJournalEntryData().getContent().get(i).getSubmittedOnDate()); + i++; + } + } + + protected void getAndValidateThereIsJournalEntriesForTransfer(Long transferId, ExpectedJournalEntryData... expectedItems) { + ExternalOwnerTransferJournalEntryData result = EXTERNAL_ASSET_OWNER_HELPER.retrieveJournalEntriesOfTransfer(transferId); + assertNotNull(result); + long totalElements = result.getJournalEntryData().getTotalElements(); + assertEquals(expectedItems.length, totalElements); + int i = 0; + assertEquals(transferId, result.getTransferData().getTransferId()); + for (ExpectedJournalEntryData expectedJournalEntryData : expectedItems) { + assertTrue(expectedJournalEntryData.amount.compareTo(result.getJournalEntryData().getContent().get(i).getAmount()) == 0); + assertEquals(expectedJournalEntryData.entryTypeId, result.getJournalEntryData().getContent().get(i).getEntryType().getId()); + assertEquals(expectedJournalEntryData.glAccountId, result.getJournalEntryData().getContent().get(i).getGlAccountId()); + assertEquals(expectedJournalEntryData.transactionDate, result.getJournalEntryData().getContent().get(i).getTransactionDate()); + assertEquals(expectedJournalEntryData.submittedOnDate, result.getJournalEntryData().getContent().get(i).getSubmittedOnDate()); + i++; + } + } + + protected void getAndValidateThereIsNoJournalEntriesForTransfer(Long transferId) { + ExternalOwnerTransferJournalEntryData result = EXTERNAL_ASSET_OWNER_HELPER.retrieveJournalEntriesOfTransfer(transferId); + assertNull(result.getJournalEntryData()); + } + + @RequiredArgsConstructor() + public static class ExpectedExternalTransferData { + + private final ExternalTransferData.StatusEnum status; + + private final String transferExternalId; + + private final String settlementDate; + + private final String effectiveFrom; + private final String effectiveTo; + private final ExternalTransferData.SubStatusEnum subStatus; + private final boolean detailsExpected; + private final BigDecimal totalOutstanding; + private final BigDecimal totalPrincipalOutstanding; + private final BigDecimal totalInterestOutstanding; + private final BigDecimal totalPenaltyOutstanding; + private final BigDecimal totalFeeOutstanding; + private final BigDecimal totalOverpaid; + + static ExpectedExternalTransferData expected(ExternalTransferData.StatusEnum status, String transferExternalId, + String settlementDate, String effectiveFrom, String effectiveTo, boolean detailsExpected, BigDecimal totalOutstanding, + BigDecimal totalPrincipalOutstanding, BigDecimal totalInterestOutstanding, BigDecimal totalPenaltyOutstanding, + BigDecimal totalFeeOutstanding, BigDecimal totalOverpaid) { + return new ExpectedExternalTransferData(status, transferExternalId, settlementDate, effectiveFrom, effectiveTo, null, + detailsExpected, totalOutstanding, totalPrincipalOutstanding, totalInterestOutstanding, totalPenaltyOutstanding, + totalFeeOutstanding, totalOverpaid); + } + + static ExpectedExternalTransferData expected(ExternalTransferData.StatusEnum status, String transferExternalId, + String settlementDate, String effectiveFrom, String effectiveTo) { + return new ExpectedExternalTransferData(status, transferExternalId, settlementDate, effectiveFrom, effectiveTo, null, false, + null, null, null, null, null, null); + } + + static ExpectedExternalTransferData expected(ExternalTransferData.StatusEnum status, String transferExternalId, + String settlementDate, String effectiveFrom, String effectiveTo, ExternalTransferData.SubStatusEnum subStatus) { + return new ExpectedExternalTransferData(status, transferExternalId, settlementDate, effectiveFrom, effectiveTo, subStatus, + false, null, null, null, null, null, null); + } + } + + @RequiredArgsConstructor() + public static class ExpectedJournalEntryData { + + private final Long glAccountId; + + private final Long entryTypeId; + + private final BigDecimal amount; + private final LocalDate transactionDate; + private final LocalDate submittedOnDate; + + static ExpectedJournalEntryData expected(Long glAccountId, Long entryTypeId, BigDecimal amount, LocalDate transactionDate, + LocalDate submittedOnDate) { + return new ExpectedJournalEntryData(glAccountId, entryTypeId, amount, transactionDate, submittedOnDate); + } + + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java new file mode 100644 index 00000000000..13a0c2a4018 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java @@ -0,0 +1,208 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.investor.externalassetowner; + +import static org.apache.fineract.client.models.ExternalTransferData.StatusEnum.CANCELLED; +import static org.apache.fineract.client.models.ExternalTransferData.StatusEnum.PENDING; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.models.PageExternalTransferData; +import org.apache.fineract.client.models.PagedRequestExternalAssetOwnerSearchRequest; +import org.apache.fineract.client.models.PostInitiateTransferResponse; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@Slf4j +@ExtendWith(LoanTestLifecycleExtension.class) +public class SearchExternalAssetOwnerTransferTest extends ExternalAssetOwnerTransferTest { + + @Test + public void saleActiveLoanToExternalAssetOwnerWithSearching() { + final String baseDate = "2020-02-29"; + LocalDate baseLocalDate = Utils.getDateAsLocalDate("29 February 2020"); + + try { + GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC, RESPONSE_SPEC, + GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, true); + setInitialBusinessDate("2020-02-29"); + Integer clientID = createClient(); + Integer loanID = createLoanForClient(clientID, "29 February 2020"); + addPenaltyForLoan(loanID, "10"); + + PostInitiateTransferResponse saleTransferResponse = createSaleTransfer(loanID, baseDate); + validateResponse(saleTransferResponse, loanID); + + // LookUp by ExternalId + String externalId = saleTransferResponse.getResourceExternalId(); + PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER + .buildExternalAssetOwnerSearchRequest(externalId, "", null, null, null, null); + PageExternalTransferData response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + + validateExternalAssetOwnerTransfer(response, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + "9999-12-31", false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("10.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + // LookUp by Effective Date + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest("", "settlement", baseLocalDate, null, null, + null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + + validateExternalAssetOwnerTransfer(response, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + "9999-12-31", false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("10.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + + // Cancel the External Asset Transfer + EXTERNAL_ASSET_OWNER_HELPER.cancelTransferByTransferExternalId(saleTransferResponse.getResourceExternalId()); + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(externalId, "", null, null, null, null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + + validateExternalAssetOwnerTransfer(response, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + baseDate, false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), new BigDecimal("757.420000"), + new BigDecimal("10.000000"), new BigDecimal("0.000000"), new BigDecimal("0.000000")), + ExpectedExternalTransferData.expected(CANCELLED, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + baseDate, false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), new BigDecimal("757.420000"), + new BigDecimal("10.000000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"))); + + // LookUp by Effective Date + // LookUp by Effective Date + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest("", "effective", baseLocalDate, baseLocalDate, + null, null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + + validateExternalAssetOwnerTransfer(response, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + baseDate, false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), new BigDecimal("757.420000"), + new BigDecimal("10.000000"), new BigDecimal("0.000000"), new BigDecimal("0.000000")), + ExpectedExternalTransferData.expected(CANCELLED, saleTransferResponse.getResourceExternalId(), baseDate, baseDate, + baseDate, false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), new BigDecimal("757.420000"), + new BigDecimal("10.000000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"))); + + } finally { + cleanUpAndRestoreBusinessDate(); + } + } + + @Test + public void initialSearchExternalAssetOwnerTransferUsingTextTest() { + String textToSearch = UUID.randomUUID().toString(); + PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER + .buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 10); + PageExternalTransferData response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + assertNotNull(response); + assertEquals("Expecting none result", 0, response.getContent().size()); + + // Search over the current Asset Transfers and get just the first five + textToSearch = ""; + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 5); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + assertNotNull(response); + assertEquals("Expecting first five results", 5, response.getContent().size()); + + textToSearch = response.getContent().iterator().next().getOwner().getExternalId(); + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 5); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + assertNotNull(response); + assertTrue("Expecting only two results", response.getContent().size() >= 2); + assertEquals("External Id is different", textToSearch, response.getContent().iterator().next().getOwner().getExternalId()); + } + + @Test + public void initialSearchExternalAssetOwnerTransferUsingEffectiveDateTest() { + final String attribute = "effective"; + LocalDate fromDate = Utils.getDateAsLocalDate("01 March 2023"); + LocalDate toDate = fromDate.plusMonths(3); + PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(null, + attribute, fromDate, toDate, 0, null); + PageExternalTransferData response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 0); + + fromDate = Utils.getDateAsLocalDate("01 January 2020"); + toDate = fromDate.plusMonths(6); + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(null, attribute, fromDate, toDate, 0, null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 1); + assertTrue("Transfers were not found", response.getContent().size() > 0); + } + + @Test + public void initialSearchExternalAssetOwnerTransferUsingSubmittedDateTest() { + final String attribute = "settlement"; + LocalDate fromDate = Utils.getDateAsLocalDate("01 March 2023"); + LocalDate toDate = fromDate.plusMonths(3); + PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(null, + attribute, fromDate, toDate, 0, null); + PageExternalTransferData response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 0); + + fromDate = Utils.getDateAsLocalDate("01 February 2020"); + toDate = fromDate.plusMonths(3); + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(null, attribute, fromDate, toDate, 0, null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 1); + assertTrue("Transfers were not found", response.getContent().size() > 0); + } + + @Test + public void initialSearchExternalAssetOwnerTransferUsingTextAndDatesTest() { + final String textToSearch = UUID.randomUUID().toString(); + final String attribute = "settlement"; + LocalDate fromDate = Utils.getDateAsLocalDate("01 March 2023"); + LocalDate toDate = fromDate.plusMonths(3); + PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER + .buildExternalAssetOwnerSearchRequest(textToSearch, attribute, fromDate, toDate, 0, null); + PageExternalTransferData response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 0); + + fromDate = Utils.getDateAsLocalDate("01 February 2020"); + toDate = fromDate.plusMonths(3); + searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, attribute, fromDate, toDate, 0, + null); + response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest); + validateResponse(response, 0); + } + + private void validateResponse(PageExternalTransferData response, final Integer size) { + assertNotNull(response); + final boolean isEmpty = (size == 0); + assertEquals(isEmpty, response.getEmpty()); + assertEquals(true, response.getFirst()); + if (isEmpty) { + assertTrue("Transfers size difference", response.getContent().size() == size); + assertTrue("Total pages difference", response.getTotalPages() == 0); + } else { + assertTrue("Total pages difference", response.getTotalPages() > 0); + assertTrue("Total number of elements difference", response.getNumberOfElements() > 0); + } + assertEquals(true, response.getFirst()); + } + +}