This is an automated email from the ASF dual-hosted git repository.

adamsaghy 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 4a2aeff35 FINERACT-1926: Asset Externalization on Fineract
4a2aeff35 is described below

commit 4a2aeff350dda04b4643f7e69748b295718e1ddc
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Thu Jul 27 21:31:18 2023 -0600

    FINERACT-1926: Asset Externalization on Fineract
---
 .../HttpMessageNotReadableErrorController.java     |  54 +++
 .../api/ExternalAssetOwnersApiResource.java        |  15 +
 .../api/ExternalAssetOwnersApiResourceSwagger.java |  69 ++--
 .../search/ExternalAssetOwnersSearchApi.java}      |  20 +-
 .../ExternalAssetOwnersSearchApiDelegate.java      |  40 ++
 .../investor/data/ExternalTransferDataDetails.java |   4 +
 .../domain/search/SearchedExternalAssetOwner.java  |  55 +++
 .../SearchingExternalAssetOwnerRepository.java}    |  19 +-
 .../SearchingExternalAssetOwnerRepositoryImpl.java | 106 ++++++
 .../search/ExternalAssetOwnerSearchService.java    |  53 +++
 .../domain/ExternalAssetOwnerSearchRequest.java}   |  18 +-
 .../mapper/ExternalAssetOwnerSearchDataMapper.java |  77 ++++
 .../common/ExternalAssetOwnerHelper.java           |  42 +++
 .../fineract/integrationtests/common/Utils.java    |   4 +
 .../ExternalAssetOwnerTransferTest.java            | 410 +++++++++++++++++++++
 .../SearchExternalAssetOwnerTransferTest.java      | 208 +++++++++++
 16 files changed, 1126 insertions(+), 68 deletions(-)

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 000000000..2449ad4ca
--- /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<HttpMessageNotReadableException>, 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 2a773e939..3f92519b0 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.domain.ExternalId;
 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<ExternalTransferResponseData> 
postApiJsonSerializerService;
     private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
     private final LoanReadPlatformServiceCommon loanReadPlatformService;
+    private final ExternalAssetOwnersSearchApiDelegate delegate;
 
     @POST
     @Path("/transfers/loans/{loanId}")
@@ -190,6 +194,17 @@ public class ExternalAssetOwnersApiResource {
 
     }
 
+    @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<ExternalTransferData> searchInvestorData(@Parameter 
PagedRequest<ExternalAssetOwnerSearchRequest> 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 6648770ba..e8c032d1c 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 @@ final class ExternalAssetOwnersApiResourceSwagger {
             public String purchasePriceRatio;
         }
     }
+
 }
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/api/search/ExternalAssetOwnersSearchApi.java
similarity index 63%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java
index 4dd5d551c..dde0dc564 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/api/search/ExternalAssetOwnersSearchApi.java
@@ -16,19 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.data;
+package org.apache.fineract.investor.api.search;
 
-import java.math.BigDecimal;
-import lombok.Data;
+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;
 
-@Data
-public class ExternalTransferDataDetails {
+public interface ExternalAssetOwnersSearchApi {
+
+    Page<ExternalTransferData> 
searchInvestorData(PagedRequest<ExternalAssetOwnerSearchRequest> request);
 
-    private Long detailsId;
-    private BigDecimal totalOutstanding;
-    private BigDecimal totalPrincipalOutstanding;
-    private BigDecimal totalInterestOutstanding;
-    private BigDecimal totalFeeChargesOutstanding;
-    private BigDecimal totalPenaltyChargesOutstanding;
-    private BigDecimal totalOverpaid;
 }
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 000000000..861404dc1
--- /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<ExternalTransferData> 
searchInvestorData(PagedRequest<ExternalAssetOwnerSearchRequest> 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 4dd5d551c..2e8d509da 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 000000000..47ac27321
--- /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/data/ExternalTransferDataDetails.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java
similarity index 64%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java
index 4dd5d551c..44683acb6 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/search/SearchingExternalAssetOwnerRepository.java
@@ -16,19 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.data;
+package org.apache.fineract.investor.domain.search;
 
-import java.math.BigDecimal;
-import lombok.Data;
+import org.apache.fineract.infrastructure.core.service.PagedRequest;
+import 
org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest;
+import org.springframework.data.domain.Page;
 
-@Data
-public class ExternalTransferDataDetails {
+public interface SearchingExternalAssetOwnerRepository {
+
+    Page<SearchedExternalAssetOwner> 
searchInvestorData(PagedRequest<ExternalAssetOwnerSearchRequest> searchRequest);
 
-    private Long detailsId;
-    private BigDecimal totalOutstanding;
-    private BigDecimal totalPrincipalOutstanding;
-    private BigDecimal totalInterestOutstanding;
-    private BigDecimal totalFeeChargesOutstanding;
-    private BigDecimal totalPenaltyChargesOutstanding;
-    private BigDecimal totalOverpaid;
 }
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 000000000..25566052f
--- /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<SearchedExternalAssetOwner> 
searchInvestorData(PagedRequest<ExternalAssetOwnerSearchRequest> searchRequest) 
{
+        final ExternalAssetOwnerSearchRequest request = 
searchRequest.getRequest().get();
+        final Pageable pageable = searchRequest.toPageable();
+
+        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+        CriteriaQuery<SearchedExternalAssetOwner> query = 
cb.createQuery(SearchedExternalAssetOwner.class);
+
+        Root<ExternalAssetOwnerTransfer> root = 
query.from(ExternalAssetOwnerTransfer.class);
+        Path<ExternalAssetOwnerTransferDetails> details = 
root.join("externalAssetOwnerTransferDetails", JoinType.LEFT);
+        Path<ExternalAssetOwner> owner = root.get("owner");
+
+        Specification<ExternalAssetOwnerTransfer> spec = (r, q, builder) -> {
+            Path<ExternalAssetOwner> o = r.get("owner");
+
+            List<Predicate> 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<Order> 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<SearchedExternalAssetOwner> 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 000000000..10f92e065
--- /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<ExternalTransferData> 
searchInvestorData(PagedRequest<ExternalAssetOwnerSearchRequest> searchRequest) 
{
+        validateTextSearchRequest(searchRequest);
+        return executeSearch(searchRequest);
+    }
+
+    private void 
validateTextSearchRequest(PagedRequest<ExternalAssetOwnerSearchRequest> 
searchRequest) {
+        Objects.requireNonNull(searchRequest, "searchRequest must not be 
null");
+    }
+
+    private Page<ExternalTransferData> 
executeSearch(PagedRequest<ExternalAssetOwnerSearchRequest> searchRequest) {
+        return 
externalAssetOwnerRepository.searchInvestorData(searchRequest).map(externalAssetOwnerSearchDataMapper::map);
+    }
+
+}
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/service/search/domain/ExternalAssetOwnerSearchRequest.java
similarity index 66%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java
index 4dd5d551c..07acd793e 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferDataDetails.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/search/domain/ExternalAssetOwnerSearchRequest.java
@@ -16,19 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.data;
+package org.apache.fineract.investor.service.search.domain;
 
-import java.math.BigDecimal;
+import java.time.LocalDate;
 import lombok.Data;
 
 @Data
-public class ExternalTransferDataDetails {
+public class ExternalAssetOwnerSearchRequest {
 
-    private Long detailsId;
-    private BigDecimal totalOutstanding;
-    private BigDecimal totalPrincipalOutstanding;
-    private BigDecimal totalInterestOutstanding;
-    private BigDecimal totalFeeChargesOutstanding;
-    private BigDecimal totalPenaltyChargesOutstanding;
-    private BigDecimal totalOverpaid;
+    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 000000000..1a171b4ef
--- /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 ea7831136..2edba122e 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 class ExternalAssetOwnerHelper extends 
IntegrationTest {
         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<GetFinancialActivityAccountsResponse> 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 778575c21..3a52a10ab 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 final class Utils {
         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 000000000..132f28cb7
--- /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<HashMap> 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<HashMap> collaterals, Integer 
collateralId, BigDecimal quantity) {
+        collaterals.add(collaterals(collateralId, quantity));
+    }
+
+    protected HashMap<String, String> collaterals(Integer collateralId, 
BigDecimal quantity) {
+        HashMap<String, String> 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<ExternalTransferData> 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 000000000..13a0c2a40
--- /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());
+    }
+
+}

Reply via email to