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

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 82e6a237a6 FINERACT-2418: add origination api implementation
82e6a237a6 is described below

commit 82e6a237a61d821b9ffd80f082c042ba059a113f
Author: Attila Budai <[email protected]>
AuthorDate: Thu Jan 22 16:43:23 2026 +0100

    FINERACT-2418: add origination api implementation
---
 .../fineract/client/feign/FineractFeignClient.java |   5 +
 .../api/LoanOriginatorApiResource.java             |  23 +-
 .../api/LoanOriginatorApiResourceSwagger.java      | 104 ++++++++
 .../domain/LoanOriginatorRepository.java           |  11 +
 .../LoanOriginatorCannotBeDeletedException.java}   |  19 +-
 ...oanOriginatorDuplicateExternalIdException.java} |  19 +-
 .../LoanOriginatorInvalidStatusException.java}     |  22 +-
 .../LoanOriginatorNotFoundException.java}          |  21 +-
 .../CreateLoanOriginatorCommandHandler.java}       |  35 +--
 .../DeleteLoanOriginatorCommandHandler.java}       |  35 +--
 .../UpdateLoanOriginatorCommandHandler.java}       |  38 ++-
 .../LoanOriginatorMapper.java}                     |  37 +--
 .../serialization/LoanOriginatorDataValidator.java | 121 +++++++++
 .../LoanOriginatorReadPlatformServiceImpl.java     |  33 ++-
 .../LoanOriginatorWritePlatformServiceImpl.java    | 154 ++++++++++++
 .../loanorigination/module-changelog-master.xml    |   1 +
 .../loanorigination/parts/0002_permissions.xml     |  78 ++++++
 .../src/main/resources/application.properties      |   2 +-
 .../src/test/resources/application-test.properties |   3 +-
 .../integrationtests/LoanOriginatorApiTest.java    | 280 +++++++++++++++++++++
 .../feign/helpers/FeignLoanOriginatorHelper.java   | 108 ++++++++
 .../feign/tests/FeignLoanOriginatorApiTest.java    | 248 ++++++++++++++++++
 .../common/loans/LoanOriginatorHelper.java         | 166 ++++++++++++
 23 files changed, 1408 insertions(+), 155 deletions(-)

diff --git 
a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
 
b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
index 8c049039f8..61a3108f5e 100644
--- 
a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
+++ 
b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java
@@ -96,6 +96,7 @@ import 
org.apache.fineract.client.feign.services.LoanCollateralApi;
 import org.apache.fineract.client.feign.services.LoanCollateralManagementApi;
 import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApi;
 import org.apache.fineract.client.feign.services.LoanInterestPauseApi;
+import org.apache.fineract.client.feign.services.LoanOriginatorsApi;
 import org.apache.fineract.client.feign.services.LoanProductsApi;
 import org.apache.fineract.client.feign.services.LoanReschedulingApi;
 import org.apache.fineract.client.feign.services.LoanTransactionsApi;
@@ -532,6 +533,10 @@ public final class FineractFeignClient {
         return create(LoanInterestPauseApi.class);
     }
 
+    public LoanOriginatorsApi loanOriginators() {
+        return create(LoanOriginatorsApi.class);
+    }
+
     public LoanProductsApi loanProducts() {
         return create(LoanProductsApi.class);
     }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java
index 9053c4a1fe..7a437755d1 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java
@@ -20,6 +20,10 @@ package org.apache.fineract.portfolio.loanorigination.api;
 
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.ws.rs.Consumes;
@@ -58,7 +62,8 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Create a new loan originator", description = 
"Creates a new loan originator record. Requires CREATE_LOAN_ORIGINATOR 
permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
LoanOriginatorApiResourceSwagger.PostLoanOriginatorsRequest.class)))
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.PostLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "400", description = "Required parameter is 
missing or incorrect format")
     @ApiResponse(responseCode = "403", description = "Duplicate external ID or 
insufficient permissions")
     public CommandProcessingResult create(@Parameter(hidden = true) final 
String apiRequestBodyAsJson) {
@@ -70,7 +75,7 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "List all loan originators", description = "Retrieves 
all loan originator records. Requires READ_LOAN_ORIGINATOR permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(array = @ArraySchema(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class))))
     @ApiResponse(responseCode = "403", description = "Insufficient 
permissions")
     public List<LoanOriginatorData> retrieveAll() {
         
this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME);
@@ -83,7 +88,7 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Retrieve a loan originator by ID", description = 
"Retrieves a loan originator by its internal ID. Requires READ_LOAN_ORIGINATOR 
permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "403", description = "Insufficient 
permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
     public LoanOriginatorData retrieveOne(@PathParam("originatorId") 
@Parameter(description = "originatorId") final Long originatorId) {
@@ -97,7 +102,7 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Retrieve a loan originator by external ID", 
description = "Retrieves a loan originator by its external ID. Requires 
READ_LOAN_ORIGINATOR permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "403", description = "Insufficient 
permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
     public LoanOriginatorData retrieveByExternalId(
@@ -112,7 +117,8 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Update a loan originator by ID", description = 
"Updates a loan originator by its internal ID. Requires UPDATE_LOAN_ORIGINATOR 
permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
LoanOriginatorApiResourceSwagger.PutLoanOriginatorsRequest.class)))
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.PutLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "400", description = "Incorrect format")
     @ApiResponse(responseCode = "403", description = "Insufficient 
permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
@@ -128,7 +134,8 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Update a loan originator by external ID", 
description = "Updates a loan originator by its external ID. Requires 
UPDATE_LOAN_ORIGINATOR permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = 
LoanOriginatorApiResourceSwagger.PutLoanOriginatorsRequest.class)))
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.PutLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "400", description = "Incorrect format")
     @ApiResponse(responseCode = "403", description = "Insufficient 
permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
@@ -147,7 +154,7 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Delete a loan originator by ID", description = 
"Deletes a loan originator by its internal ID. Requires DELETE_LOAN_ORIGINATOR 
permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.DeleteLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "403", description = "Originator is mapped to 
loans and cannot be deleted, or insufficient permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
     public CommandProcessingResult delete(@PathParam("originatorId") 
@Parameter(description = "originatorId") final Long originatorId) {
@@ -160,7 +167,7 @@ public class LoanOriginatorApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Delete a loan originator by external ID", 
description = "Deletes a loan originator by its external ID. Requires 
DELETE_LOAN_ORIGINATOR permission.")
-    @ApiResponse(responseCode = "200", description = "OK")
+    @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanOriginatorApiResourceSwagger.DeleteLoanOriginatorsResponse.class)))
     @ApiResponse(responseCode = "403", description = "Originator is mapped to 
loans and cannot be deleted, or insufficient permissions")
     @ApiResponse(responseCode = "404", description = "Originator not found")
     public CommandProcessingResult deleteByExternalId(
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResourceSwagger.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResourceSwagger.java
new file mode 100644
index 0000000000..c80b0d281e
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResourceSwagger.java
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanorigination.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+final class LoanOriginatorApiResourceSwagger {
+
+    private LoanOriginatorApiResourceSwagger() {}
+
+    @Schema(description = "GetLoanOriginatorsResponse")
+    public static final class GetLoanOriginatorsResponse {
+
+        private GetLoanOriginatorsResponse() {}
+
+        @Schema(example = "1")
+        public Long id;
+        @Schema(example = "EXT-001")
+        public String externalId;
+        @Schema(example = "Main Branch Originator")
+        public String name;
+        @Schema(example = "ACTIVE")
+        public String status;
+        @Schema(example = "1")
+        public Long originatorTypeId;
+        @Schema(example = "1")
+        public Long channelTypeId;
+    }
+
+    @Schema(description = "PostLoanOriginatorsRequest")
+    public static final class PostLoanOriginatorsRequest {
+
+        private PostLoanOriginatorsRequest() {}
+
+        @Schema(example = "EXT-001")
+        public String externalId;
+        @Schema(example = "Main Branch Originator", requiredMode = 
Schema.RequiredMode.REQUIRED)
+        public String name;
+        @Schema(example = "ACTIVE")
+        public String status;
+        @Schema(example = "1")
+        public Long originatorTypeId;
+        @Schema(example = "1")
+        public Long channelTypeId;
+    }
+
+    @Schema(description = "PostLoanOriginatorsResponse")
+    public static final class PostLoanOriginatorsResponse {
+
+        private PostLoanOriginatorsResponse() {}
+
+        @Schema(example = "1")
+        public Long resourceId;
+    }
+
+    @Schema(description = "PutLoanOriginatorsRequest")
+    public static final class PutLoanOriginatorsRequest {
+
+        private PutLoanOriginatorsRequest() {}
+
+        @Schema(example = "Updated Name")
+        public String name;
+        @Schema(example = "INACTIVE")
+        public String status;
+        @Schema(example = "2")
+        public Long originatorTypeId;
+        @Schema(example = "2")
+        public Long channelTypeId;
+    }
+
+    @Schema(description = "PutLoanOriginatorsResponse")
+    public static final class PutLoanOriginatorsResponse {
+
+        private PutLoanOriginatorsResponse() {}
+
+        @Schema(example = "1")
+        public Long resourceId;
+    }
+
+    @Schema(description = "DeleteLoanOriginatorsResponse")
+    public static final class DeleteLoanOriginatorsResponse {
+
+        private DeleteLoanOriginatorsResponse() {}
+
+        @Schema(example = "1")
+        public Long resourceId;
+    }
+}
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
index 09c2969240..99bf4a5c1b 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
@@ -23,6 +23,8 @@ 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;
+import org.springframework.data.repository.query.Param;
 
 public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
 
@@ -31,4 +33,13 @@ public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator,
     boolean existsByExternalId(ExternalId externalId);
 
     List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
+
+    @Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType 
LEFT JOIN FETCH lo.channelType")
+    List<LoanOriginator> findAllWithCodeValues();
+
+    @Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType 
LEFT JOIN FETCH lo.channelType WHERE lo.id = :id")
+    Optional<LoanOriginator> findByIdWithCodeValues(@Param("id") Long id);
+
+    @Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType 
LEFT JOIN FETCH lo.channelType WHERE lo.externalId = :externalId")
+    Optional<LoanOriginator> 
findByExternalIdWithCodeValues(@Param("externalId") ExternalId externalId);
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorCannotBeDeletedException.java
similarity index 55%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorCannotBeDeletedException.java
index 09c2969240..a5c6fe265e 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorCannotBeDeletedException.java
@@ -16,19 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.domain;
+package org.apache.fineract.portfolio.loanorigination.exception;
 
-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.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 
-public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
+public class LoanOriginatorCannotBeDeletedException extends 
AbstractPlatformDomainRuleException {
 
-    Optional<LoanOriginator> findByExternalId(ExternalId externalId);
-
-    boolean existsByExternalId(ExternalId externalId);
-
-    List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
+    public LoanOriginatorCannotBeDeletedException(Long id) {
+        super("error.msg.loan.originator.cannot.be.deleted.mapped.to.loan",
+                "Loan Originator with id " + id + " cannot be deleted as it is 
mapped to one or more loans", id);
+    }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorDuplicateExternalIdException.java
similarity index 55%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorDuplicateExternalIdException.java
index 09c2969240..33612c182b 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorDuplicateExternalIdException.java
@@ -16,19 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.domain;
+package org.apache.fineract.portfolio.loanorigination.exception;
 
-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.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 
-public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
+public class LoanOriginatorDuplicateExternalIdException extends 
AbstractPlatformDomainRuleException {
 
-    Optional<LoanOriginator> findByExternalId(ExternalId externalId);
-
-    boolean existsByExternalId(ExternalId externalId);
-
-    List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
+    public LoanOriginatorDuplicateExternalIdException(String externalId) {
+        super("error.msg.loan.originator.duplicate.external.id", "Loan 
Originator with external id '" + externalId + "' already exists",
+                externalId);
+    }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorInvalidStatusException.java
similarity index 51%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorInvalidStatusException.java
index 09c2969240..3b8630dbf1 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorInvalidStatusException.java
@@ -16,19 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.domain;
+package org.apache.fineract.portfolio.loanorigination.exception;
 
-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.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
 
-public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
+public class LoanOriginatorInvalidStatusException extends 
AbstractPlatformDomainRuleException {
 
-    Optional<LoanOriginator> findByExternalId(ExternalId externalId);
+    public LoanOriginatorInvalidStatusException(String status) {
+        super("error.msg.loan.originator.invalid.status",
+                "Invalid loan originator status: " + status + ". Valid values 
are: ACTIVE, PENDING, INACTIVE", status);
+    }
 
-    boolean existsByExternalId(ExternalId externalId);
-
-    List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
+    public LoanOriginatorInvalidStatusException(String status, Throwable 
cause) {
+        super("error.msg.loan.originator.invalid.status",
+                "Invalid loan originator status: " + status + ". Valid values 
are: ACTIVE, PENDING, INACTIVE", status, cause);
+    }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorNotFoundException.java
similarity index 55%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorNotFoundException.java
index 09c2969240..eec740b206 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/exception/LoanOriginatorNotFoundException.java
@@ -16,19 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.domain;
+package org.apache.fineract.portfolio.loanorigination.exception;
 
-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.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
 
-public interface LoanOriginatorRepository extends 
JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
+public class LoanOriginatorNotFoundException extends 
AbstractPlatformResourceNotFoundException {
 
-    Optional<LoanOriginator> findByExternalId(ExternalId externalId);
+    public LoanOriginatorNotFoundException(Long id) {
+        super("error.msg.loan.originator.id.not.found", "Loan Originator with 
id " + id + " not found", id);
+    }
 
-    boolean existsByExternalId(ExternalId externalId);
-
-    List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
+    public LoanOriginatorNotFoundException(String externalId) {
+        super("error.msg.loan.originator.external.id.not.found", "Loan 
Originator with external id " + externalId + " not found",
+                externalId);
+    }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/CreateLoanOriginatorCommandHandler.java
similarity index 53%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/CreateLoanOriginatorCommandHandler.java
index dd9f84c3c6..cd8fe7196d 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/CreateLoanOriginatorCommandHandler.java
@@ -16,36 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.service;
+package org.apache.fineract.portfolio.loanorigination.handler;
 
-import java.util.List;
-import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
+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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
 @Service
+@CommandType(entity = "LOAN_ORIGINATOR", action = "CREATE")
+@RequiredArgsConstructor
 @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
-public class LoanOriginatorReadPlatformServiceImpl implements 
LoanOriginatorReadPlatformService {
+public class CreateLoanOriginatorCommandHandler implements 
NewCommandSourceHandler {
 
-    private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented 
yet";
+    private final LoanOriginatorWritePlatformService writePlatformService;
 
     @Override
-    public List<LoanOriginatorData> retrieveAll() {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveById(Long id) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public Long resolveIdByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.create(command);
     }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/DeleteLoanOriginatorCommandHandler.java
similarity index 53%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/DeleteLoanOriginatorCommandHandler.java
index dd9f84c3c6..383a182864 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/DeleteLoanOriginatorCommandHandler.java
@@ -16,36 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.service;
+package org.apache.fineract.portfolio.loanorigination.handler;
 
-import java.util.List;
-import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
+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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
 @Service
+@CommandType(entity = "LOAN_ORIGINATOR", action = "DELETE")
+@RequiredArgsConstructor
 @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
-public class LoanOriginatorReadPlatformServiceImpl implements 
LoanOriginatorReadPlatformService {
+public class DeleteLoanOriginatorCommandHandler implements 
NewCommandSourceHandler {
 
-    private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented 
yet";
+    private final LoanOriginatorWritePlatformService writePlatformService;
 
     @Override
-    public List<LoanOriginatorData> retrieveAll() {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveById(Long id) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public Long resolveIdByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.delete(command.entityId());
     }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/UpdateLoanOriginatorCommandHandler.java
similarity index 52%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/UpdateLoanOriginatorCommandHandler.java
index dd9f84c3c6..712e6f6743 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/handler/UpdateLoanOriginatorCommandHandler.java
@@ -1,3 +1,4 @@
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -16,36 +17,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.service;
+package org.apache.fineract.portfolio.loanorigination.handler;
 
-import java.util.List;
-import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
+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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@CommandType(entity = "LOAN_ORIGINATOR", action = "UPDATE")
+@RequiredArgsConstructor
 @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
-public class LoanOriginatorReadPlatformServiceImpl implements 
LoanOriginatorReadPlatformService {
-
-    private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented 
yet";
-
-    @Override
-    public List<LoanOriginatorData> retrieveAll() {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
+public class UpdateLoanOriginatorCommandHandler implements 
NewCommandSourceHandler {
 
-    @Override
-    public LoanOriginatorData retrieveById(Long id) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
+    private final LoanOriginatorWritePlatformService writePlatformService;
 
+    @Transactional
     @Override
-    public Long resolveIdByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.writePlatformService.update(command.entityId(), command);
     }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java
similarity index 54%
copy from 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
copy to 
fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java
index dd9f84c3c6..c891c6e0cb 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java
@@ -16,36 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanorigination.service;
+package org.apache.fineract.portfolio.loanorigination.mapper;
 
 import java.util.List;
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
 import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Service;
 
-@Service
+@Mapper(config = MapstructMapperConfig.class)
 @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
-public class LoanOriginatorReadPlatformServiceImpl implements 
LoanOriginatorReadPlatformService {
+public interface LoanOriginatorMapper {
 
-    private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented 
yet";
+    @Mapping(target = "originatorTypeId", source = "originatorType.id")
+    @Mapping(target = "channelTypeId", source = "channelType.id")
+    @Mapping(target = "externalId", source = "externalId")
+    @Mapping(target = "status", expression = 
"java(entity.getStatus().getValue())")
+    LoanOriginatorData toData(LoanOriginator entity);
 
-    @Override
-    public List<LoanOriginatorData> retrieveAll() {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveById(Long id) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public LoanOriginatorData retrieveByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
-
-    @Override
-    public Long resolveIdByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
-    }
+    List<LoanOriginatorData> toDataList(List<LoanOriginator> entities);
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/serialization/LoanOriginatorDataValidator.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/serialization/LoanOriginatorDataValidator.java
new file mode 100644
index 0000000000..dba7eeb67a
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/serialization/LoanOriginatorDataValidator.java
@@ -0,0 +1,121 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanorigination.serialization;
+
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CREATE_REQUEST_PARAMS;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.EXTERNAL_ID_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.NAME_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.RESOURCE_NAME;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.STATUS_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.UPDATE_REQUEST_PARAMS;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
+import 
org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorInvalidStatusException;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
+public class LoanOriginatorDataValidator {
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    public void validateForCreate(final String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
CREATE_REQUEST_PARAMS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource(RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String externalId = 
this.fromApiJsonHelper.extractStringNamed(EXTERNAL_ID_PARAM, element);
+        
baseDataValidator.reset().parameter(EXTERNAL_ID_PARAM).value(externalId).notBlank().notExceedingLengthOf(100);
+
+        final String name = 
this.fromApiJsonHelper.extractStringNamed(NAME_PARAM, element);
+        
baseDataValidator.reset().parameter(NAME_PARAM).value(name).ignoreIfNull().notExceedingLengthOf(255);
+
+        if (this.fromApiJsonHelper.parameterExists(STATUS_PARAM, element)) {
+            final String status = 
this.fromApiJsonHelper.extractStringNamed(STATUS_PARAM, element);
+            
baseDataValidator.reset().parameter(STATUS_PARAM).value(status).notBlank();
+            validateStatus(status);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
UPDATE_REQUEST_PARAMS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource(RESOURCE_NAME);
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = 
this.fromApiJsonHelper.extractStringNamed(NAME_PARAM, element);
+        
baseDataValidator.reset().parameter(NAME_PARAM).value(name).ignoreIfNull().notExceedingLengthOf(255);
+
+        if (this.fromApiJsonHelper.parameterExists(STATUS_PARAM, element)) {
+            final String status = 
this.fromApiJsonHelper.extractStringNamed(STATUS_PARAM, element);
+            
baseDataValidator.reset().parameter(STATUS_PARAM).value(status).notBlank();
+            validateStatus(status);
+        }
+
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void validateStatus(final String status) {
+        if (status != null) {
+            try {
+                LoanOriginatorStatus.fromString(status);
+            } catch (IllegalArgumentException e) {
+                throw new LoanOriginatorInvalidStatusException(status, e);
+            }
+        }
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final 
List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+}
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
index dd9f84c3c6..9913858760 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformServiceImpl.java
@@ -19,33 +19,50 @@
 package org.apache.fineract.portfolio.loanorigination.service;
 
 import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository;
+import 
org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
+import 
org.apache.fineract.portfolio.loanorigination.mapper.LoanOriginatorMapper;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
 @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
 public class LoanOriginatorReadPlatformServiceImpl implements 
LoanOriginatorReadPlatformService {
 
-    private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented 
yet";
+    private final LoanOriginatorRepository loanOriginatorRepository;
+    private final LoanOriginatorMapper loanOriginatorMapper;
 
     @Override
     public List<LoanOriginatorData> retrieveAll() {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+        final List<LoanOriginator> originators = 
this.loanOriginatorRepository.findAllWithCodeValues();
+        return this.loanOriginatorMapper.toDataList(originators);
     }
 
     @Override
-    public LoanOriginatorData retrieveById(Long id) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public LoanOriginatorData retrieveById(final Long id) {
+        final LoanOriginator originator = 
this.loanOriginatorRepository.findByIdWithCodeValues(id)
+                .orElseThrow(() -> new LoanOriginatorNotFoundException(id));
+        return this.loanOriginatorMapper.toData(originator);
     }
 
     @Override
-    public LoanOriginatorData retrieveByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public LoanOriginatorData retrieveByExternalId(final String externalId) {
+        final LoanOriginator originator = 
this.loanOriginatorRepository.findByExternalIdWithCodeValues(new 
ExternalId(externalId))
+                .orElseThrow(() -> new 
LoanOriginatorNotFoundException(externalId));
+        return this.loanOriginatorMapper.toData(originator);
     }
 
     @Override
-    public Long resolveIdByExternalId(String externalId) {
-        throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
+    public Long resolveIdByExternalId(final String externalId) {
+        final LoanOriginator originator = 
this.loanOriginatorRepository.findByExternalId(new ExternalId(externalId))
+                .orElseThrow(() -> new 
LoanOriginatorNotFoundException(externalId));
+        return originator.getId();
     }
 }
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorWritePlatformServiceImpl.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorWritePlatformServiceImpl.java
new file mode 100644
index 0000000000..04801ea596
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorWritePlatformServiceImpl.java
@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanorigination.service;
+
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CHANNEL_TYPE_CODE_NAME;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CHANNEL_TYPE_ID_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.EXTERNAL_ID_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.NAME_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_CODE_NAME;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_ID_PARAM;
+import static 
org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.STATUS_PARAM;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import 
org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
+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.domain.ExternalId;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
+import 
org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorCannotBeDeletedException;
+import 
org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorDuplicateExternalIdException;
+import 
org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
+import 
org.apache.fineract.portfolio.loanorigination.serialization.LoanOriginatorDataValidator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
+public class LoanOriginatorWritePlatformServiceImpl implements 
LoanOriginatorWritePlatformService {
+
+    private final LoanOriginatorRepository loanOriginatorRepository;
+    private final LoanOriginatorMappingRepository 
loanOriginatorMappingRepository;
+    private final LoanOriginatorDataValidator loanOriginatorDataValidator;
+    private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
+
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+        this.loanOriginatorDataValidator.validateForCreate(command.json());
+
+        final String externalIdValue = 
command.stringValueOfParameterNamed(EXTERNAL_ID_PARAM);
+        final ExternalId externalId = new ExternalId(externalIdValue);
+
+        if (this.loanOriginatorRepository.existsByExternalId(externalId)) {
+            throw new 
LoanOriginatorDuplicateExternalIdException(externalIdValue);
+        }
+
+        final String name = command.stringValueOfParameterNamed(NAME_PARAM);
+
+        final String statusValue = 
command.stringValueOfParameterNamed(STATUS_PARAM);
+        final LoanOriginatorStatus status = (statusValue != null && 
!statusValue.isEmpty()) ? LoanOriginatorStatus.fromString(statusValue)
+                : LoanOriginatorStatus.ACTIVE;
+
+        final CodeValue originatorType = resolveCodeValue(command, 
ORIGINATOR_TYPE_ID_PARAM, ORIGINATOR_TYPE_CODE_NAME);
+        final CodeValue channelType = resolveCodeValue(command, 
CHANNEL_TYPE_ID_PARAM, CHANNEL_TYPE_CODE_NAME);
+
+        final LoanOriginator originator = LoanOriginator.create(externalId, 
name, status, originatorType, channelType);
+        this.loanOriginatorRepository.saveAndFlush(originator);
+
+        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(originator.getId())
+                .withEntityExternalId(externalId).build();
+    }
+
+    @Override
+    public CommandProcessingResult update(final Long id, final JsonCommand 
command) {
+        this.loanOriginatorDataValidator.validateForUpdate(command.json());
+
+        final LoanOriginator originator = 
this.loanOriginatorRepository.findById(id)
+                .orElseThrow(() -> new LoanOriginatorNotFoundException(id));
+
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        if (command.isChangeInStringParameterNamed(NAME_PARAM, 
originator.getName())) {
+            final String newName = 
command.stringValueOfParameterNamed(NAME_PARAM);
+            originator.setName(newName);
+            changes.put(NAME_PARAM, newName);
+        }
+
+        if (command.isChangeInStringParameterNamed(STATUS_PARAM, 
originator.getStatus().getValue())) {
+            final String newStatusValue = 
command.stringValueOfParameterNamed(STATUS_PARAM);
+            final LoanOriginatorStatus newStatus = 
LoanOriginatorStatus.fromString(newStatusValue);
+            originator.setStatus(newStatus);
+            changes.put(STATUS_PARAM, newStatusValue);
+        }
+
+        final Long currentOriginatorTypeId = originator.getOriginatorType() != 
null ? originator.getOriginatorType().getId() : null;
+        if (command.isChangeInLongParameterNamed(ORIGINATOR_TYPE_ID_PARAM, 
currentOriginatorTypeId)) {
+            final CodeValue newOriginatorType = resolveCodeValue(command, 
ORIGINATOR_TYPE_ID_PARAM, ORIGINATOR_TYPE_CODE_NAME);
+            originator.setOriginatorType(newOriginatorType);
+            changes.put(ORIGINATOR_TYPE_ID_PARAM, newOriginatorType != null ? 
newOriginatorType.getId() : null);
+        }
+
+        final Long currentChannelTypeId = originator.getChannelType() != null 
? originator.getChannelType().getId() : null;
+        if (command.isChangeInLongParameterNamed(CHANNEL_TYPE_ID_PARAM, 
currentChannelTypeId)) {
+            final CodeValue newChannelType = resolveCodeValue(command, 
CHANNEL_TYPE_ID_PARAM, CHANNEL_TYPE_CODE_NAME);
+            originator.setChannelType(newChannelType);
+            changes.put(CHANNEL_TYPE_ID_PARAM, newChannelType != null ? 
newChannelType.getId() : null);
+        }
+
+        if (!changes.isEmpty()) {
+            this.loanOriginatorRepository.saveAndFlush(originator);
+        }
+
+        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(originator.getId())
+                
.withEntityExternalId(originator.getExternalId()).with(changes).build();
+    }
+
+    @Override
+    public CommandProcessingResult delete(final Long id) {
+        final LoanOriginator originator = 
this.loanOriginatorRepository.findById(id)
+                .orElseThrow(() -> new LoanOriginatorNotFoundException(id));
+
+        if (this.loanOriginatorMappingRepository.existsByOriginatorId(id)) {
+            throw new LoanOriginatorCannotBeDeletedException(id);
+        }
+
+        final ExternalId externalId = originator.getExternalId();
+        this.loanOriginatorRepository.delete(originator);
+
+        return new 
CommandProcessingResultBuilder().withEntityId(id).withEntityExternalId(externalId).build();
+    }
+
+    private CodeValue resolveCodeValue(final JsonCommand command, final String 
paramName, final String codeName) {
+        final Long codeValueId = command.longValueOfParameterNamed(paramName);
+        if (codeValueId == null) {
+            return null;
+        }
+        return 
this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(codeName,
 codeValueId);
+    }
+}
diff --git 
a/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/module-changelog-master.xml
 
b/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/module-changelog-master.xml
index 6bc107ecab..9c8f305f09 100644
--- 
a/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/module-changelog-master.xml
+++ 
b/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/module-changelog-master.xml
@@ -23,4 +23,5 @@
   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_permissions.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/parts/0002_permissions.xml
 
b/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/parts/0002_permissions.xml
new file mode 100644
index 0000000000..1d95b6c437
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/resources/db/changelog/tenant/module/loanorigination/parts/0002_permissions.xml
@@ -0,0 +1,78 @@
+<?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="loan-origination-perm-001" author="fineract">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission 
WHERE code = 'CREATE_LOAN_ORIGINATOR'</sqlCheck>
+        </preConditions>
+        <insert tableName="m_permission">
+            <column name="grouping" value="portfolio"/>
+            <column name="code" value="CREATE_LOAN_ORIGINATOR"/>
+            <column name="entity_name" value="LOAN_ORIGINATOR"/>
+            <column name="action_name" value="CREATE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+
+    <changeSet id="loan-origination-perm-002" author="fineract">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission 
WHERE code = 'READ_LOAN_ORIGINATOR'</sqlCheck>
+        </preConditions>
+        <insert tableName="m_permission">
+            <column name="grouping" value="portfolio"/>
+            <column name="code" value="READ_LOAN_ORIGINATOR"/>
+            <column name="entity_name" value="LOAN_ORIGINATOR"/>
+            <column name="action_name" value="READ"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+
+    <changeSet id="loan-origination-perm-003" author="fineract">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission 
WHERE code = 'UPDATE_LOAN_ORIGINATOR'</sqlCheck>
+        </preConditions>
+        <insert tableName="m_permission">
+            <column name="grouping" value="portfolio"/>
+            <column name="code" value="UPDATE_LOAN_ORIGINATOR"/>
+            <column name="entity_name" value="LOAN_ORIGINATOR"/>
+            <column name="action_name" value="UPDATE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+
+    <changeSet id="loan-origination-perm-004" author="fineract">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission 
WHERE code = 'DELETE_LOAN_ORIGINATOR'</sqlCheck>
+        </preConditions>
+        <insert tableName="m_permission">
+            <column name="grouping" value="portfolio"/>
+            <column name="code" value="DELETE_LOAN_ORIGINATOR"/>
+            <column name="entity_name" value="LOAN_ORIGINATOR"/>
+            <column name="action_name" value="DELETE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+
+</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/application.properties 
b/fineract-provider/src/main/resources/application.properties
index 93130efaeb..77c4ef0daf 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -212,7 +212,7 @@ 
fineract.sampling.resetPeriodSec=${FINERACT_SAMPLING_RESET_PERIOD_IN_SEC:60}
 #Modules
 
fineract.module.self-service.enabled=${FINERACT_MODULE_SELF_SERVICE_ENABLED:false}
 fineract.module.investor.enabled=${FINERACT_MODULE_INVESTOR_ENABLED:true}
-fineract.module.loan-origination.enabled=${FINERACT_MODULE_LOAN_ORIGINATION_ENABLED:false}
+fineract.module.loan-origination.enabled=${FINERACT_MODULE_LOAN_ORIGINATION_ENABLED:true}
 
 fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true}
 fineract.client-connect-timeout=${FINERACT_CLIENT_CONNECT_TIMEOUT:30}
diff --git a/fineract-provider/src/test/resources/application-test.properties 
b/fineract-provider/src/test/resources/application-test.properties
index e5142d8468..d708c5b610 100644
--- a/fineract-provider/src/test/resources/application-test.properties
+++ b/fineract-provider/src/test/resources/application-test.properties
@@ -125,8 +125,7 @@ fineract.sampling.sampledClasses=
 
 fineract.module.investor.enabled=true
 fineract.module.self-service.enabled=true
-# TODO: activate this module when the implementation is ready
-fineract.module.loan-origination.enabled=false
+fineract.module.loan-origination.enabled=true
 
 # sql validation
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanOriginatorApiTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanOriginatorApiTest.java
new file mode 100644
index 0000000000..576936b9ab
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanOriginatorApiTest.java
@@ -0,0 +1,280 @@
+/**
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanOriginatorHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+@Order(1)
+public class LoanOriginatorApiTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification responseSpec400;
+    private ResponseSpecification responseSpec403;
+    private ResponseSpecification responseSpec404;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.responseSpec400 = new 
ResponseSpecBuilder().expectStatusCode(400).build();
+        this.responseSpec403 = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        this.responseSpec404 = new 
ResponseSpecBuilder().expectStatusCode(404).build();
+    }
+
+    // ==================== CRUD LIFECYCLE TESTS ====================
+
+    @Test
+    public void testCreateOriginatorWithMinimalData() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+
+        // Create with only externalId
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        assertNotNull(originatorId, "Originator ID should not be null");
+
+        // Verify created data
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
+
+        assertEquals(externalId, originator.get("externalId"));
+        assertEquals("ACTIVE", originator.get("status"), "Default status 
should be ACTIVE");
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testCreateOriginatorWithAllFields() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final String name = "Test Originator";
+        final String status = "PENDING";
+
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId, 
name, status, null, null);
+
+        assertNotNull(originatorId);
+
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
+
+        assertEquals(externalId, originator.get("externalId"));
+        assertEquals(name, originator.get("name"));
+        assertEquals(status, originator.get("status"));
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testRetrieveOriginatorById() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
+
+        assertNotNull(originator);
+        assertEquals(originatorId, originator.get("id"));
+        assertEquals(externalId, originator.get("externalId"));
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testRetrieveOriginatorByExternalId() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec, 
externalId);
+
+        assertNotNull(originator);
+        assertEquals(originatorId, originator.get("id"));
+        assertEquals(externalId, originator.get("externalId"));
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testRetrieveAllOriginators() {
+        final String externalId1 = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final String externalId2 = 
LoanOriginatorHelper.generateUniqueExternalId();
+
+        final Integer originatorId1 = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId1);
+        final Integer originatorId2 = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId2);
+
+        final List<HashMap<String, Object>> originators = 
LoanOriginatorHelper.getAllOriginators(requestSpec, responseSpec);
+
+        assertNotNull(originators);
+        assertTrue(originators.size() >= 2, "Should have at least 2 
originators");
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId1);
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId2);
+    }
+
+    @Test
+    public void testUpdateOriginatorPartially() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId, 
"Original Name", "ACTIVE",
+                null, null);
+
+        // Update only the name
+        final HashMap<String, Object> updateResult = 
LoanOriginatorHelper.updateOriginator(requestSpec, responseSpec, originatorId,
+                "Updated Name", null, null, null);
+
+        assertNotNull(updateResult);
+        assertTrue(updateResult.containsKey("changes"), "Response should 
contain changes");
+
+        // Verify update
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
+        assertEquals("Updated Name", originator.get("name"));
+        assertEquals("ACTIVE", originator.get("status"), "Status should remain 
unchanged");
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testUpdateOriginatorByExternalId() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, 
externalId, "Original Name", "ACTIVE", null, null);
+
+        // Update by external ID
+        final HashMap<String, Object> updateResult = 
LoanOriginatorHelper.updateOriginatorByExternalId(requestSpec, responseSpec,
+                externalId, "Updated via ExternalId", null, null, null);
+
+        assertNotNull(updateResult);
+
+        // Verify update
+        final HashMap<String, Object> originator = 
LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec, 
externalId);
+        assertEquals("Updated via ExternalId", originator.get("name"));
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginatorByExternalId(requestSpec, 
responseSpec, externalId);
+    }
+
+    @Test
+    public void testDeleteOriginator() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        // Delete
+        final Integer deletedId = 
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
+        assertEquals(originatorId, deletedId);
+
+        // Verify deleted - should return 404
+        LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec404, 
originatorId);
+    }
+
+    @Test
+    public void testDeleteOriginatorByExternalId() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        // Delete by external ID
+        final Integer deletedId = 
LoanOriginatorHelper.deleteOriginatorByExternalId(requestSpec, responseSpec, 
externalId);
+        assertEquals(originatorId, deletedId);
+
+        // Verify deleted - should return 404
+        LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, 
responseSpec404, externalId);
+    }
+
+    // ==================== VALIDATION ERROR TESTS ====================
+
+    @Test
+    public void testCreateOriginatorWithMissingExternalId() {
+        final String invalidJson = "{ \"name\": \"Test\" }";
+
+        LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, 
responseSpec400, invalidJson);
+    }
+
+    @Test
+    public void testCreateOriginatorWithDuplicateExternalId() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+
+        // Create first originator
+        final Integer originatorId = 
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
+
+        // Attempt to create duplicate - should return 403
+        final String duplicateJson = "{ \"externalId\": \"" + externalId + "\" 
}";
+        LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, 
responseSpec403, duplicateJson);
+
+        // Cleanup
+        LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, 
originatorId);
+    }
+
+    @Test
+    public void testCreateOriginatorWithInvalidStatus() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        final String invalidJson = "{ \"externalId\": \"" + externalId + "\", 
\"status\": \"INVALID\" }";
+
+        LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, 
responseSpec403, invalidJson);
+    }
+
+    @Test
+    public void testGetOriginatorByNonExistentId() {
+        LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec404, 
999999);
+    }
+
+    @Test
+    public void testGetOriginatorByNonExistentExternalId() {
+        LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, 
responseSpec404, "NON-EXISTENT-EXTERNAL-ID");
+    }
+
+    @Test
+    public void testUpdateNonExistentOriginator() {
+        final String json = "{ \"name\": \"Updated\" }";
+        Utils.performServerPut(requestSpec, responseSpec404, 
"/fineract-provider/api/v1/loan-originators/999999?" + Utils.TENANT_IDENTIFIER,
+                json, "");
+    }
+
+    @Test
+    public void testDeleteNonExistentOriginator() {
+        Utils.performServerDelete(requestSpec, responseSpec404,
+                "/fineract-provider/api/v1/loan-originators/999999?" + 
Utils.TENANT_IDENTIFIER, "");
+    }
+
+    // ==================== CODE VALUE VALIDATION TESTS ====================
+
+    @Test
+    public void testCreateOriginatorWithInvalidCodeValueId() {
+        final String externalId = 
LoanOriginatorHelper.generateUniqueExternalId();
+        // Use an ID that doesn't exist
+        final String json = "{ \"externalId\": \"" + externalId + "\", 
\"originatorTypeId\": 999999 }";
+
+        LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, 
responseSpec404, json);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java
new file mode 100644
index 0000000000..020f3e7a45
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java
@@ -0,0 +1,108 @@
+/**
+ * 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.client.feign.helpers;
+
+import static org.apache.fineract.client.feign.util.FeignCalls.fail;
+import static org.apache.fineract.client.feign.util.FeignCalls.ok;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.fineract.client.feign.FineractFeignClient;
+import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
+import org.apache.fineract.client.models.GetLoanOriginatorsResponse;
+import org.apache.fineract.client.models.PostLoanOriginatorsRequest;
+import org.apache.fineract.client.models.PostLoanOriginatorsResponse;
+import org.apache.fineract.client.models.PutLoanOriginatorsRequest;
+import org.apache.fineract.client.models.PutLoanOriginatorsResponse;
+
+public class FeignLoanOriginatorHelper {
+
+    private final FineractFeignClient fineractClient;
+
+    public FeignLoanOriginatorHelper(FineractFeignClient fineractClient) {
+        this.fineractClient = fineractClient;
+    }
+
+    public Long createOriginator(String externalId) {
+        return createOriginator(new 
PostLoanOriginatorsRequest().externalId(externalId).name(externalId));
+    }
+
+    public Long createOriginator(String externalId, String name, String 
status) {
+        return createOriginator(new 
PostLoanOriginatorsRequest().externalId(externalId).name(name).status(status));
+    }
+
+    public Long createOriginator(PostLoanOriginatorsRequest request) {
+        PostLoanOriginatorsResponse response = ok(() -> 
fineractClient.loanOriginators().create11(request));
+        return response.getResourceId();
+    }
+
+    public CallFailedRuntimeException 
createOriginatorExpectingError(PostLoanOriginatorsRequest request) {
+        return fail(() -> fineractClient.loanOriginators().create11(request));
+    }
+
+    public List<GetLoanOriginatorsResponse> getAllOriginators() {
+        return ok(() -> fineractClient.loanOriginators().retrieveAll28());
+    }
+
+    public GetLoanOriginatorsResponse getOriginatorById(Long originatorId) {
+        return ok(() -> 
fineractClient.loanOriginators().retrieveOne18(originatorId));
+    }
+
+    public CallFailedRuntimeException getOriginatorByIdExpectingError(Long 
originatorId) {
+        return fail(() -> 
fineractClient.loanOriginators().retrieveOne18(originatorId));
+    }
+
+    public GetLoanOriginatorsResponse getOriginatorByExternalId(String 
externalId) {
+        return ok(() -> 
fineractClient.loanOriginators().retrieveByExternalId(externalId));
+    }
+
+    public CallFailedRuntimeException 
getOriginatorByExternalIdExpectingError(String externalId) {
+        return fail(() -> 
fineractClient.loanOriginators().retrieveByExternalId(externalId));
+    }
+
+    public PutLoanOriginatorsResponse updateOriginator(Long originatorId, 
PutLoanOriginatorsRequest request) {
+        return ok(() -> 
fineractClient.loanOriginators().update16(originatorId, request));
+    }
+
+    public PutLoanOriginatorsResponse updateOriginatorByExternalId(String 
externalId, PutLoanOriginatorsRequest request) {
+        return ok(() -> 
fineractClient.loanOriginators().updateByExternalId(externalId, request));
+    }
+
+    public CallFailedRuntimeException updateOriginatorExpectingError(Long 
originatorId, PutLoanOriginatorsRequest request) {
+        return fail(() -> 
fineractClient.loanOriginators().update16(originatorId, request));
+    }
+
+    public Long deleteOriginator(Long originatorId) {
+        var response = ok(() -> 
fineractClient.loanOriginators().delete14(originatorId));
+        return response.getResourceId();
+    }
+
+    public Long deleteOriginatorByExternalId(String externalId) {
+        var response = ok(() -> 
fineractClient.loanOriginators().deleteByExternalId(externalId));
+        return response.getResourceId();
+    }
+
+    public CallFailedRuntimeException deleteOriginatorExpectingError(Long 
originatorId) {
+        return fail(() -> 
fineractClient.loanOriginators().delete14(originatorId));
+    }
+
+    public static String generateUniqueExternalId() {
+        return "EXT-" + UUID.randomUUID().toString().substring(0, 8);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanOriginatorApiTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanOriginatorApiTest.java
new file mode 100644
index 0000000000..e150137277
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanOriginatorApiTest.java
@@ -0,0 +1,248 @@
+/**
+ * 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.client.feign.tests;
+
+import java.util.List;
+import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
+import org.apache.fineract.client.models.GetLoanOriginatorsResponse;
+import org.apache.fineract.client.models.PostLoanOriginatorsRequest;
+import org.apache.fineract.client.models.PutLoanOriginatorsRequest;
+import org.apache.fineract.client.models.PutLoanOriginatorsResponse;
+import org.apache.fineract.integrationtests.client.FeignIntegrationTest;
+import 
org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanOriginatorHelper;
+import org.apache.fineract.integrationtests.common.FineractFeignClientHelper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+@Order(1)
+public class FeignLoanOriginatorApiTest extends FeignIntegrationTest {
+
+    private static FeignLoanOriginatorHelper originatorHelper;
+
+    @BeforeAll
+    public static void setup() {
+        originatorHelper = new 
FeignLoanOriginatorHelper(FineractFeignClientHelper.getFineractFeignClient());
+    }
+
+    @Test
+    public void testCreateOriginatorWithMinimalData() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        assertThat(originatorId).isNotNull();
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorById(originatorId);
+
+        assertThat(originator.getExternalId()).isEqualTo(externalId);
+        assertThat(originator.getStatus()).isEqualTo("ACTIVE");
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testCreateOriginatorWithAllFields() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final String name = "Test Originator";
+        final String status = "PENDING";
+
+        final Long originatorId = 
originatorHelper.createOriginator(externalId, name, status);
+
+        assertThat(originatorId).isNotNull();
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorById(originatorId);
+
+        assertThat(originator.getExternalId()).isEqualTo(externalId);
+        assertThat(originator.getName()).isEqualTo(name);
+        assertThat(originator.getStatus()).isEqualTo(status);
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testRetrieveOriginatorById() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorById(originatorId);
+
+        assertThat(originator).isNotNull();
+        assertThat(originator.getId()).isEqualTo(originatorId);
+        assertThat(originator.getExternalId()).isEqualTo(externalId);
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testRetrieveOriginatorByExternalId() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorByExternalId(externalId);
+
+        assertThat(originator).isNotNull();
+        assertThat(originator.getId()).isEqualTo(originatorId);
+        assertThat(originator.getExternalId()).isEqualTo(externalId);
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testRetrieveAllOriginators() {
+        final String externalId1 = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final String externalId2 = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+
+        final Long originatorId1 = 
originatorHelper.createOriginator(externalId1);
+        final Long originatorId2 = 
originatorHelper.createOriginator(externalId2);
+
+        final List<GetLoanOriginatorsResponse> originators = 
originatorHelper.getAllOriginators();
+
+        assertThat(originators).isNotNull();
+        assertThat(originators).hasSizeGreaterThanOrEqualTo(2);
+
+        originatorHelper.deleteOriginator(originatorId1);
+        originatorHelper.deleteOriginator(originatorId2);
+    }
+
+    @Test
+    public void testUpdateOriginatorPartially() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final Long originatorId = 
originatorHelper.createOriginator(externalId, "Original Name", "ACTIVE");
+
+        final PutLoanOriginatorsResponse updateResult = 
originatorHelper.updateOriginator(originatorId,
+                new PutLoanOriginatorsRequest().name("Updated Name"));
+
+        assertThat(updateResult).isNotNull();
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorById(originatorId);
+        assertThat(originator.getName()).isEqualTo("Updated Name");
+        assertThat(originator.getStatus()).isEqualTo("ACTIVE");
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testUpdateOriginatorByExternalId() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        originatorHelper.createOriginator(externalId, "Original Name", 
"ACTIVE");
+
+        final PutLoanOriginatorsResponse updateResult = 
originatorHelper.updateOriginatorByExternalId(externalId,
+                new PutLoanOriginatorsRequest().name("Updated via 
ExternalId"));
+
+        assertThat(updateResult).isNotNull();
+
+        final GetLoanOriginatorsResponse originator = 
originatorHelper.getOriginatorByExternalId(externalId);
+        assertThat(originator.getName()).isEqualTo("Updated via ExternalId");
+
+        originatorHelper.deleteOriginatorByExternalId(externalId);
+    }
+
+    @Test
+    public void testDeleteOriginator() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        final Long deletedId = originatorHelper.deleteOriginator(originatorId);
+        assertThat(deletedId).isEqualTo(originatorId);
+
+        final CallFailedRuntimeException exception = 
originatorHelper.getOriginatorByIdExpectingError(originatorId);
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testDeleteOriginatorByExternalId() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        final Long deletedId = 
originatorHelper.deleteOriginatorByExternalId(externalId);
+        assertThat(deletedId).isEqualTo(originatorId);
+
+        final CallFailedRuntimeException exception = 
originatorHelper.getOriginatorByExternalIdExpectingError(externalId);
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testCreateOriginatorWithOptionalName() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final PostLoanOriginatorsRequest request = new 
PostLoanOriginatorsRequest().externalId(externalId);
+
+        final Long originatorId = originatorHelper.createOriginator(request);
+        assertThat(originatorId).isNotNull();
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testCreateOriginatorWithDuplicateExternalId() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+
+        final Long originatorId = 
originatorHelper.createOriginator(externalId);
+
+        final CallFailedRuntimeException exception = originatorHelper
+                .createOriginatorExpectingError(new 
PostLoanOriginatorsRequest().externalId(externalId).name("Duplicate"));
+        assertThat(exception.getStatus()).isEqualTo(403);
+
+        originatorHelper.deleteOriginator(originatorId);
+    }
+
+    @Test
+    public void testCreateOriginatorWithInvalidStatus() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final PostLoanOriginatorsRequest request = new 
PostLoanOriginatorsRequest().externalId(externalId).name("Test").status("INVALID");
+
+        final CallFailedRuntimeException exception = 
originatorHelper.createOriginatorExpectingError(request);
+        assertThat(exception.getStatus()).isEqualTo(403);
+    }
+
+    @Test
+    public void testGetOriginatorByNonExistentId() {
+        final CallFailedRuntimeException exception = 
originatorHelper.getOriginatorByIdExpectingError(999999L);
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testGetOriginatorByNonExistentExternalId() {
+        final CallFailedRuntimeException exception = 
originatorHelper.getOriginatorByExternalIdExpectingError("NON-EXISTENT-EXTERNAL-ID");
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testUpdateNonExistentOriginator() {
+        final CallFailedRuntimeException exception = 
originatorHelper.updateOriginatorExpectingError(999999L,
+                new PutLoanOriginatorsRequest().name("Updated"));
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testDeleteNonExistentOriginator() {
+        final CallFailedRuntimeException exception = 
originatorHelper.deleteOriginatorExpectingError(999999L);
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+
+    @Test
+    public void testCreateOriginatorWithInvalidCodeValueId() {
+        final String externalId = 
FeignLoanOriginatorHelper.generateUniqueExternalId();
+        final PostLoanOriginatorsRequest request = new 
PostLoanOriginatorsRequest().externalId(externalId).name("Test")
+                .originatorTypeId(999999L);
+
+        final CallFailedRuntimeException exception = 
originatorHelper.createOriginatorExpectingError(request);
+        assertThat(exception.getStatus()).isEqualTo(404);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanOriginatorHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanOriginatorHelper.java
new file mode 100644
index 0000000000..716fc72258
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanOriginatorHelper.java
@@ -0,0 +1,166 @@
+/**
+ * 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.common.loans;
+
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+import org.apache.fineract.integrationtests.common.Utils;
+
+public final class LoanOriginatorHelper {
+
+    private static final String LOAN_ORIGINATOR_URL = 
"/fineract-provider/api/v1/loan-originators";
+
+    private LoanOriginatorHelper() {}
+
+    // ========== CREATE ==========
+
+    public static Integer createOriginator(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
+            final String externalId) {
+        return createOriginator(requestSpec, responseSpec, externalId, null, 
null, null, null);
+    }
+
+    public static Integer createOriginator(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
+            final String externalId, final String name, final String status, 
final Long originatorTypeId, final Long channelTypeId) {
+        final String json = buildCreateJson(externalId, name, status, 
originatorTypeId, channelTypeId);
+        return Utils.performServerPost(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "resourceId");
+    }
+
+    public static HashMap<String, Object> 
createOriginatorWithFullResponse(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String externalId) 
{
+        final String json = buildCreateJson(externalId, null, null, null, 
null);
+        return Utils.performServerPost(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "");
+    }
+
+    public static List<HashMap<String, Object>> 
createOriginatorExpectingError(final RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String json) {
+        return Utils.performServerPost(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "errors");
+    }
+
+    // ========== READ ==========
+
+    public static HashMap<String, Object> getOriginatorById(final 
RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer 
originatorId) {
+        return Utils.performServerGet(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + Utils.TENANT_IDENTIFIER,
+                "");
+    }
+
+    public static HashMap<String, Object> getOriginatorByExternalId(final 
RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String externalId) 
{
+        return Utils.performServerGet(requestSpec, responseSpec,
+                LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + 
Utils.TENANT_IDENTIFIER, "");
+    }
+
+    public static List<HashMap<String, Object>> getAllOriginators(final 
RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec) {
+        return Utils.performServerGet(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, "");
+    }
+
+    // ========== UPDATE ==========
+
+    public static HashMap<String, Object> updateOriginator(final 
RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer originatorId, final String name, final String 
status, final Long originatorTypeId, final Long channelTypeId) {
+        final String json = buildUpdateJson(name, status, originatorTypeId, 
channelTypeId);
+        return Utils.performServerPut(requestSpec, responseSpec, 
LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + Utils.TENANT_IDENTIFIER,
+                json, "");
+    }
+
+    public static HashMap<String, Object> updateOriginatorByExternalId(final 
RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final String externalId, 
final String name, final String status,
+            final Long originatorTypeId, final Long channelTypeId) {
+        final String json = buildUpdateJson(name, status, originatorTypeId, 
channelTypeId);
+        return Utils.performServerPut(requestSpec, responseSpec,
+                LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + 
Utils.TENANT_IDENTIFIER, json, "");
+    }
+
+    // ========== DELETE ==========
+
+    public static Integer deleteOriginator(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec,
+            final Integer originatorId) {
+        return Utils.performServerDelete(requestSpec, responseSpec,
+                LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + 
Utils.TENANT_IDENTIFIER, "resourceId");
+    }
+
+    public static Integer deleteOriginatorByExternalId(final 
RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String externalId) {
+        return Utils.performServerDelete(requestSpec, responseSpec,
+                LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + 
Utils.TENANT_IDENTIFIER, "resourceId");
+    }
+
+    // ========== JSON BUILDERS ==========
+
+    private static String buildCreateJson(final String externalId, final 
String name, final String status, final Long originatorTypeId,
+            final Long channelTypeId) {
+        final StringBuilder json = new StringBuilder("{");
+        json.append("\"externalId\": \"").append(externalId).append("\"");
+        if (name != null) {
+            json.append(", \"name\": \"").append(name).append("\"");
+        }
+        if (status != null) {
+            json.append(", \"status\": \"").append(status).append("\"");
+        }
+        if (originatorTypeId != null) {
+            json.append(", \"originatorTypeId\": ").append(originatorTypeId);
+        }
+        if (channelTypeId != null) {
+            json.append(", \"channelTypeId\": ").append(channelTypeId);
+        }
+        json.append("}");
+        return json.toString();
+    }
+
+    private static String buildUpdateJson(final String name, final String 
status, final Long originatorTypeId, final Long channelTypeId) {
+        final StringBuilder json = new StringBuilder("{");
+        boolean first = true;
+        if (name != null) {
+            json.append("\"name\": \"").append(name).append("\"");
+            first = false;
+        }
+        if (status != null) {
+            if (!first) {
+                json.append(", ");
+            }
+            json.append("\"status\": \"").append(status).append("\"");
+            first = false;
+        }
+        if (originatorTypeId != null) {
+            if (!first) {
+                json.append(", ");
+            }
+            json.append("\"originatorTypeId\": ").append(originatorTypeId);
+            first = false;
+        }
+        if (channelTypeId != null) {
+            if (!first) {
+                json.append(", ");
+            }
+            json.append("\"channelTypeId\": ").append(channelTypeId);
+        }
+        json.append("}");
+        return json.toString();
+    }
+
+    // ========== UTILITIES ==========
+
+    public static String generateUniqueExternalId() {
+        return "EXT-" + UUID.randomUUID().toString().substring(0, 8);
+    }
+}

Reply via email to