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

commit 955222ee82e5af7bb009fbf984f44855f97ea937
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Sat Feb 7 14:42:00 2026 -0500

    FINERACT-2421: External Asset Owner create api
---
 .../commands/service/CommandWrapperBuilder.java    |  7 ++
 .../core/service/CommandParameterUtil.java         |  1 +
 .../core/validator/ParseAndValidator.java          | 17 +++++
 .../AssetExternalizationStepDef.java               | 78 ++++++++++++++++++++
 .../fineract/test/support/TestContextKey.java      |  2 +
 .../features/AssetExternalization.feature          | 23 ++++++
 .../api/ExternalAssetOwnersApiResource.java        | 28 +++++++-
 .../api/ExternalAssetOwnersApiResourceSwagger.java | 18 +++++
 .../investor/data/ExternalTransferOwnerData.java   |  1 +
 .../ExternalAssetOwnerDuplicateException.java}     | 20 +++---
 .../serialization/ExternalAssetOwnerValidator.java | 51 ++++++++++++++
 ...e.java => CreateExternalAssetOwnerHandler.java} | 21 +++---
 .../service/ExternalAssetOwnersReadService.java    |  5 ++
 .../ExternalAssetOwnersReadServiceImpl.java        |  9 +++
 .../service/ExternalAssetOwnersTransferMapper.java |  3 +
 .../service/ExternalAssetOwnersWriteService.java   |  1 +
 .../ExternalAssetOwnersWriteServiceImpl.java       | 17 +++++
 .../module/investor/module-changelog-master.xml    |  1 +
 ..._add_external_asser_owner_create_permission.xml | 34 +++++++++
 .../integrationtests/investor/InvestorHelper.java  | 41 +++++++++++
 .../externalassetowner/ExternalAssetOwnerTest.java | 82 ++++++++++++++++++++++
 21 files changed, 440 insertions(+), 20 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 135f937475..9a7302c033 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
@@ -3732,6 +3732,13 @@ public class CommandWrapperBuilder {
         return this;
     }
 
+    public CommandWrapperBuilder createExternalAssetOwner() {
+        this.actionName = "CREATE";
+        this.entityName = "EXTERNAL_ASSET_OWNER";
+        this.href = "/external-asset-owners";
+        return this;
+    }
+
     public CommandWrapperBuilder buybackLoanToExternalAssetOwner(final Long 
loanId) {
         this.actionName = "BUYBACK";
         this.entityName = "LOAN";
diff --git 
a/fineract-core/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
index 7c0efb6524..f4e9a1dc44 100644
--- 
a/fineract-core/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
@@ -30,6 +30,7 @@ public final class CommandParameterUtil {
     public static final String CANCEL_COMMAND_VALUE = "cancel";
     public static final String UPDATE_COMMAND_VALUE = "update";
     public static final String DELETE_COMMAND_VALUE = "delete";
+    public static final String CREATE_COMMAND_VALUE = "create";
 
     private CommandParameterUtil() {}
 
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ParseAndValidator.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ParseAndValidator.java
index dbc6b285a0..c88384e36a 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ParseAndValidator.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/validator/ParseAndValidator.java
@@ -18,8 +18,12 @@
  */
 package org.apache.fineract.infrastructure.core.validator;
 
+import com.google.common.reflect.TypeToken;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -48,4 +52,17 @@ public class ParseAndValidator {
                     dataValidator.getDataValidationErrors());
         }
     }
+
+    protected static void validateRequestBody(String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+    }
+
+    protected void validateForSupportedParameters(final String json, final 
Collection<String> supportedParameters,
+            final FromJsonHelper fromApiJsonHelper) {
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
supportedParameters);
+    }
+
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java
index 4f05df5207..2a93d76bc8 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java
@@ -37,6 +37,7 @@
 package org.apache.fineract.test.stepdef.assetexternalization;
 
 import static org.apache.fineract.client.feign.util.FeignCalls.fail;
+import static org.apache.fineract.client.feign.util.FeignCalls.ok;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import io.cucumber.datatable.DataTable;
@@ -60,11 +61,14 @@ import 
org.apache.fineract.client.models.ExternalOwnerJournalEntryData;
 import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData;
 import org.apache.fineract.client.models.ExternalTransferData;
 import 
org.apache.fineract.client.models.ExternalTransferLoanProductAttributesData;
+import org.apache.fineract.client.models.ExternalTransferOwnerData;
 import org.apache.fineract.client.models.GetLoanProductsResponse;
 import org.apache.fineract.client.models.JournalEntryData;
 import org.apache.fineract.client.models.PageExternalTransferData;
 import 
org.apache.fineract.client.models.PageExternalTransferLoanProductAttributesData;
 import 
org.apache.fineract.client.models.PostExternalAssetOwnerLoanProductAttributeRequest;
+import org.apache.fineract.client.models.PostExternalAssetOwnerRequest;
+import org.apache.fineract.client.models.PostExternalAssetOwnerResponse;
 import org.apache.fineract.client.models.PostInitiateTransferResponse;
 import org.apache.fineract.client.models.PostLoansResponse;
 import 
org.apache.fineract.client.models.PutExternalAssetOwnerLoanProductAttributeRequest;
@@ -929,4 +933,78 @@ public class AssetExternalizationStepDef extends 
AbstractStepDef {
         createAssetExternalizationRequestByLoanId(table, transferExternalId, 
false);
     }
 
+    @When("Admin creates a new external asset owner with a unique 
ownerExternalId")
+    public void createExternalAssetOwnerWithUniqueId() {
+        String ownerExternalId = 
Utils.randomStringGenerator(OWNER_EXTERNAL_ID_PREFIX, 20);
+        testContext().set(TestContextKey.EXTERNAL_ASSET_OWNER_EXTERNAL_ID, 
ownerExternalId);
+
+        PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(ownerExternalId);
+        PostExternalAssetOwnerResponse response = ok(() -> 
externalAssetOwnersApi().createExternalAssetOwner(request));
+        testContext().set(TestContextKey.EXTERNAL_ASSET_OWNER_CREATE_RESPONSE, 
response);
+
+        log.debug("Created external asset owner with externalId: {}, 
resourceId: {}", ownerExternalId, response.getResourceId());
+    }
+
+    @Then("External asset owner creation response has a non-null resourceId")
+    public void verifyCreateResponseHasResourceId() {
+        PostExternalAssetOwnerResponse response = 
testContext().get(TestContextKey.EXTERNAL_ASSET_OWNER_CREATE_RESPONSE);
+        assertThat(response).as("External asset owner create response should 
not be null").isNotNull();
+        assertThat(response.getResourceId()).as("resourceId should not be 
null").isNotNull();
+    }
+
+    @Then("External asset owner list contains the created owner")
+    public void verifyOwnerExistsInList() {
+        String ownerExternalId = 
testContext().get(TestContextKey.EXTERNAL_ASSET_OWNER_EXTERNAL_ID);
+        PostExternalAssetOwnerResponse createResponse = 
testContext().get(TestContextKey.EXTERNAL_ASSET_OWNER_CREATE_RESPONSE);
+
+        List<ExternalTransferOwnerData> owners = ok(() -> 
externalAssetOwnersApi().retrieveExternalAssetOwners());
+        assertThat(owners).as("Owners list should not be empty").isNotEmpty();
+
+        ExternalTransferOwnerData found = owners.stream().filter(o -> 
ownerExternalId.equals(o.getExternalId())).findFirst().orElse(null);
+
+        assertThat(found).as("Owner with externalId '%s' should exist in the 
list", ownerExternalId).isNotNull();
+        assertThat(found.getId()).as("Owner id from GET should match 
resourceId from create").isEqualTo(createResponse.getResourceId());
+    }
+
+    @When("Admin tries to create an external asset owner with null 
ownerExternalId then it should fail with {int} status code")
+    public void createExternalAssetOwnerWithNullIdFails(int 
expectedStatusCode) {
+        PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(null);
+
+        CallFailedRuntimeException exception = fail(() -> 
externalAssetOwnersApi().createExternalAssetOwner(request));
+
+        assertThat(exception.getStatus()).as("Expected HTTP %d for null 
ownerExternalId", expectedStatusCode).isEqualTo(expectedStatusCode);
+        assertThat(exception.getDeveloperMessage()).as("Error message should 
indicate ownerExternalId cannot be blank")
+                
.containsAnyOf("validation.msg.externalAssetOwner.ownerExternalId.cannot.be.blank",
 "ownerExternalId");
+    }
+
+    @When("Admin tries to create an external asset owner with a duplicate 
ownerExternalId then it should fail with {int} status code")
+    public void createExternalAssetOwnerWithDuplicateIdFails(int 
expectedStatusCode) {
+        String ownerExternalId = 
testContext().get(TestContextKey.EXTERNAL_ASSET_OWNER_EXTERNAL_ID);
+
+        PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(ownerExternalId);
+
+        CallFailedRuntimeException exception = fail(() -> 
externalAssetOwnersApi().createExternalAssetOwner(request));
+
+        assertThat(exception.getStatus()).as("Expected HTTP %d for duplicate 
ownerExternalId", expectedStatusCode)
+                .isEqualTo(expectedStatusCode);
+        assertThat(exception.getDeveloperMessage()).as("Error message should 
indicate duplicate external id")
+                .contains("External Asset Owner id already exist");
+    }
+
+    @When("Admin tries to create an external asset owner with empty JSON body 
then it should fail with {int} status code")
+    public void createExternalAssetOwnerWithEmptyBodyFails(int 
expectedStatusCode) {
+        PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest();
+
+        CallFailedRuntimeException exception = fail(() -> 
externalAssetOwnersApi().createExternalAssetOwner(request));
+
+        assertThat(exception.getStatus()).as("Expected HTTP %d for missing 
ownerExternalId", expectedStatusCode)
+                .isEqualTo(expectedStatusCode);
+    }
+
+    @Then("Admin retrieves all external asset owners successfully")
+    public void retrieveAllExternalAssetOwners() {
+        List<ExternalTransferOwnerData> owners = ok(() -> 
externalAssetOwnersApi().retrieveExternalAssetOwners());
+        assertThat(owners).as("Owners list should not be null").isNotNull();
+    }
+
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index 99b5e28733..1b18697f58 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -255,6 +255,8 @@ public abstract class TestContextKey {
     public static final String ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_PREFIX = 
"assetExternalizationTransferPrefix";
     public static final String ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID = 
"assetExternalizationOwnerExternalId";
     public static final String 
ASSET_EXTERNALIZATION_PREVIOUS_OWNER_EXTERNAL_ID = 
"assetExternalizationPreviousOwnerExternalId";
+    public static final String EXTERNAL_ASSET_OWNER_CREATE_RESPONSE = 
"externalAssetOwnerCreateResponse";
+    public static final String EXTERNAL_ASSET_OWNER_EXTERNAL_ID = 
"externalAssetOwnerExternalId";
     public static final String TRANSACTION_EVENT = "transactionEvent";
     public static final String LOAN_WRITE_OFF_RESPONSE = 
"loanWriteOffResponse";
     public static final String LOAN_DELINQUENCY_ACTION_RESPONSE = 
"loanDelinquencyActionResponse";
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
index 0635bde96d..ddb17f7364 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
@@ -2019,3 +2019,26 @@ Feature: Asset Externalization
     Then LoanOwnershipTransferBusinessEvent with transfer type: "BUYBACK" and 
transfer asset owner based on intermediarySale is created
     When Admin set external asset owner loan product attribute 
"SETTLEMENT_MODEL" value "DEFAULT_SETTLEMENT" for loan product "LP1_DUE_DATE"
 
+  @TestRailId:C4640
+  Scenario: Verify creation of new external asset owner and it presence in the 
list
+    When Admin creates a new external asset owner with a unique ownerExternalId
+    Then External asset owner creation response has a non-null resourceId
+    Then External asset owner list contains the created owner
+
+  @TestRailId:C4641
+  Scenario: Verify creation of an external asset owner fails for null, 
duplicate and empty ownerExternalId
+    When Admin tries to create an external asset owner with null 
ownerExternalId then it should fail with 400 status code
+    When Admin tries to create an external asset owner with empty JSON body 
then it should fail with 400 status code
+    When Admin creates a new external asset owner with a unique ownerExternalId
+    Then External asset owner creation response has a non-null resourceId
+    When Admin tries to create an external asset owner with a duplicate 
ownerExternalId then it should fail with 403 status code
+
+  @TestRailId:C4642
+  Scenario: Verify creation of multiple external asset owners and presence of 
all items the list
+    When Admin creates a new external asset owner with a unique ownerExternalId
+    Then External asset owner creation response has a non-null resourceId
+    Then External asset owner list contains the created owner
+    When Admin creates a new external asset owner with a unique ownerExternalId
+    Then External asset owner creation response has a non-null resourceId
+    Then External asset owner list contains the created owner
+    Then Admin retrieves all external asset owners successfully
\ No newline at end of file
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 f1a02f95ea..783b3c26db 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
@@ -20,6 +20,7 @@ package org.apache.fineract.investor.api;
 
 import static 
org.apache.fineract.infrastructure.core.service.CommandParameterUtil.BUY_BACK_COMMAND_VALUE;
 import static 
org.apache.fineract.infrastructure.core.service.CommandParameterUtil.CANCEL_COMMAND_VALUE;
+import static 
org.apache.fineract.infrastructure.core.service.CommandParameterUtil.CREATE_COMMAND_VALUE;
 import static 
org.apache.fineract.infrastructure.core.service.CommandParameterUtil.INTERMEDIARY_SALE_COMMAND_VALUE;
 import static 
org.apache.fineract.infrastructure.core.service.CommandParameterUtil.SALE_COMMAND_VALUE;
 
@@ -38,6 +39,7 @@ import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.MediaType;
+import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.batch.command.CommandHandlerRegistry;
@@ -55,6 +57,7 @@ import 
org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
 import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
 import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
 import org.apache.fineract.investor.data.ExternalTransferData;
+import org.apache.fineract.investor.data.ExternalTransferOwnerData;
 import org.apache.fineract.investor.data.request.ExternalAssetOwnerRequest;
 import org.apache.fineract.investor.service.ExternalAssetOwnersReadService;
 import 
org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest;
@@ -85,7 +88,8 @@ public class ExternalAssetOwnersApiResource {
                     (id, json) -> new 
CommandWrapperBuilder().withJson(json).intermediarySaleLoanToExternalAssetOwner(id).build(),
                     SALE_COMMAND_VALUE, (id, json) -> new 
CommandWrapperBuilder().withJson(json).saleLoanToExternalAssetOwner(id).build(),
                     BUY_BACK_COMMAND_VALUE,
-                    (id, json) -> new 
CommandWrapperBuilder().withJson(json).buybackLoanToExternalAssetOwner(id).build()));
+                    (id, json) -> new 
CommandWrapperBuilder().withJson(json).buybackLoanToExternalAssetOwner(id).build(),
+                    CREATE_COMMAND_VALUE, (id, json) -> new 
CommandWrapperBuilder().withJson(json).createExternalAssetOwner().build()));
 
     @POST
     @Path("/transfers/loans/{loanId}")
@@ -215,4 +219,26 @@ public class ExternalAssetOwnersApiResource {
         platformUserRightsContext.isAuthenticated();
         return delegate.searchInvestorData(request);
     }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Create an External Asset Owner using the External 
Id")
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostExternalAssetOwnerRequest.class)))
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
ExternalAssetOwnersApiResourceSwagger.PostExternalAssetOwnerResponse.class)))
+    @ApiResponse(responseCode = "400", description = "Bad requests due invalid 
json data")
+    public CommandProcessingResult createExternalAssetOwner(@Parameter(hidden 
= true) final String apiRequestBodyAsJson) {
+        platformUserRightsContext.isAuthenticated();
+        final CommandWrapper commandRequest = 
COMMAND_HANDLER_REGISTRY.execute(CREATE_COMMAND_VALUE, null, 
apiRequestBodyAsJson,
+                new UnrecognizedQueryParamException(COMMAND_PARAM, 
CREATE_COMMAND_VALUE));
+        return 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+    }
+
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Get all External Asset Owner with details")
+    public List<ExternalTransferOwnerData> retrieveExternalAssetOwners() {
+        platformUserRightsContext.isAuthenticated();
+        return externalAssetOwnersReadService.retrieveAllExternalOwners();
+    }
 }
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 92e0b9a091..ea5a3f1830 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
@@ -68,4 +68,22 @@ final class ExternalAssetOwnersApiResourceSwagger {
         }
     }
 
+    @Schema(description = "PostExternalAssetOwnerRequest")
+    public static final class PostExternalAssetOwnerRequest {
+
+        private PostExternalAssetOwnerRequest() {}
+
+        @Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e", description 
= "External Asset Owner External Id")
+        public String ownerExternalId;
+    }
+
+    @Schema(description = "PostExternalAssetOwnerResponse")
+    public static final class PostExternalAssetOwnerResponse {
+
+        private PostExternalAssetOwnerResponse() {}
+
+        @Schema(example = "1", description = "External Asset Owner Id")
+        public Long resourceId;
+    }
+
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferOwnerData.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferOwnerData.java
index edc1602bdd..c8e80a9d6f 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferOwnerData.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferOwnerData.java
@@ -24,4 +24,5 @@ import lombok.Data;
 public class ExternalTransferOwnerData {
 
     private final String externalId;
+    private Long id;
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerDuplicateException.java
similarity index 58%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerDuplicateException.java
index 146a26421c..450a7f0f3f 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerDuplicateException.java
@@ -16,19 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.investor.service;
+package org.apache.fineract.investor.exception;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import 
org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 
-public interface ExternalAssetOwnersWriteService {
-
-    CommandProcessingResult intermediarySaleLoanByLoanId(JsonCommand 
jsonCommand);
-
-    CommandProcessingResult saleLoanByLoanId(JsonCommand command);
-
-    CommandProcessingResult buybackLoanByLoanId(JsonCommand command);
+/**
+ * A {@link RuntimeException} thrown when a GL Closure for a given date and 
Office combination is already present
+ */
+public class ExternalAssetOwnerDuplicateException extends 
AbstractPlatformDomainRuleException {
 
-    CommandProcessingResult cancelTransactionById(JsonCommand command);
+    public ExternalAssetOwnerDuplicateException(final String externalId) {
+        super("error.msg.provided.external.id.already.exists", "Provided 
external id already exists with Id " + externalId);
+    }
 
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/serialization/ExternalAssetOwnerValidator.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/serialization/ExternalAssetOwnerValidator.java
new file mode 100644
index 0000000000..918d335273
--- /dev/null
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/serialization/ExternalAssetOwnerValidator.java
@@ -0,0 +1,51 @@
+
+/**
+ * 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.serialization;
+
+import com.google.gson.JsonElement;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
+import org.apache.fineract.investor.data.ExternalTransferRequestParameters;
+import org.apache.fineract.portfolio.common.service.Validator;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class ExternalAssetOwnerValidator extends ParseAndValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    public void validateForCreate(JsonCommand command) {
+        final String json = command.json();
+        validateRequestBody(json);
+        validateForSupportedParameters(json, 
List.of(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID), 
fromApiJsonHelper);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        Validator.validateOrThrow("externalAssetOwner", baseDataValidator -> {
+            final String ownerExternalId = 
this.fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
+                    element);
+            
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID).value(ownerExternalId).notNull();
+        });
+    }
+
+}
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerHandler.java
similarity index 60%
copy from 
fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
copy to 
fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerHandler.java
index 146a26421c..e06ddfa580 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerHandler.java
@@ -18,17 +18,22 @@
  */
 package org.apache.fineract.investor.service;
 
+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 ExternalAssetOwnersWriteService {
+@RequiredArgsConstructor
+@Service
+@CommandType(entity = "EXTERNAL_ASSET_OWNER", action = "CREATE")
+public class CreateExternalAssetOwnerHandler implements 
NewCommandSourceHandler {
 
-    CommandProcessingResult intermediarySaleLoanByLoanId(JsonCommand 
jsonCommand);
-
-    CommandProcessingResult saleLoanByLoanId(JsonCommand command);
-
-    CommandProcessingResult buybackLoanByLoanId(JsonCommand command);
-
-    CommandProcessingResult cancelTransactionById(JsonCommand command);
+    private final ExternalAssetOwnersWriteService 
externalAssetOwnersWriteService;
 
+    @Override
+    public CommandProcessingResult processCommand(JsonCommand command) {
+        return 
externalAssetOwnersWriteService.createExternalAssetOwner(command);
+    }
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadService.java
index 919551e0c8..d42338f975 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersReadService.java
@@ -18,10 +18,12 @@
  */
 package org.apache.fineract.investor.service;
 
+import java.util.List;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
 import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
 import org.apache.fineract.investor.data.ExternalTransferData;
+import org.apache.fineract.investor.data.ExternalTransferOwnerData;
 import org.springframework.data.domain.Page;
 
 public interface ExternalAssetOwnersReadService {
@@ -40,4 +42,7 @@ public interface ExternalAssetOwnersReadService {
     ExternalTransferData retrieveFirstTransferByExternalId(ExternalId 
externalTransferId);
 
     Long retrieveLastTransferIdByExternalId(ExternalId externalTransferId);
+
+    List<ExternalTransferOwnerData> retrieveAllExternalOwners();
+
 }
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 8c86b932ae..8c336ab10b 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,6 +18,7 @@
  */
 package org.apache.fineract.investor.service;
 
+import java.util.List;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.accounting.journalentry.JournalEntryMapper;
@@ -26,8 +27,10 @@ import 
org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
 import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
 import org.apache.fineract.investor.data.ExternalTransferData;
+import org.apache.fineract.investor.data.ExternalTransferOwnerData;
 import 
org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping;
 import 
org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMappingRepository;
+import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
 import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
 import 
org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping;
 import 
org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository;
@@ -52,6 +55,7 @@ public class ExternalAssetOwnersReadServiceImpl implements 
ExternalAssetOwnersRe
     private final ExternalAssetOwnerJournalEntryMappingRepository 
externalAssetOwnerJournalEntryMappingRepository;
     private final ExternalAssetOwnersTransferMapper mapper;
     private final JournalEntryMapper journalEntryMapper;
+    private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
 
     @Override
     public Page<ExternalTransferData> retrieveTransferData(Long loanId, String 
externalLoanId, String externalTransferId, Integer offset,
@@ -142,4 +146,9 @@ public class ExternalAssetOwnersReadServiceImpl implements 
ExternalAssetOwnersRe
         return PageRequest.of(offset, limit, Sort.by("id"));
     }
 
+    @Override
+    public List<ExternalTransferOwnerData> retrieveAllExternalOwners() {
+        return mapper.mapOwners(externalAssetOwnerRepository.findAll());
+    }
+
 }
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersTransferMapper.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersTransferMapper.java
index b60e85e06d..3ca32ae3aa 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersTransferMapper.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersTransferMapper.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.investor.service;
 
+import java.util.List;
 import org.apache.fineract.accounting.journalentry.JournalEntryMapper;
 import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
 import org.apache.fineract.investor.data.ExternalTransferData;
@@ -49,6 +50,8 @@ public interface ExternalAssetOwnersTransferMapper {
 
     ExternalTransferOwnerData mapOwner(ExternalAssetOwner source);
 
+    List<ExternalTransferOwnerData> mapOwners(List<ExternalAssetOwner> source);
+
     @Mapping(target = "detailsId", source = "id")
     ExternalTransferDataDetails mapDetails(ExternalAssetOwnerTransferDetails 
details);
 
diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
index 146a26421c..8b0dea4cc9 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteService.java
@@ -31,4 +31,5 @@ public interface ExternalAssetOwnersWriteService {
 
     CommandProcessingResult cancelTransactionById(JsonCommand command);
 
+    CommandProcessingResult createExternalAssetOwner(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
index bb8477b239..8fd3873bd9 100644
--- 
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
@@ -59,7 +59,9 @@ 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.ExternalAssetOwnerDuplicateException;
 import 
org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
+import org.apache.fineract.investor.serialization.ExternalAssetOwnerValidator;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -82,6 +84,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements 
ExternalAssetOwnersW
     private final DelayedSettlementAttributeService 
delayedSettlementAttributeService;
     private final ConfigurationDomainService configurationDomainService;
     private final ExternalAssetOwnersReadService 
externalAssetOwnersReadService;
+    private final ExternalAssetOwnerValidator externalAssetOwnerValidator;
 
     @Override
     @Transactional
@@ -601,4 +604,18 @@ public class ExternalAssetOwnersWriteServiceImpl 
implements ExternalAssetOwnersW
         return 
configurationDomainService.getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer().stream()
                 .map(LoanStatus::valueOf).collect(Collectors.toList());
     }
+
+    @Override
+    public CommandProcessingResult createExternalAssetOwner(JsonCommand 
command) {
+        externalAssetOwnerValidator.validateForCreate(command);
+        String ownerExternalId = 
command.stringValueOfParameterNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID);
+        Optional<ExternalAssetOwner> optExternalId = 
externalAssetOwnerRepository
+                .findByExternalId(ExternalIdFactory.produce(ownerExternalId));
+        if (!optExternalId.isEmpty()) {
+            throw new ExternalAssetOwnerDuplicateException(ownerExternalId);
+        }
+
+        final ExternalAssetOwner externalAssetOwner = 
createAndGetAssetOwner(ownerExternalId);
+        return new 
CommandProcessingResultBuilder().withEntityId(externalAssetOwner.getId()).build();
+    }
 }
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 d7f6174a5e..10dc6bbf3f 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
@@ -43,4 +43,5 @@
   <include relativeToChangelogFile="true" 
file="parts/0019_add_configurable_allowed_loan_statuses.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/0020_add_previous_owner_reference.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/0021_external_owner_reference_in_journal_entry_aggregation.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/0022_add_external_asser_owner_create_permission.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0022_add_external_asser_owner_create_permission.xml
 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0022_add_external_asser_owner_create_permission.xml
new file mode 100644
index 0000000000..cf3438dae8
--- /dev/null
+++ 
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0022_add_external_asser_owner_create_permission.xml
@@ -0,0 +1,34 @@
+<?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="investor"/>
+            <column name="code" value="CREATE_EXTERNAL_ASSET_OWNER"/>
+            <column name="entity_name" value="EXTERNAL_ASSET_OWNER"/>
+            <column name="action_name" value="CREATE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/InvestorHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/InvestorHelper.java
new file mode 100644
index 0000000000..5aa7bec75b
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/InvestorHelper.java
@@ -0,0 +1,41 @@
+/**
+ * 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;
+
+import java.util.List;
+import org.apache.fineract.client.models.ExternalTransferOwnerData;
+import org.apache.fineract.client.models.PostExternalAssetOwnerRequest;
+import org.apache.fineract.client.models.PostExternalAssetOwnerResponse;
+import org.apache.fineract.client.util.Calls;
+import org.apache.fineract.integrationtests.common.FineractClientHelper;
+
+public final class InvestorHelper {
+
+    private InvestorHelper() {
+
+    }
+
+    public static List<ExternalTransferOwnerData> 
retrieveExternalAssetOwners() {
+        return 
Calls.ok(FineractClientHelper.getFineractClient().externalAssetOwners.retrieveExternalAssetOwners());
+    }
+
+    public static PostExternalAssetOwnerResponse 
createExternalAssetOwner(final PostExternalAssetOwnerRequest request) {
+        return 
Calls.ok(FineractClientHelper.getFineractClient().externalAssetOwners.createExternalAssetOwner(request));
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTest.java
new file mode 100644
index 0000000000..e9b091581f
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTest.java
@@ -0,0 +1,82 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.ExternalTransferOwnerData;
+import org.apache.fineract.client.models.PostExternalAssetOwnerRequest;
+import org.apache.fineract.client.models.PostExternalAssetOwnerResponse;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
+import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.investor.InvestorHelper;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class ExternalAssetOwnerTest extends BaseLoanIntegrationTest {
+
+    @Test
+    public void testCreateExternalAssetOwnerSuccessfully() {
+        final String ownerExternalId = 
Utils.uniqueRandomStringGenerator("eao", 20);
+        final PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(ownerExternalId);
+
+        final PostExternalAssetOwnerResponse response = 
InvestorHelper.createExternalAssetOwner(request);
+
+        assertNotNull(response);
+        assertNotNull(response.getResourceId());
+
+        List<ExternalTransferOwnerData> externalAssetOwners = 
InvestorHelper.retrieveExternalAssetOwners();
+        assertTrue(externalAssetOwners.size() > 0);
+        Optional<ExternalTransferOwnerData> optExternalTransferOwnerData = 
externalAssetOwners.stream()
+                .filter(eao -> 
eao.getExternalId().equals(ownerExternalId)).findFirst();
+        assertTrue(optExternalTransferOwnerData.isPresent());
+    }
+
+    @Test
+    public void testCreateExternalAssetOwnerFailsWhenOwnerExternalIdIsNull() {
+        final PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(null);
+
+        final CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                () -> InvestorHelper.createExternalAssetOwner(request));
+        assertEquals(400, exception.getResponse().code());
+        
assertTrue(exception.getMessage().contains("validation.msg.externalAssetOwner.ownerExternalId.cannot.be.blank"));
+    }
+
+    @Test
+    public void 
testCreateExternalAssetOwnerFailsWhenOwnerExternalIdIsDuplicate() {
+        final String ownerExternalId = 
Utils.uniqueRandomStringGenerator("eao", 20);
+        final PostExternalAssetOwnerRequest request = new 
PostExternalAssetOwnerRequest().ownerExternalId(ownerExternalId);
+
+        final PostExternalAssetOwnerResponse firstResponse = 
InvestorHelper.createExternalAssetOwner(request);
+        assertNotNull(firstResponse);
+        assertNotNull(firstResponse.getResourceId());
+
+        final CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                () -> InvestorHelper.createExternalAssetOwner(request));
+        assertEquals(403, exception.getResponse().code());
+        
assertTrue(exception.getMessage().contains("error.msg.provided.external.id.already.exists"));
+    }
+}


Reply via email to