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 045f0ec12 [FINERACT-1926] POST API for asset externalization
045f0ec12 is described below

commit 045f0ec121a0e5a5ed6a4335f50f6286ccc4a36b
Author: taskain7 <[email protected]>
AuthorDate: Tue May 23 07:00:58 2023 +0200

    [FINERACT-1926] POST API for asset externalization
---
 .../commands/service/CommandWrapperBuilder.java    |  16 ++
 ...PortfolioCommandSourceWritePlatformService.java |   0
 .../infrastructure/cache/domain/CacheType.java     |   0
 .../domain/ConfigurationDomainService.java         |   0
 .../core/data/CommandProcessingResultBuilder.java  |   0
 .../core/data/LoanIdAndExternalIdData.java         |  14 +-
 .../exception/UnrecognizedQueryParamException.java |   0
 .../core/service/CommandParameterUtil.java         |   0
 .../core/service/ExternalIdFactory.java            |   0
 .../service/LoanReadPlatformServiceCommon.java     |  11 +-
 .../api/ExternalAssetOwnersApiResource.java        |  46 +++-
 .../api/ExternalAssetOwnersApiResourceSwagger.java |  69 ++++-
 ...rData.java => ExternalTransferChangedData.java} |  14 +-
 .../investor/data/ExternalTransferData.java        |   3 +-
 ...java => ExternalTransferRequestParameters.java} |  22 +-
 .../ExternalTransferResponseData.java}             |  16 +-
 .../investor/domain/ExternalAssetOwner.java        |   3 +-
 .../domain/ExternalAssetOwnerRepository.java       |   4 +
 .../domain/ExternalAssetOwnerTransfer.java         |   8 +-
 .../ExternalAssetOwnerTransferRepository.java      |  13 +-
 .../investor}/domain/ExternalIdConverter.java      |   6 +-
 ...ternalAssetOwnerInitiateTransferException.java} |  11 +-
 ...lAssetOwnerInitiateTransferExceptionMapper.java |  45 ++++
 .../BuybackLoanFromExternalAssetOwnerHandler.java  |  24 +-
 .../ExternalAssetOwnersReadServiceImpl.java        |  13 +-
 .../service/ExternalAssetOwnersWriteService.java   |  14 +-
 .../ExternalAssetOwnersWriteServiceImpl.java       | 215 +++++++++++++++
 .../SaleLoanToExternalAssetOwnerHandler.java       |  24 +-
 .../module/investor/module-changelog-master.xml    |   3 +
 .../module/investor/parts/0003_asset_schemas.xml   |  20 ++
 .../0004_change_purchase_price_ratio_type.xml}     |  17 +-
 .../parts/0005_add_sale_and_buyback_command.xml    |  41 +++
 .../fineract/cob/data/LoanIdAndExternalId.java     |  11 +-
 .../core/auditing/CustomAuditingHandler.java       |   7 +-
 .../core/domain/ExternalIdConverter.java           |   3 +-
 .../loanaccount/domain/LoanRepository.java         |  11 +
 .../loanaccount/domain/LoanRepositoryWrapper.java  |   4 +
 .../service/LoanReadPlatformServiceImpl.java       |  14 +-
 .../ExternalAssetOwnerHelper.java                  |  53 ++++
 .../InitiateExternalAssetOwnerTransferTest.java    | 300 +++++++++++++++++++++
 40 files changed, 967 insertions(+), 108 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 1ffc5265e..ca182beff 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3622,4 +3622,20 @@ public class CommandWrapperBuilder {
         this.href = "/loans/" + loanId + 
"/transactions?command=undo-charge-off";
         return this;
     }
+
+    public CommandWrapperBuilder saleLoanToExternalAssetOwner(final Long 
loanId) {
+        this.actionName = "SALE";
+        this.entityName = "LOAN";
+        this.loanId = loanId;
+        this.href = "/external-asset-owners/transfers/loans/" + loanId;
+        return this;
+    }
+
+    public CommandWrapperBuilder buybackLoanToExternalAssetOwner(final Long 
loanId) {
+        this.actionName = "BUYBACK";
+        this.entityName = "LOAN";
+        this.loanId = loanId;
+        this.href = "/external-asset-owners/transfers/loans/" + loanId;
+        return this;
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
similarity index 100%
copy from 
fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
copy to 
fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/domain/CacheType.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResultBuilder.java
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/LoanIdAndExternalIdData.java
similarity index 71%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
copy to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/LoanIdAndExternalIdData.java
index eee4e5d8e..f09191a3a 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/LoanIdAndExternalIdData.java
@@ -16,12 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.domain;
+package org.apache.fineract.infrastructure.core.data;
 
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 
-public interface ExternalAssetOwnerRepository
-        extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
+@Data
+@AllArgsConstructor
+public class LoanIdAndExternalIdData {
 
+    private Long loanId;
+    private ExternalId loanExternalId;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/UnrecognizedQueryParamException.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/CommandParameterUtil.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/ExternalIdFactory.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ExternalIdFactory.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/ExternalIdFactory.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ExternalIdFactory.java
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceCommon.java
similarity index 71%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceCommon.java
index eee4e5d8e..871be9ab2 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceCommon.java
@@ -16,12 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.domain;
+package org.apache.fineract.portfolio.loanaccount.service;
 
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.apache.fineract.infrastructure.core.data.LoanIdAndExternalIdData;
 
-public interface ExternalAssetOwnerRepository
-        extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
+public interface LoanReadPlatformServiceCommon {
 
+    LoanIdAndExternalIdData getTransferableLoanIdAndExternalId(Long loanId);
+
+    Long getLoanIdByLoanExternalId(String externalId);
 }
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 6c9f55bac..c918e7dee 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
@@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -37,13 +38,20 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
 import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang3.NotImplementedException;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
 import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
 import 
org.apache.fineract.infrastructure.security.service.PlatformUserRightsContext;
 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.portfolio.loanaccount.service.LoanReadPlatformServiceCommon;
 import org.springframework.stereotype.Component;
 
 @Path("/v1/external-asset-owners")
@@ -56,29 +64,40 @@ public class ExternalAssetOwnersApiResource {
     private final ExternalAssetOwnersReadService 
externalAssetOwnersReadService;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
     private final DefaultToApiJsonSerializer<ExternalTransferData> 
apiJsonSerializerService;
+    private final DefaultToApiJsonSerializer<ExternalTransferResponseData> 
postApiJsonSerializerService;
+    private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
+    private final LoanReadPlatformServiceCommon loanReadPlatformService;
 
     @POST
     @Path("/transfers/loans/{loanId}")
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferRequest.class)))
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferResponse.class))),
+            @ApiResponse(responseCode = "403", description = "Transfer cannot 
be initiated") })
     public String transferRequestWithLoanId(@PathParam("loanId") final Long 
loanId,
             @QueryParam("command") @Parameter(description = "command") final 
String commandParam,
             @Parameter(hidden = true) final String apiRequestBodyAsJson) {
         platformUserRightsContext.isAuthenticated();
-
-        throw new NotImplementedException("Not implemented yet");
+        return getResult(loanId, apiRequestBodyAsJson, commandParam);
     }
 
     @POST
     @Path("/transfers/loans/external-id/{loanExternalId}")
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
-    public String 
transferRequestWithLoanExternalId(@PathParam("loanExternalId") final Long 
loanId,
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferRequest.class)))
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferResponse.class))),
+            @ApiResponse(responseCode = "403", description = "Transfer cannot 
be initiated") })
+    public String 
transferRequestWithLoanExternalId(@PathParam("loanExternalId") final String 
externalLoanId,
             @QueryParam("command") @Parameter(description = "command") final 
String commandParam,
             @Parameter(hidden = true) final String apiRequestBodyAsJson) {
         platformUserRightsContext.isAuthenticated();
+        Long loanId = 
loanReadPlatformService.getLoanIdByLoanExternalId(externalLoanId);
 
-        throw new NotImplementedException("Not implemented yet");
+        return getResult(loanId, apiRequestBodyAsJson, commandParam);
     }
 
     @GET
@@ -99,4 +118,21 @@ public class ExternalAssetOwnersApiResource {
         ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         return apiJsonSerializerService.serialize(settings, transferDataList);
     }
+
+    private String getResult(Long loanId, String apiRequestBodyAsJson, String 
commandParam) {
+        final CommandWrapperBuilder builder = new 
CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+        CommandWrapper commandRequest = null;
+        if (CommandParameterUtil.is(commandParam, "sale")) {
+            commandRequest = 
builder.saleLoanToExternalAssetOwner(loanId).build();
+        } else if (CommandParameterUtil.is(commandParam, "buyback")) {
+            commandRequest = 
builder.buybackLoanToExternalAssetOwner(loanId).build();
+        }
+
+        if (commandRequest == null) {
+            throw new UnrecognizedQueryParamException("command", commandParam);
+        }
+        CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return postApiJsonSerializerService.serialize(result);
+    }
 }
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 43976241e..aba5bd722 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
@@ -19,7 +19,6 @@
 package org.apache.fineract.investor.api;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.Set;
 import org.apache.fineract.investor.data.ExternalTransferStatus;
@@ -68,7 +67,7 @@ final class ExternalAssetOwnersApiResourceSwagger {
             public String transferExternalId;
 
             @Schema(example = "1")
-            public BigDecimal purchasePriceRatio;
+            public String purchasePriceRatio;
 
             @Schema(example = "[2023, 5, 23]")
             public LocalDate settlementDate;
@@ -87,4 +86,70 @@ final class ExternalAssetOwnersApiResourceSwagger {
         public Integer totalFilteredRecords;
         public Set<GetExternalTransferPageItems> pageItems;
     }
+
+    @Schema(description = "PostInitiateTransferRequest")
+    public static final class PostInitiateTransferRequest {
+
+        private PostInitiateTransferRequest() {}
+
+        @Schema(example = "[2023, 5, 23]")
+        public LocalDate settlementDate;
+
+        @Schema(example = "1234567890987654321abc")
+        public String ownerExternalId;
+
+        @Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e")
+        public String transferExternalId;
+
+        @Schema(example = "1.2345678")
+        public String purchasePriceRatio;
+
+        @Schema(example = "yyyy-MM-dd")
+        public String dateformat;
+
+        @Schema(example = "en")
+        public String locale;
+    }
+
+    @Schema(description = "PostInitiateTransferResponse")
+    public static final class PostInitiateTransferResponse {
+
+        private PostInitiateTransferResponse() {}
+
+        @Schema(example = "1", description = "transfer ID")
+        public Long resourceId;
+
+        @Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e", description 
= "transfer external ID")
+        public String resourceExternalId;
+
+        @Schema(example = "2", description = "loan ID")
+        public Long subResourceId;
+
+        @Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c2e", description 
= "loan external ID")
+        public String subResourceExternalId;
+
+        public ExternalAssetOwnerTransferChangesData changes;
+
+        @Schema(example = "yyyy-MM-dd")
+        public String dateformat;
+
+        @Schema(example = "en")
+        public String locale;
+
+        @Schema(description = "ExternalAssetOwnerTransferChangesData")
+        static final class ExternalAssetOwnerTransferChangesData {
+
+            @Schema(example = "[2023, 5, 23]")
+            public String settlementDate;
+
+            @Schema(example = "1234567890987654321abc")
+            public String ownerExternalId;
+
+            @Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e")
+            public String transferExternalId;
+
+            @Schema(example = "1.23456789")
+            public String purchasePriceRatio;
+        }
+    }
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferChangedData.java
similarity index 73%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferChangedData.java
index 23f2bb861..7df77203b 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferChangedData.java
@@ -18,20 +18,14 @@
  */
 package org.apache.fineract.investor.data;
 
-import java.math.BigDecimal;
 import java.time.LocalDate;
 import lombok.Data;
 
 @Data
-public class ExternalTransferData {
+public class ExternalTransferChangedData {
 
-    private Long transferId;
-    private ExternalTransferOwnerData owner;
-    private ExternalTransferLoanMappingData loan;
-    private String transferExternalId;
-    private BigDecimal purchasePriceRatio;
     private LocalDate settlementDate;
-    private ExternalTransferStatus status;
-    private LocalDate effectiveFrom;
-    private LocalDate effectiveTo;
+    private String transferExternalId;
+    private String ownerExternalId;
+    private String purchasePriceRatio;
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
index 23f2bb861..ca1d1d878 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
@@ -18,7 +18,6 @@
  */
 package org.apache.fineract.investor.data;
 
-import java.math.BigDecimal;
 import java.time.LocalDate;
 import lombok.Data;
 
@@ -29,7 +28,7 @@ public class ExternalTransferData {
     private ExternalTransferOwnerData owner;
     private ExternalTransferLoanMappingData loan;
     private String transferExternalId;
-    private BigDecimal purchasePriceRatio;
+    private String purchasePriceRatio;
     private LocalDate settlementDate;
     private ExternalTransferStatus status;
     private LocalDate effectiveFrom;
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java
similarity index 62%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java
index 23f2bb861..127b58727 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferData.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java
@@ -18,20 +18,14 @@
  */
 package org.apache.fineract.investor.data;
 
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import lombok.Data;
+public final class ExternalTransferRequestParameters {
 
-@Data
-public class ExternalTransferData {
+    private ExternalTransferRequestParameters() {}
 
-    private Long transferId;
-    private ExternalTransferOwnerData owner;
-    private ExternalTransferLoanMappingData loan;
-    private String transferExternalId;
-    private BigDecimal purchasePriceRatio;
-    private LocalDate settlementDate;
-    private ExternalTransferStatus status;
-    private LocalDate effectiveFrom;
-    private LocalDate effectiveTo;
+    public static final String SETTLEMENT_DATE = "settlement_date";
+    public static final String OWNER_EXTERNAL_ID = "owner_external_id";
+    public static final String TRANSFER_EXTERNAL_ID = "transfer_external_id";
+    public static final String PURCHASE_PRICE_RATIO = "purchase_price_ratio";
+    public static final String DATEFORMAT = "dateformat";
+    public static final String LOCALE = "locale";
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferResponseData.java
similarity index 69%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferResponseData.java
index eee4e5d8e..b6cc3bbd5 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferResponseData.java
@@ -16,12 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.domain;
+package org.apache.fineract.investor.data;
 
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import lombok.Data;
 
-public interface ExternalAssetOwnerRepository
-        extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
+@Data
+public class ExternalTransferResponseData {
 
+    private Long resourceId;
+    private String resourceExternalId;
+    private Long subResourceId;
+    private String subResourceExternalId;
+    private ExternalTransferChangedData changes;
+    private String dateformat;
+    private String locale;
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwner.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwner.java
index fd7c4d3de..4f2422c78 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwner.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwner.java
@@ -21,7 +21,6 @@ package org.apache.fineract.investor.domain;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
-import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
@@ -30,7 +29,7 @@ import 
org.apache.fineract.infrastructure.core.domain.ExternalId;
 @Getter
 @Setter
 @Entity
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor
 @Table(name = "m_external_asset_owner")
 public class ExternalAssetOwner extends AbstractAuditableWithUTCDateTimeCustom 
{
 
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
index eee4e5d8e..90e554247 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
@@ -18,10 +18,14 @@
  */
 package org.apache.fineract.investor.domain;
 
+import java.util.Optional;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public interface ExternalAssetOwnerRepository
         extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
 
+    Optional<ExternalAssetOwner> findByExternalId(ExternalId externalId);
+
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransfer.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransfer.java
index e292ba6c0..468c3dc22 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransfer.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransfer.java
@@ -18,14 +18,12 @@
  */
 package org.apache.fineract.investor.domain;
 
-import java.math.BigDecimal;
 import java.time.LocalDate;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
@@ -34,7 +32,7 @@ import 
org.apache.fineract.infrastructure.core.domain.ExternalId;
 @Getter
 @Setter
 @Table(name = "m_external_asset_owner_transfer")
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor
 @Entity
 public class ExternalAssetOwnerTransfer extends 
AbstractAuditableWithUTCDateTimeCustom {
 
@@ -51,8 +49,8 @@ public class ExternalAssetOwnerTransfer extends 
AbstractAuditableWithUTCDateTime
     @Column(name = "status", length = 50)
     private String status;
 
-    @Column(name = "purchase_price_ratio", precision = 19, scale = 6)
-    private BigDecimal purchasePriceRatio;
+    @Column(name = "purchase_price_ratio", length = 50)
+    private String purchasePriceRatio;
 
     @Column(name = "settlement_date")
     private LocalDate settlementDate;
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransferRepository.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransferRepository.java
index 556c33fe6..799143d0a 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransferRepository.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerTransferRepository.java
@@ -19,6 +19,8 @@
 package org.apache.fineract.investor.domain;
 
 import java.util.List;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Query;
@@ -27,7 +29,12 @@ import org.springframework.data.repository.query.Param;
 public interface ExternalAssetOwnerTransferRepository
         extends JpaRepository<ExternalAssetOwnerTransfer, Long>, 
JpaSpecificationExecutor<ExternalAssetOwnerTransfer> {
 
-    @Query("select e from ExternalAssetOwnerTransfer e where (:loanId is null 
or e.loanId = :loanId) and (:loanExternalId is null or e.externalLoanId = 
:loanExternalId) and (:transferExternalId is null or e.externalId = 
:transferExternalId)")
-    List<ExternalAssetOwnerTransfer> findAllByIncomingId(@Param("loanId") Long 
loanId, @Param("loanExternalId") String loanExternalId,
-            @Param("transferExternalId") String transferExternalId);
+    List<ExternalAssetOwnerTransfer> findAllByLoanId(Long loanId);
+
+    List<ExternalAssetOwnerTransfer> findAllByExternalLoanId(ExternalId 
externalLoanId);
+
+    List<ExternalAssetOwnerTransfer> findAllByExternalId(ExternalId 
externalId);
+
+    @Query("select e from ExternalAssetOwnerTransfer e where e.loanId = 
:loanId and e.id = (select max(ex.id) from ExternalAssetOwnerTransfer ex where 
ex.loanId = :loanId)")
+    Optional<ExternalAssetOwnerTransfer> findLatestByLoanId(@Param("loanId") 
Long loanId);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalIdConverter.java
similarity index 85%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalIdConverter.java
index e67504e77..b51da860a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalIdConverter.java
@@ -16,11 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.core.domain;
+package org.apache.fineract.investor.domain;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 
 @Converter(autoApply = true)
 public class ExternalIdConverter implements AttributeConverter<ExternalId, 
String> {
@@ -32,7 +34,7 @@ public class ExternalIdConverter implements 
AttributeConverter<ExternalId, Strin
 
     @Override
     public ExternalId convertToEntityAttribute(String externalId) {
-        return StringUtils.isBlank(externalId) ? ExternalId.empty() : new 
ExternalId(externalId);
+        return StringUtils.isBlank(externalId) ? ExternalId.empty() : 
ExternalIdFactory.produce(externalId);
     }
 
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerInitiateTransferException.java
similarity index 70%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerInitiateTransferException.java
index eee4e5d8e..6fc4bcd8a 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerInitiateTransferException.java
@@ -16,12 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.domain;
+package org.apache.fineract.investor.exception;
 
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-
-public interface ExternalAssetOwnerRepository
-        extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
+public class ExternalAssetOwnerInitiateTransferException extends 
RuntimeException {
 
+    public ExternalAssetOwnerInitiateTransferException(String message) {
+        super(message);
+    }
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/exceptionmapper/ExternalAssetOwnerInitiateTransferExceptionMapper.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/exceptionmapper/ExternalAssetOwnerInitiateTransferExceptionMapper.java
new file mode 100644
index 000000000..bdae122d0
--- /dev/null
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/exceptionmapper/ExternalAssetOwnerInitiateTransferExceptionMapper.java
@@ -0,0 +1,45 @@
+/**
+ * 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.exception.exceptionmapper;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import 
org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
+import org.springframework.stereotype.Component;
+
+@Provider
+@Component
+@Slf4j
+public class ExternalAssetOwnerInitiateTransferExceptionMapper implements 
ExceptionMapper<ExternalAssetOwnerInitiateTransferException> {
+
+    @Override
+    public Response toResponse(ExternalAssetOwnerInitiateTransferException 
exception) {
+        final String globalisationMessageCode = 
"error.msg.external.asset.owner.initiate";
+        final String defaultUserMessage = exception.getMessage();
+        log.warn("Exception: {}, Message: {}", exception.getClass().getName(), 
defaultUserMessage);
+
+        final ApiParameterError error = 
ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage);
+
+        return 
Response.status(Response.Status.FORBIDDEN).entity(error).type(MediaType.APPLICATION_JSON).build();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/BuybackLoanFromExternalAssetOwnerHandler.java
similarity index 54%
copy from 
fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/service/BuybackLoanFromExternalAssetOwnerHandler.java
index 7a897c547..9fc11ca38 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/BuybackLoanFromExternalAssetOwnerHandler.java
@@ -16,18 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.commands.service;
+package org.apache.fineract.investor.service;
 
-import org.apache.fineract.commands.domain.CommandWrapper;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.stereotype.Service;
 
-public interface PortfolioCommandSourceWritePlatformService {
+@RequiredArgsConstructor
+@Service
+@CommandType(entity = "LOAN", action = "BUYBACK")
+public class BuybackLoanFromExternalAssetOwnerHandler implements 
NewCommandSourceHandler {
 
-    CommandProcessingResult logCommandSource(CommandWrapper commandRequest);
+    private final ExternalAssetOwnersWriteService 
externalAssetOwnersWriteService;
 
-    CommandProcessingResult approveEntry(Long id);
-
-    Long rejectEntry(Long id);
-
-    Long deleteEntry(Long makerCheckerId);
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return externalAssetOwnersWriteService.buyBackLoanByLoanId(command);
+    }
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadServiceImpl.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadServiceImpl.java
index b5b61db38..392fa9b24 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadServiceImpl.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadServiceImpl.java
@@ -18,8 +18,10 @@
  */
 package org.apache.fineract.investor.service;
 
+import java.util.ArrayList;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import org.apache.fineract.investor.data.ExternalTransferData;
 import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
 import 
org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
@@ -33,12 +35,17 @@ public class ExternalAssetOwnersReadServiceImpl implements 
ExternalAssetOwnersRe
 
     private final ExternalAssetOwnerTransferRepository 
externalAssetOwnerTransferRepository;
     private final ExternalAssetOwnersTransferMapper mapper;
-    private final ExternalAssetOwnersTransferMapper 
externalAssetOwnersTransferMapper;
 
     @Override
     public List<ExternalTransferData> retrieveTransferData(Long loanId, String 
externalLoanId, String transferExternalId) {
-        List<ExternalAssetOwnerTransfer> result = 
externalAssetOwnerTransferRepository.findAllByIncomingId(loanId, externalLoanId,
-                transferExternalId);
+        List<ExternalAssetOwnerTransfer> result = new ArrayList<>();
+        if (loanId != null) {
+            
result.addAll(externalAssetOwnerTransferRepository.findAllByLoanId(loanId));
+        } else if (externalLoanId != null) {
+            
result.addAll(externalAssetOwnerTransferRepository.findAllByExternalLoanId(ExternalIdFactory.produce(externalLoanId)));
+        } else if (transferExternalId != null) {
+            
result.addAll(externalAssetOwnerTransferRepository.findAllByExternalId(ExternalIdFactory.produce(transferExternalId)));
+        }
         return result.stream().map(mapper::mapTransfer).toList();
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
similarity index 70%
copy from 
fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
index 7a897c547..b3133875d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
@@ -16,18 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.commands.service;
+package org.apache.fineract.investor.service;
 
-import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 
-public interface PortfolioCommandSourceWritePlatformService {
+public interface ExternalAssetOwnersWriteService {
 
-    CommandProcessingResult logCommandSource(CommandWrapper commandRequest);
+    CommandProcessingResult saleLoanByLoanId(JsonCommand command);
 
-    CommandProcessingResult approveEntry(Long id);
-
-    Long rejectEntry(Long id);
-
-    Long deleteEntry(Long makerCheckerId);
+    CommandProcessingResult buyBackLoanByLoanId(JsonCommand command);
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
new file mode 100644
index 000000000..50876c654
--- /dev/null
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
@@ -0,0 +1,215 @@
+/**
+ * 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;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.data.LoanIdAndExternalIdData;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.investor.data.ExternalTransferRequestParameters;
+import org.apache.fineract.investor.data.ExternalTransferStatus;
+import org.apache.fineract.investor.domain.ExternalAssetOwner;
+import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
+import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
+import 
org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
+import 
org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceCommon;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class ExternalAssetOwnersWriteServiceImpl implements 
ExternalAssetOwnersWriteService {
+
+    private final ExternalAssetOwnerTransferRepository 
externalAssetOwnerTransferRepository;
+    private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final LoanReadPlatformServiceCommon loanReadPlatformService;
+
+    @Override
+    @Transactional
+    public CommandProcessingResult saleLoanByLoanId(JsonCommand command) {
+        Long loanId = command.getLoanId();
+        LoanIdAndExternalIdData loanIdAndExternalId = 
loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId);
+        ExternalAssetOwnerTransfer externalAssetOwnerTransfer = 
parseJson(loanId, command.json(), loanIdAndExternalId.getLoanExternalId(),
+                ExternalTransferStatus.PENDING);
+        validateSale(externalAssetOwnerTransfer, loanIdAndExternalId);
+        ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = 
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
+        return buildResponseData(savedExternalAssetOwnerTransfer);
+    }
+
+    @Override
+    @Transactional
+    public CommandProcessingResult buyBackLoanByLoanId(JsonCommand command) {
+        Long loanId = command.getLoanId();
+        LoanIdAndExternalIdData loanIdAndExternalId = 
loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId);
+        ExternalAssetOwnerTransfer externalAssetOwnerTransfer = 
parseJson(command.getLoanId(), command.json(),
+                loanIdAndExternalId.getLoanExternalId(), 
ExternalTransferStatus.BUYBACK);
+        validateBuyBack(externalAssetOwnerTransfer);
+        ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = 
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
+        return buildResponseData(savedExternalAssetOwnerTransfer);
+    }
+
+    private CommandProcessingResult 
buildResponseData(ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer) {
+        Map<String, Object> changes = new HashMap<>();
+        changes.put(ExternalTransferRequestParameters.SETTLEMENT_DATE, 
savedExternalAssetOwnerTransfer.getSettlementDate());
+        changes.put(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
+                
savedExternalAssetOwnerTransfer.getOwner().getExternalId().getValue());
+        changes.put(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID,
+                
savedExternalAssetOwnerTransfer.getOwner().getExternalId().getValue());
+        changes.put(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, 
savedExternalAssetOwnerTransfer.getPurchasePriceRatio());
+        return new 
CommandProcessingResultBuilder().withEntityId(savedExternalAssetOwnerTransfer.getId())
+                
.withEntityExternalId(savedExternalAssetOwnerTransfer.getExternalId())
+                .withSubEntityId(savedExternalAssetOwnerTransfer.getLoanId())
+                
.withSubEntityExternalId(Objects.isNull(savedExternalAssetOwnerTransfer.getExternalLoanId())
 ? null
+                        : savedExternalAssetOwnerTransfer.getExternalLoanId())
+                .with(changes).build();
+    }
+
+    private void validateSale(ExternalAssetOwnerTransfer 
externalAssetOwnerTransfer, LoanIdAndExternalIdData loanIdAndExternalId) {
+        validateSettlementDate(externalAssetOwnerTransfer);
+        validateTransferStatusForSale(externalAssetOwnerTransfer);
+        validateLoanStatus(loanIdAndExternalId);
+    }
+
+    private void validateBuyBack(ExternalAssetOwnerTransfer 
externalAssetOwnerTransfer) {
+        validateSettlementDate(externalAssetOwnerTransfer);
+        validateTransferStatusForBuyBack(externalAssetOwnerTransfer);
+    }
+
+    private void validateSettlementDate(ExternalAssetOwnerTransfer 
externalAssetOwnerTransfer) {
+        if 
(externalAssetOwnerTransfer.getSettlementDate().isBefore(ThreadLocalContextUtil.getBusinessDate()))
 {
+            throw new ExternalAssetOwnerInitiateTransferException("Settlement 
date cannot be in the past");
+        }
+    }
+
+    private void validateLoanStatus(LoanIdAndExternalIdData 
loanIdAndExternalIdAndExternalId) {
+        if (Objects.isNull(loanIdAndExternalIdAndExternalId)) {
+            throw new ExternalAssetOwnerInitiateTransferException("Loan is not 
in active status");
+        }
+    }
+
+    private void validateTransferStatusForSale(ExternalAssetOwnerTransfer 
externalAssetOwnerTransfer) {
+        Optional<ExternalAssetOwnerTransfer> latestTransferOptional = 
externalAssetOwnerTransferRepository
+                .findLatestByLoanId(externalAssetOwnerTransfer.getLoanId());
+        if (latestTransferOptional.isPresent()) {
+            ExternalAssetOwnerTransfer latestTransfer = 
latestTransferOptional.get();
+            ExternalTransferStatus latestTransferStatus = 
ExternalTransferStatus.valueOf(latestTransfer.getStatus());
+            if (latestTransferStatus.equals(ExternalTransferStatus.PENDING)) {
+                throw new ExternalAssetOwnerInitiateTransferException(
+                        "External asset owner transfer is already in PENDING 
state for this loan.");
+            } else if 
(latestTransferStatus.equals(ExternalTransferStatus.ACTIVE)) {
+                throw new ExternalAssetOwnerInitiateTransferException(
+                        "This loan cannot be sold, because it is owned by an 
external asset owner.");
+            }
+        }
+    }
+
+    private void validateTransferStatusForBuyBack(ExternalAssetOwnerTransfer 
externalAssetOwnerTransfer) {
+        Optional<ExternalAssetOwnerTransfer> latestTransferOptional = 
externalAssetOwnerTransferRepository
+                .findLatestByLoanId(externalAssetOwnerTransfer.getLoanId());
+        if (latestTransferOptional.isEmpty()) {
+            throw new ExternalAssetOwnerInitiateTransferException(
+                    "This loan cannot be bought back, because it is not owned 
by an external asset owner");
+        } else {
+            ExternalAssetOwnerTransfer latestTransfer = 
latestTransferOptional.get();
+            ExternalTransferStatus latestTransferStatus = 
ExternalTransferStatus.valueOf(latestTransfer.getStatus());
+            if (latestTransferStatus.equals(ExternalTransferStatus.BUYBACK)) {
+                throw new ExternalAssetOwnerInitiateTransferException(
+                        "External asset owner transfer is already in BUYBACK 
state for this loan.");
+            }
+        }
+    }
+
+    private ExternalAssetOwnerTransfer parseJson(Long loanId, String 
apiRequestBodyAsJson, ExternalId externalLoanId,
+            ExternalTransferStatus status) {
+        ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new 
ExternalAssetOwnerTransfer();
+
+        validateRequestBody(apiRequestBodyAsJson);
+        final JsonElement json = fromApiJsonHelper.parse(apiRequestBodyAsJson);
+
+        ExternalAssetOwner owner = getOwner(json);
+        externalAssetOwnerTransfer.setOwnerId(owner.getId());
+        externalAssetOwnerTransfer.setOwner(owner);
+        
externalAssetOwnerTransfer.setExternalId(getTransferExternalIdFromJson(json));
+        externalAssetOwnerTransfer.setStatus(status.name());
+        
externalAssetOwnerTransfer.setPurchasePriceRatio(getPurchasePriceRatioFromJson(json));
+        
externalAssetOwnerTransfer.setSettlementDate(getSettlementDateFromJson(json));
+        
externalAssetOwnerTransfer.setEffectiveDateFrom(ThreadLocalContextUtil.getBusinessDate());
+        externalAssetOwnerTransfer.setEffectiveDateTo(LocalDate.of(9999, 12, 
31));
+        externalAssetOwnerTransfer.setLoanId(loanId);
+        externalAssetOwnerTransfer.setExternalLoanId(externalLoanId);
+        return externalAssetOwnerTransfer;
+    }
+
+    private void validateRequestBody(String apiRequestBodyAsJson) {
+        final Set<String> requestParameters = new HashSet<>(
+                
Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE, 
ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
+                        
ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, 
ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
+                        ExternalTransferRequestParameters.DATEFORMAT, 
ExternalTransferRequestParameters.LOCALE));
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, 
apiRequestBodyAsJson, requestParameters);
+    }
+
+    private LocalDate getSettlementDateFromJson(JsonElement json) {
+        String dateFormat = 
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.DATEFORMAT,
 json);
+        String locale = 
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.LOCALE, 
json);
+        return 
fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE,
 json, dateFormat,
+                JsonParserHelper.localeFromString(locale));
+    }
+
+    private ExternalId getTransferExternalIdFromJson(JsonElement json) {
+        String transferExternalId = 
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID,
 json);
+        return ExternalIdFactory.produce(transferExternalId);
+    }
+
+    private String getPurchasePriceRatioFromJson(JsonElement json) {
+        return 
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
 json);
+    }
+
+    private ExternalAssetOwner getOwner(JsonElement json) {
+        String ownerExternalId = 
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
 json);
+        Optional<ExternalAssetOwner> byExternalId = 
externalAssetOwnerRepository
+                .findByExternalId(ExternalIdFactory.produce(ownerExternalId));
+        return byExternalId.orElseGet(() -> 
createAndGetAssetOwner(ownerExternalId));
+    }
+
+    private ExternalAssetOwner createAndGetAssetOwner(String externalId) {
+        ExternalAssetOwner externalAssetOwner = new ExternalAssetOwner();
+        
externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId));
+        return externalAssetOwnerRepository.save(externalAssetOwner);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/SaleLoanToExternalAssetOwnerHandler.java
similarity index 54%
rename from 
fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
rename to 
fineract-investor/src/main/java/org/apache/fineract/investor/service/SaleLoanToExternalAssetOwnerHandler.java
index 7a897c547..39c56a1ef 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/SaleLoanToExternalAssetOwnerHandler.java
@@ -16,18 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.commands.service;
+package org.apache.fineract.investor.service;
 
-import org.apache.fineract.commands.domain.CommandWrapper;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.stereotype.Service;
 
-public interface PortfolioCommandSourceWritePlatformService {
+@RequiredArgsConstructor
+@Service
+@CommandType(entity = "LOAN", action = "SALE")
+public class SaleLoanToExternalAssetOwnerHandler implements 
NewCommandSourceHandler {
 
-    CommandProcessingResult logCommandSource(CommandWrapper commandRequest);
+    private final ExternalAssetOwnersWriteService 
externalAssetOwnersWriteService;
 
-    CommandProcessingResult approveEntry(Long id);
-
-    Long rejectEntry(Long id);
-
-    Long deleteEntry(Long makerCheckerId);
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return externalAssetOwnersWriteService.saleLoanByLoanId(command);
+    }
 }
diff --git 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
index 7f959be36..c7fa915cc 100644
--- 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
+++ 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
@@ -24,4 +24,7 @@
   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
   <include relativeToChangelogFile="true" 
file="parts/0001_initial_schema.xml"/>
   <include relativeToChangelogFile="true" file="parts/0002_asset_schemas.xml"/>
+  <include relativeToChangelogFile="true" file="parts/0003_asset_schemas.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/0004_change_purchase_price_ratio_type.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/0005_add_sale_and_buyback_command.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0003_asset_schemas.xml
 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0003_asset_schemas.xml
index bd12acd21..6730c2e06 100644
--- 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0003_asset_schemas.xml
+++ 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0003_asset_schemas.xml
@@ -44,4 +44,24 @@
 
   </changeSet>
 
+  <changeSet author="fineract" id="3" context="mysql">
+
+    <addColumn tableName="m_external_asset_owner_transfer">
+      <column name="external_loan_id" type="varchar(100)"/>
+      <column name="loan_id" type="bigint" remarks="Loan ID">
+        <constraints nullable="false"/>
+      </column>
+    </addColumn>
+  </changeSet>
+  <changeSet author="fineract" id="4" context="mysql">
+
+    <addForeignKeyConstraint baseColumnNames="loan_id"
+                             baseTableName="m_external_asset_owner_transfer"
+                             
constraintName="FK_external_asset_owner_transfer_loan_id" deferrable="false"
+                             initiallyDeferred="false"
+                             onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                             referencedTableName="m_loan" validate="true"/>
+
+  </changeSet>
+
 </databaseChangeLog>
diff --git 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0004_change_purchase_price_ratio_type.xml
similarity index 56%
copy from 
fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
copy to 
fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0004_change_purchase_price_ratio_type.xml
index 7f959be36..f5caa3fe6 100644
--- 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
+++ 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0004_change_purchase_price_ratio_type.xml
@@ -20,8 +20,17 @@
 
 -->
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-  <include relativeToChangelogFile="true" 
file="parts/0001_initial_schema.xml"/>
-  <include relativeToChangelogFile="true" file="parts/0002_asset_schemas.xml"/>
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+
+    <changeSet author="fineract" id="1" context="postgresql">
+        <modifyDataType columnName="purchase_price_ratio"
+                        newDataType="VARCHAR(50)"
+                        tableName="m_external_asset_owner_transfer"/>
+    </changeSet>
+    <changeSet author="fineract" id="2" context="mysql">
+        <modifyDataType columnName="purchase_price_ratio"
+                        newDataType="VARCHAR(50)"
+                        tableName="m_external_asset_owner_transfer"/>
+    </changeSet>
 </databaseChangeLog>
diff --git 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0005_add_sale_and_buyback_command.xml
 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0005_add_sale_and_buyback_command.xml
new file mode 100644
index 000000000..802fabc66
--- /dev/null
+++ 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0005_add_sale_and_buyback_command.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet id="1" author="fineract">
+        <insert tableName="m_permission">
+            <column name="grouping" value="transaction_loan"/>
+            <column name="code" value="SALE_LOAN"/>
+            <column name="entity_name" value="LOAN"/>
+            <column name="action_name" value="SALE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+        <insert tableName="m_permission">
+            <column name="grouping" value="transaction_loan"/>
+            <column name="code" value="BUYBACK_LOAN"/>
+            <column name="entity_name" value="LOAN"/>
+            <column name="action_name" value="BUYBACK"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndExternalId.java
similarity index 71%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndExternalId.java
index eee4e5d8e..40a0584f2 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndExternalId.java
@@ -16,12 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.domain;
+package org.apache.fineract.cob.data;
 
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 
-public interface ExternalAssetOwnerRepository
-        extends JpaRepository<ExternalAssetOwner, Long>, 
JpaSpecificationExecutor<ExternalAssetOwner> {
+public interface LoanIdAndExternalId {
 
+    Long getId();
+
+    ExternalId getExternalId();
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java
index 74910ae8b..e1268e9a1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandler.java
@@ -60,7 +60,12 @@ public class CustomAuditingHandler extends AuditingHandler {
     }
 
     private DateTimeProvider fetchDateTimeProvider(Object bean) {
-        return bean instanceof AbstractAuditableWithUTCDateTimeCustom ? 
CustomDateTimeProvider.TENANT : CustomDateTimeProvider.INSTANCE;
+        if (bean instanceof AbstractAuditableWithUTCDateTimeCustom
+                || bean instanceof 
org.apache.fineract.investor.domain.AbstractAuditableWithUTCDateTimeCustom) {
+            return CustomDateTimeProvider.TENANT;
+        } else {
+            return CustomDateTimeProvider.INSTANCE;
+        }
     }
 
     /**
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
index e67504e77..f04af3057 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/ExternalIdConverter.java
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.domain;
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 
 @Converter(autoApply = true)
 public class ExternalIdConverter implements AttributeConverter<ExternalId, 
String> {
@@ -32,7 +33,7 @@ public class ExternalIdConverter implements 
AttributeConverter<ExternalId, Strin
 
     @Override
     public ExternalId convertToEntityAttribute(String externalId) {
-        return StringUtils.isBlank(externalId) ? ExternalId.empty() : new 
ExternalId(externalId);
+        return StringUtils.isBlank(externalId) ? ExternalId.empty() : 
ExternalIdFactory.produce(externalId);
     }
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 09f502a32..085cc59b7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -85,6 +85,11 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
 
     String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where 
loan.accountNumber = :accountNumber";
 
+    String GET_NON_CLOSED_LOAN_BY_LOAN_ID = "select loan from Loan loan where 
loan.id = :loanId and loan.loanStatus in (100,200,300,"
+            + "303,304)";
+
+    String EXISTS_NON_CLOSED_BY_EXTERNAL_LOAN_ID = "select case when (count 
(loan) > 0) then 'true' else 'false' end from Loan loan where loan.externalId = 
:externalLoanId and loan.loanStatus in (100,200,300,303,304)";
+
     String FIND_ID_BY_EXTERNAL_ID = "SELECT loan.id FROM Loan loan WHERE 
loan.externalId = :externalId";
 
     // should follow the logic of 
`FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE` query
@@ -191,6 +196,12 @@ public interface LoanRepository extends 
JpaRepository<Loan, Long>, JpaSpecificat
     @Query(FIND_BY_ACCOUNT_NUMBER)
     Loan findLoanAccountByAccountNumber(@Param("accountNumber") String 
accountNumber);
 
+    @Query(GET_NON_CLOSED_LOAN_BY_LOAN_ID)
+    Loan getNonClosedLoanIdAndExternalIdByLoanId(@Param("loanId") Long loanId);
+
+    @Query(EXISTS_NON_CLOSED_BY_EXTERNAL_LOAN_ID)
+    boolean existsNonClosedLoanByExternalLoanId(@Param("externalLoanId") 
ExternalId externalLoanId);
+
     boolean existsByExternalId(@Param("externalId") ExternalId externalId);
 
     @Query(FIND_ALL_NON_CLOSED)
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
index 497335802..f861c3869 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java
@@ -262,4 +262,8 @@ public class LoanRepositoryWrapper {
     public List<Long> findLoanIdsByStatusId(Integer statusId) {
         return repository.findLoanIdByStatusId(statusId);
     }
+
+    public Loan getNonClosedLoanIdAndExternalIdByLoanId(Long loanId) {
+        return repository.getNonClosedLoanIdAndExternalIdByLoanId(loanId);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 8ea66f57c..60754d1b3 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -41,6 +41,7 @@ import 
org.apache.fineract.infrastructure.codes.data.CodeValueData;
 import 
org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
 import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.LoanIdAndExternalIdData;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -144,7 +145,7 @@ import org.springframework.util.CollectionUtils;
 @AllArgsConstructor
 @Service
 @Transactional(readOnly = true)
-public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, 
LoanReadPlatformServiceCommon {
 
     private static final String ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE = 
"submitted-date";
     private final JdbcTemplate jdbcTemplate;
@@ -591,6 +592,17 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService {
         }
     }
 
+    @Override
+    public LoanIdAndExternalIdData getTransferableLoanIdAndExternalId(Long 
loanId) {
+        Loan loan = 
loanRepositoryWrapper.getNonClosedLoanIdAndExternalIdByLoanId(loanId);
+        return new LoanIdAndExternalIdData(loan.getId(), loan.getExternalId());
+    }
+
+    @Override
+    public Long getLoanIdByLoanExternalId(String externalId) {
+        return 
loanRepositoryWrapper.findIdByExternalId(ExternalIdFactory.produce(externalId));
+    }
+
     private static final class LoanMapper implements 
RowMapper<LoanAccountData> {
 
         private final DatabaseSpecificSQLGenerator sqlGenerator;
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerHelper.java
new file mode 100644
index 000000000..26f9538af
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerHelper.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.integrationtests.investor.externalassetowner;
+
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import org.apache.fineract.integrationtests.client.IntegrationTest;
+import org.apache.fineract.integrationtests.common.Utils;
+
+public class ExternalAssetOwnerHelper extends IntegrationTest {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public ExternalAssetOwnerHelper(final RequestSpecification requestSpec, 
final ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public String initiateTransferByLoanId(Long loanId, String command, String 
json) {
+        final String INITIATE_TRANSFER_URL = 
"/fineract-provider/api/v1/external-asset-owners/transfers/loans/" + loanId + 
"?"
+                + Utils.TENANT_IDENTIFIER + "&command=" + command;
+        return Utils.performServerPost(requestSpec, responseSpec, 
INITIATE_TRANSFER_URL, json);
+    }
+
+    public String retrieveTransferByTransferExternalId(String 
transferExternalId) {
+        final String RETRIEVE_TRANSFER_URL = 
"/fineract-provider/api/v1/external-asset-owners/transfers?" + 
Utils.TENANT_IDENTIFIER
+                + "&transferExternalId=" + transferExternalId;
+        return Utils.performServerGet(requestSpec, responseSpec, 
RETRIEVE_TRANSFER_URL);
+    }
+
+    public String retrieveTransferByLoanId(Long loanId) {
+        final String RETRIEVE_TRANSFER_URL = 
"/fineract-provider/api/v1/external-asset-owners/transfers?" + 
Utils.TENANT_IDENTIFIER
+                + "&loanId=" + loanId;
+        return Utils.performServerGet(requestSpec, responseSpec, 
RETRIEVE_TRANSFER_URL);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
new file mode 100644
index 000000000..fe78adeab
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
@@ -0,0 +1,300 @@
+/**
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+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.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+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.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class InitiateExternalAssetOwnerTransferTest {
+
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpecError;
+    private RequestSpecification requestSpec;
+    private ExternalAssetOwnerHelper externalAssetOwnerHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LocalDate todaysDate;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        responseSpecError = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        externalAssetOwnerHelper = new ExternalAssetOwnerHelper(requestSpec, 
responseSpec);
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+
+        todaysDate = Utils.getLocalDateOfTenant();
+    }
+
+    @Test
+    public void saleActiveLoanToExternalAssetOwner() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2));
+            
GlobalConfigurationHelper.updateValueForGlobalConfiguration(requestSpec, 
responseSpec, "10", "0");
+
+            final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec);
+            Assertions.assertNotNull(clientID);
+
+            Integer overdueFeeChargeId = 
ChargesHelper.createCharges(requestSpec, responseSpec,
+                    
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+            Assertions.assertNotNull(overdueFeeChargeId);
+
+            final Integer loanProductID = 
createLoanProduct(overdueFeeChargeId.toString());
+            Assertions.assertNotNull(loanProductID);
+            HashMap loanStatusHashMap;
+
+            final Integer loanID = 
applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, 
"10 January 2020");
+
+            Assertions.assertNotNull(loanID);
+
+            loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, 
responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+            loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 
2020", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+            String loanDetails = 
loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+            loanStatusHashMap = 
loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", 
loanID,
+                    
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+            String transferExternalId = "36efeb06-d835-48a1-99eb-09bd1d348c1e";
+            String saleResponse = 
externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "sale",
+                    getSaleRequestJson("04 March 2020", transferExternalId));
+            Type type = new TypeToken<Map<String, Object>>() {}.getType();
+            Map<String, Object> responseMap = new 
Gson().fromJson(saleResponse, type);
+            assertEquals(responseMap.get("resourceExternalId"), 
transferExternalId);
+
+            String retrieveResponse = 
externalAssetOwnerHelper.retrieveTransferByLoanId(loanID.longValue());
+            Type retrieveType = new TypeToken<List<Map<String, Object>>>() 
{}.getType();
+            List<Map<String, Object>> retrieveResponseMap = new 
Gson().fromJson(retrieveResponse, retrieveType);
+            assertEquals(1, retrieveResponseMap.size());
+            assertEquals(retrieveResponseMap.get(0).get("transferExternalId"), 
transferExternalId);
+            assertEquals(retrieveResponseMap.get(0).get("status"), "PENDING");
+        } finally {
+            requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+            requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+            requestSpec.header("Fineract-Platform-TenantId", "default");
+            responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+        }
+    }
+
+    @Test
+    public void saleIsNotAllowedWhenTransferIsAlreadyPending() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2));
+            
GlobalConfigurationHelper.updateValueForGlobalConfiguration(requestSpec, 
responseSpec, "10", "0");
+
+            final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec);
+            Assertions.assertNotNull(clientID);
+
+            Integer overdueFeeChargeId = 
ChargesHelper.createCharges(requestSpec, responseSpec,
+                    
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+            Assertions.assertNotNull(overdueFeeChargeId);
+
+            final Integer loanProductID = 
createLoanProduct(overdueFeeChargeId.toString());
+            Assertions.assertNotNull(loanProductID);
+            HashMap loanStatusHashMap;
+
+            final Integer loanID = 
applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, 
"10 January 2020");
+
+            Assertions.assertNotNull(loanID);
+
+            loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, 
responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+            loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 
2020", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+            String loanDetails = 
loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+            loanStatusHashMap = 
loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", 
loanID,
+                    
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+            String transferExternalId = "36efeb06-d835-48a1-99eb-09bd1d348c1e";
+            String saleResponse = 
externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "sale",
+                    getSaleRequestJson("04 March 2020", transferExternalId));
+            Type type = new TypeToken<Map<String, Object>>() {}.getType();
+            Map<String, Object> responseMap = new 
Gson().fromJson(saleResponse, type);
+            assertEquals(responseMap.get("resourceExternalId"), 
transferExternalId);
+
+            externalAssetOwnerHelper = new 
ExternalAssetOwnerHelper(requestSpec, responseSpecError);
+            String errorResponse = 
externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "sale",
+                    getSaleRequestJson("04 March 2020", transferExternalId));
+            Map<String, Object> errorResponseMap = new 
Gson().fromJson(errorResponse, type);
+            assertEquals("External asset owner transfer is already in PENDING 
state for this loan.",
+                    errorResponseMap.get("developerMessage"));
+        } finally {
+            requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+            requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+            requestSpec.header("Fineract-Platform-TenantId", "default");
+            responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+        }
+    }
+
+    @Test
+    public void saleAndBuybackOnTheSameDay() {
+        try {
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2));
+            
GlobalConfigurationHelper.updateValueForGlobalConfiguration(requestSpec, 
responseSpec, "10", "0");
+
+            final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec);
+            Assertions.assertNotNull(clientID);
+
+            Integer overdueFeeChargeId = 
ChargesHelper.createCharges(requestSpec, responseSpec,
+                    
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+            Assertions.assertNotNull(overdueFeeChargeId);
+
+            final Integer loanProductID = 
createLoanProduct(overdueFeeChargeId.toString());
+            Assertions.assertNotNull(loanProductID);
+            HashMap loanStatusHashMap;
+
+            final Integer loanID = 
applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, 
"10 January 2020");
+
+            Assertions.assertNotNull(loanID);
+
+            loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, 
responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+            loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 
2020", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+            String loanDetails = 
loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+            loanStatusHashMap = 
loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", 
loanID,
+                    
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+            String transferExternalId = "36efeb06-d835-48a1-99eb-09bd1d348c1e";
+            String saleResponse = 
externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "sale",
+                    getSaleRequestJson("04 March 2020", transferExternalId));
+            Type type = new TypeToken<Map<String, Object>>() {}.getType();
+            Map<String, Object> responseMap = new 
Gson().fromJson(saleResponse, type);
+            assertEquals(responseMap.get("resourceExternalId"), 
transferExternalId);
+
+            String buybackTransferExternalId = 
"36efeb06-d835-48a1-99eb-09bd1d348c1e";
+            String buybackResponse = 
externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "buyback",
+                    getSaleRequestJson("04 March 2020", 
buybackTransferExternalId));
+            Map<String, Object> buybackResponseMap = new 
Gson().fromJson(buybackResponse, type);
+            assertEquals(buybackResponseMap.get("resourceExternalId"), 
buybackTransferExternalId);
+
+            String retrieveResponse = 
externalAssetOwnerHelper.retrieveTransferByLoanId(loanID.longValue());
+            Type retrieveType = new TypeToken<List<Map<String, Object>>>() 
{}.getType();
+            List<Map<String, Object>> retrieveResponseMap = new 
Gson().fromJson(retrieveResponse, retrieveType);
+            assertEquals(2, retrieveResponseMap.size());
+            assertEquals(retrieveResponseMap.get(0).get("transferExternalId"), 
transferExternalId);
+            assertEquals(retrieveResponseMap.get(0).get("status"), "PENDING");
+            assertEquals(retrieveResponseMap.get(1).get("transferExternalId"), 
buybackTransferExternalId);
+            assertEquals(retrieveResponseMap.get(1).get("status"), "BUYBACK");
+        } finally {
+            requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+            requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+            requestSpec.header("Fineract-Platform-TenantId", "default");
+            responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+        }
+    }
+
+    private Integer createLoanProduct(final String chargeId) {
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
+                
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+                .build(chargeId);
+        return loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer applyForLoanApplication(final String clientID, final 
String loanProductID, final String savingsID, final String date) {
+
+        List<HashMap> collaterals = new ArrayList<>();
+        final Integer collateralId = 
CollateralManagementHelper.createCollateralProduct(requestSpec, responseSpec);
+        Assertions.assertNotNull(collateralId);
+        final Integer clientCollateralId = 
CollateralManagementHelper.createClientCollateral(requestSpec, responseSpec, 
clientID,
+                collateralId);
+        Assertions.assertNotNull(clientCollateralId);
+        addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+        final 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, savingsID);
+        return loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private void addCollaterals(List<HashMap> collaterals, Integer 
collateralId, BigDecimal quantity) {
+        collaterals.add(collaterals(collateralId, quantity));
+    }
+
+    private 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;
+    }
+
+    private String getSaleRequestJson(String date, String transferExternalId) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("settlement_date", date);
+        map.put("owner_external_id", "1234567890987654321");
+        map.put("transfer_external_id", transferExternalId);
+        map.put("purchase_price_ratio", "1.234");
+        map.put("dateformat", "dd MMMM yyyy");
+        map.put("locale", "en");
+        return new Gson().toJson(map);
+    }
+
+}


Reply via email to