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 c57f44d3f0 FINERACT-2418: Add originators details to external business 
events
c57f44d3f0 is described below

commit c57f44d3f0fc266d9b11b11d29a83a9c7b056861
Author: mariiaKraievska <[email protected]>
AuthorDate: Mon Feb 2 15:50:24 2026 +0200

    FINERACT-2418: Add originators details to external business events
---
 fineract-loan-origination/dependencies.gradle      |   4 +
 .../domain/LoanOriginatorMappingRepository.java    |  11 ++
 .../LoanAccountDataV1OriginatorEnricher.java       |  72 ++++++++
 .../enricher/LoanOriginatorAvroMapper.java         |  70 ++++++++
 .../LoanTransactionDataV1OriginatorEnricher.java   |  72 ++++++++
 .../LoanAccountDataV1OriginatorEnricherTest.java   | 198 +++++++++++++++++++++
 .../enricher/LoanOriginatorAvroMapperTest.java     | 122 +++++++++++++
 ...oanTransactionDataV1OriginatorEnricherTest.java | 165 +++++++++++++++++
 8 files changed, 714 insertions(+)

diff --git a/fineract-loan-origination/dependencies.gradle 
b/fineract-loan-origination/dependencies.gradle
index dee9b9db49..8a0d0643c9 100644
--- a/fineract-loan-origination/dependencies.gradle
+++ b/fineract-loan-origination/dependencies.gradle
@@ -25,6 +25,10 @@ dependencies {
     // implementation dependencies are directly used (compiled against) in 
src/main (and src/test)
     //
     implementation(project(path: ':fineract-core'))
+    implementation('org.apache.avro:avro')
+    implementation(
+            project(path: ':fineract-avro-schemas')
+            )
     implementation(project(path: ':fineract-loan'))
 
     implementation(
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorMappingRepository.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorMappingRepository.java
index 257029fc12..c658d37f45 100644
--- 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorMappingRepository.java
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorMappingRepository.java
@@ -22,12 +22,23 @@ import java.util.List;
 import java.util.Optional;
 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 LoanOriginatorMappingRepository
         extends JpaRepository<LoanOriginatorMapping, Long>, 
JpaSpecificationExecutor<LoanOriginatorMapping> {
 
     List<LoanOriginatorMapping> findByLoanId(Long loanId);
 
+    @Query("""
+            SELECT m FROM LoanOriginatorMapping m
+            JOIN FETCH m.originator o
+            LEFT JOIN FETCH o.originatorType
+            LEFT JOIN FETCH o.channelType
+            WHERE m.loanId = :loanId
+            """)
+    List<LoanOriginatorMapping> 
findByLoanIdWithOriginatorDetails(@Param("loanId") Long loanId);
+
     boolean existsByLoanId(Long loanId);
 
     boolean existsByOriginatorId(Long originatorId);
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricher.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricher.java
new file mode 100644
index 0000000000..85d3059fff
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricher.java
@@ -0,0 +1,72 @@
+/**
+ * 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.enricher;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.core.service.DataEnricher;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
+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 LoanAccountDataV1OriginatorEnricher implements 
DataEnricher<LoanAccountDataV1> {
+
+    private final LoanOriginatorMappingRepository 
loanOriginatorMappingRepository;
+    private final LoanOriginatorAvroMapper loanOriginatorAvroMapper;
+
+    @Override
+    public boolean isDataTypeSupported(final Class<LoanAccountDataV1> 
dataType) {
+        return dataType.isAssignableFrom(LoanAccountDataV1.class);
+    }
+
+    @Override
+    public void enrich(final LoanAccountDataV1 data) {
+        if (data == null || data.getId() == null) {
+            return;
+        }
+
+        final List<LoanOriginatorMapping> mappings = 
loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(data.getId());
+        if (mappings == null || mappings.isEmpty()) {
+            return;
+        }
+
+        final List<OriginatorDetailsV1> originators = new ArrayList<>();
+        for (LoanOriginatorMapping mapping : mappings) {
+            final LoanOriginator originator = mapping.getOriginator();
+            if (originator != null) {
+                final OriginatorDetailsV1 originatorDetails = 
loanOriginatorAvroMapper.toAvro(originator);
+                if (originatorDetails != null) {
+                    originators.add(originatorDetails);
+                }
+            }
+        }
+
+        if (!originators.isEmpty()) {
+            data.setOriginators(originators);
+        }
+    }
+}
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapper.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapper.java
new file mode 100644
index 0000000000..0e04ae3ed7
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapper.java
@@ -0,0 +1,70 @@
+/**
+ * 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.enricher;
+
+import org.apache.fineract.avro.generic.v1.CodeValueDataV1;
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", 
havingValue = "true")
+public class LoanOriginatorAvroMapper {
+
+    /**
+     * Converts a LoanOriginator entity to OriginatorDetailsV1 Avro
+     */
+    public OriginatorDetailsV1 toAvro(final LoanOriginator originator) {
+        if (originator == null) {
+            return null;
+        }
+
+        final OriginatorDetailsV1.Builder builder = 
OriginatorDetailsV1.newBuilder();
+
+        builder.setId(originator.getId());
+        builder.setExternalId(originator.getExternalId() != null ? 
originator.getExternalId().getValue() : null);
+        builder.setName(originator.getName());
+        builder.setStatus(originator.getStatus() != null ? 
originator.getStatus().getValue() : null);
+        
builder.setOriginatorType(mapCodeValue(originator.getOriginatorType()));
+        builder.setChannelType(mapCodeValue(originator.getChannelType()));
+
+        return builder.build();
+    }
+
+    /**
+     * Converts a CodeValue entity to CodeValueDataV1 Avro
+     */
+    private CodeValueDataV1 mapCodeValue(CodeValue codeValue) {
+        if (codeValue == null) {
+            return null;
+        }
+
+        CodeValueDataV1.Builder builder = CodeValueDataV1.newBuilder();
+        builder.setId(codeValue.getId());
+        builder.setName(codeValue.getLabel());
+        builder.setPosition(codeValue.getPosition());
+        builder.setDescription(codeValue.getDescription());
+        builder.setActive(codeValue.isActive());
+        builder.setMandatory(codeValue.isMandatory());
+
+        return builder.build();
+    }
+}
diff --git 
a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricher.java
 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricher.java
new file mode 100644
index 0000000000..721932d3ee
--- /dev/null
+++ 
b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricher.java
@@ -0,0 +1,72 @@
+/**
+ * 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.enricher;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.core.service.DataEnricher;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
+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 LoanTransactionDataV1OriginatorEnricher implements 
DataEnricher<LoanTransactionDataV1> {
+
+    private final LoanOriginatorMappingRepository 
loanOriginatorMappingRepository;
+    private final LoanOriginatorAvroMapper loanOriginatorAvroMapper;
+
+    @Override
+    public boolean isDataTypeSupported(final Class<LoanTransactionDataV1> 
dataType) {
+        return dataType.isAssignableFrom(LoanTransactionDataV1.class);
+    }
+
+    @Override
+    public void enrich(final LoanTransactionDataV1 data) {
+        if (data == null || data.getLoanId() == null) {
+            return;
+        }
+
+        final List<LoanOriginatorMapping> mappings = 
loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(data.getLoanId());
+        if (mappings == null || mappings.isEmpty()) {
+            return;
+        }
+
+        final List<OriginatorDetailsV1> originators = new ArrayList<>();
+        for (LoanOriginatorMapping mapping : mappings) {
+            final LoanOriginator originator = mapping.getOriginator();
+            if (originator != null) {
+                final OriginatorDetailsV1 originatorDetails = 
loanOriginatorAvroMapper.toAvro(originator);
+                if (originatorDetails != null) {
+                    originators.add(originatorDetails);
+                }
+            }
+        }
+
+        if (!originators.isEmpty()) {
+            data.setOriginators(originators);
+        }
+    }
+}
diff --git 
a/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricherTest.java
 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricherTest.java
new file mode 100644
index 0000000000..0dee5f95ad
--- /dev/null
+++ 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanAccountDataV1OriginatorEnricherTest.java
@@ -0,0 +1,198 @@
+/**
+ * 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.enricher;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class LoanAccountDataV1OriginatorEnricherTest {
+
+    @Mock
+    private LoanOriginatorMappingRepository loanOriginatorMappingRepository;
+
+    @Mock
+    private LoanOriginatorAvroMapper loanOriginatorAvroMapper;
+
+    @InjectMocks
+    private LoanAccountDataV1OriginatorEnricher enricher;
+
+    private LoanAccountDataV1 loanAccountData;
+    private Long loanId;
+
+    @BeforeEach
+    void setUp() {
+        loanId = 1L;
+        loanAccountData = new LoanAccountDataV1();
+        loanAccountData.setId(loanId);
+    }
+
+    @Test
+    void testIsDataTypeSupported() {
+        assertTrue(enricher.isDataTypeSupported(LoanAccountDataV1.class));
+    }
+
+    @Test
+    void testEnrich_WithOriginators() {
+        // Given
+        final LoanOriginator originator = createTestOriginator(1L, 
"test-originator-1", "Test Originator 1");
+        final LoanOriginatorMapping mapping = 
LoanOriginatorMapping.create(loanId, originator);
+        final List<LoanOriginatorMapping> mappings = List.of(mapping);
+
+        final OriginatorDetailsV1 originatorDetails = 
createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
+
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
+        
when(loanOriginatorAvroMapper.toAvro(originator)).thenReturn(originatorDetails);
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper).toAvro(originator);
+        assertNotNull(loanAccountData.getOriginators());
+        assertEquals(1, loanAccountData.getOriginators().size());
+        assertEquals("test-originator-1", 
loanAccountData.getOriginators().getFirst().getExternalId());
+    }
+
+    @Test
+    void testEnrich_WithMultipleOriginators() {
+        // Given
+        final LoanOriginator originator1 = createTestOriginator(1L, 
"test-originator-1", "Test Originator 1");
+        final LoanOriginator originator2 = createTestOriginator(2L, 
"test-originator-2", "Test Originator 2");
+        final List<LoanOriginatorMapping> mappings = 
List.of(LoanOriginatorMapping.create(loanId, originator1),
+                LoanOriginatorMapping.create(loanId, originator2));
+
+        final OriginatorDetailsV1 details1 = createOriginatorDetailsV1(1L, 
"test-originator-1", "Test Originator 1");
+        final OriginatorDetailsV1 details2 = createOriginatorDetailsV1(2L, 
"test-originator-2", "Test Originator 2");
+
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
+        
when(loanOriginatorAvroMapper.toAvro(originator1)).thenReturn(details1);
+        
when(loanOriginatorAvroMapper.toAvro(originator2)).thenReturn(details2);
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        assertNotNull(loanAccountData.getOriginators());
+        assertEquals(2, loanAccountData.getOriginators().size());
+    }
+
+    @Test
+    void testEnrich_NoOriginators() {
+        // Given
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(Collections.emptyList());
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper, never()).toAvro(any());
+        assertNull(loanAccountData.getOriginators());
+    }
+
+    @Test
+    void testEnrich_NullLoanId() {
+        // Given
+        loanAccountData.setId(null);
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        verify(loanOriginatorMappingRepository, 
never()).findByLoanIdWithOriginatorDetails(any());
+        assertNull(loanAccountData.getOriginators());
+    }
+
+    @Test
+    void testEnrich_NullData() {
+        // When
+        enricher.enrich(null);
+
+        // Then
+        verify(loanOriginatorMappingRepository, 
never()).findByLoanIdWithOriginatorDetails(any());
+    }
+
+    @Test
+    void testEnrich_NullMappings() {
+        // Given
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(null);
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper, never()).toAvro(any());
+        assertNull(loanAccountData.getOriginators());
+    }
+
+    @Test
+    void testEnrich_NullOriginatorInMapping() {
+        // Given
+        final LoanOriginatorMapping mapping = 
mock(LoanOriginatorMapping.class);
+        when(mapping.getOriginator()).thenReturn(null);
+        final List<LoanOriginatorMapping> mappings = List.of(mapping);
+
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
+
+        // When
+        enricher.enrich(loanAccountData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper, never()).toAvro(any());
+        assertNull(loanAccountData.getOriginators());
+    }
+
+    private LoanOriginator createTestOriginator(final Long id, final String 
externalId, final String name) {
+        final LoanOriginator originator = LoanOriginator.create(new 
ExternalId(externalId), name, LoanOriginatorStatus.ACTIVE, null, null);
+        originator.setId(id);
+        return originator;
+    }
+
+    private OriginatorDetailsV1 createOriginatorDetailsV1(final Long id, final 
String externalId, final String name) {
+        return 
OriginatorDetailsV1.newBuilder().setId(id).setExternalId(externalId).setName(name).setStatus("ACTIVE")
+                .setOriginatorType(null).setChannelType(null).build();
+    }
+}
diff --git 
a/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapperTest.java
 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapperTest.java
new file mode 100644
index 0000000000..9ee9bab614
--- /dev/null
+++ 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanOriginatorAvroMapperTest.java
@@ -0,0 +1,122 @@
+/**
+ * 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.enricher;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class LoanOriginatorAvroMapperTest {
+
+    private LoanOriginatorAvroMapper mapper;
+
+    @BeforeEach
+    void setUp() {
+        mapper = new LoanOriginatorAvroMapper();
+    }
+
+    @Test
+    void testToAvro_WithAllFields() {
+        // Given
+        final CodeValue originatorType = createCodeValue(1L, "MERCHANT", 
"Merchant");
+        final CodeValue channelType = createCodeValue(2L, "ONLINE", "Online");
+        final LoanOriginator originator = LoanOriginator.create(new 
ExternalId("test-external-id"), "Test Originator",
+                LoanOriginatorStatus.ACTIVE, originatorType, channelType);
+        originator.setId(100L);
+
+        // When
+        final OriginatorDetailsV1 result = mapper.toAvro(originator);
+
+        // Then
+        assertNotNull(result);
+        assertEquals(100L, result.getId());
+        assertEquals("test-external-id", result.getExternalId());
+        assertEquals("Test Originator", result.getName());
+        assertEquals("ACTIVE", result.getStatus());
+        assertNotNull(result.getOriginatorType());
+        assertEquals(1L, result.getOriginatorType().getId());
+        assertEquals("MERCHANT", result.getOriginatorType().getName());
+        assertNotNull(result.getChannelType());
+        assertEquals(2L, result.getChannelType().getId());
+        assertEquals("ONLINE", result.getChannelType().getName());
+    }
+
+    @Test
+    void testToAvro_WithNullFields() {
+        // Given
+        final LoanOriginator originator = LoanOriginator.create(new 
ExternalId("test-external-id"), null, LoanOriginatorStatus.PENDING,
+                null, null);
+        originator.setId(200L);
+
+        // When
+        final OriginatorDetailsV1 result = mapper.toAvro(originator);
+
+        // Then
+        assertNotNull(result);
+        assertEquals(200L, result.getId());
+        assertEquals("test-external-id", result.getExternalId());
+        assertNull(result.getName());
+        assertEquals("PENDING", result.getStatus());
+        assertNull(result.getOriginatorType());
+        assertNull(result.getChannelType());
+    }
+
+    @Test
+    void testToAvro_NullOriginator() {
+        // When
+        final OriginatorDetailsV1 result = mapper.toAvro(null);
+
+        // Then
+        assertNull(result);
+    }
+
+    @Test
+    void testToAvro_NullExternalId() {
+        // Given
+        final LoanOriginator originator = LoanOriginator.create(null, "Test", 
LoanOriginatorStatus.ACTIVE, null, null);
+        originator.setId(300L);
+
+        // When
+        final OriginatorDetailsV1 result = mapper.toAvro(originator);
+
+        // Then
+        assertNotNull(result);
+        assertEquals(300L, result.getId());
+        assertNull(result.getExternalId());
+    }
+
+    private CodeValue createCodeValue(final Long id, final String label, final 
String description) {
+        final CodeValue codeValue = new CodeValue();
+        codeValue.setId(id);
+        codeValue.setLabel(label);
+        codeValue.setDescription(description);
+        codeValue.setPosition(1);
+        codeValue.setActive(true);
+        codeValue.setMandatory(false);
+        return codeValue;
+    }
+}
diff --git 
a/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricherTest.java
 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricherTest.java
new file mode 100644
index 0000000000..901b84fc32
--- /dev/null
+++ 
b/fineract-loan-origination/src/test/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanTransactionDataV1OriginatorEnricherTest.java
@@ -0,0 +1,165 @@
+/**
+ * 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.enricher;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
+import 
org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class LoanTransactionDataV1OriginatorEnricherTest {
+
+    @Mock
+    private LoanOriginatorMappingRepository loanOriginatorMappingRepository;
+
+    @Mock
+    private LoanOriginatorAvroMapper loanOriginatorAvroMapper;
+
+    @InjectMocks
+    private LoanTransactionDataV1OriginatorEnricher enricher;
+
+    private LoanTransactionDataV1 loanTransactionData;
+    private Long loanId;
+
+    @BeforeEach
+    void setUp() {
+        loanId = 1L;
+        loanTransactionData = new LoanTransactionDataV1();
+        loanTransactionData.setLoanId(loanId);
+    }
+
+    @Test
+    void testIsDataTypeSupported() {
+        assertTrue(enricher.isDataTypeSupported(LoanTransactionDataV1.class));
+    }
+
+    @Test
+    void testEnrich_WithOriginators() {
+        // Given
+        final LoanOriginator originator = createTestOriginator(1L, 
"test-originator-1", "Test Originator 1");
+        final LoanOriginatorMapping mapping = 
LoanOriginatorMapping.create(loanId, originator);
+        final List<LoanOriginatorMapping> mappings = List.of(mapping);
+
+        final OriginatorDetailsV1 originatorDetails = 
createOriginatorDetailsV1(1L, "test-originator-1", "Test Originator 1");
+
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
+        
when(loanOriginatorAvroMapper.toAvro(originator)).thenReturn(originatorDetails);
+
+        // When
+        enricher.enrich(loanTransactionData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper).toAvro(originator);
+        assertNotNull(loanTransactionData.getOriginators());
+        assertEquals(1, loanTransactionData.getOriginators().size());
+        assertEquals("test-originator-1", 
loanTransactionData.getOriginators().getFirst().getExternalId());
+    }
+
+    @Test
+    void testEnrich_WithMultipleOriginators() {
+        // Given
+        final LoanOriginator originator1 = createTestOriginator(1L, 
"test-originator-1", "Test Originator 1");
+        final LoanOriginator originator2 = createTestOriginator(2L, 
"test-originator-2", "Test Originator 2");
+        final List<LoanOriginatorMapping> mappings = 
List.of(LoanOriginatorMapping.create(loanId, originator1),
+                LoanOriginatorMapping.create(loanId, originator2));
+
+        final OriginatorDetailsV1 details1 = createOriginatorDetailsV1(1L, 
"test-originator-1", "Test Originator 1");
+        final OriginatorDetailsV1 details2 = createOriginatorDetailsV1(2L, 
"test-originator-2", "Test Originator 2");
+
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(mappings);
+        
when(loanOriginatorAvroMapper.toAvro(originator1)).thenReturn(details1);
+        
when(loanOriginatorAvroMapper.toAvro(originator2)).thenReturn(details2);
+
+        // When
+        enricher.enrich(loanTransactionData);
+
+        // Then
+        assertNotNull(loanTransactionData.getOriginators());
+        assertEquals(2, loanTransactionData.getOriginators().size());
+    }
+
+    @Test
+    void testEnrich_NoOriginators() {
+        // Given
+        
when(loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(loanId)).thenReturn(Collections.emptyList());
+
+        // When
+        enricher.enrich(loanTransactionData);
+
+        // Then
+        
verify(loanOriginatorMappingRepository).findByLoanIdWithOriginatorDetails(loanId);
+        verify(loanOriginatorAvroMapper, never()).toAvro(any());
+        assertNull(loanTransactionData.getOriginators());
+    }
+
+    @Test
+    void testEnrich_NullLoanId() {
+        // Given
+        loanTransactionData.setLoanId(null);
+
+        // When
+        enricher.enrich(loanTransactionData);
+
+        // Then
+        verify(loanOriginatorMappingRepository, 
never()).findByLoanIdWithOriginatorDetails(any());
+        assertNull(loanTransactionData.getOriginators());
+    }
+
+    @Test
+    void testEnrich_NullData() {
+        // When
+        enricher.enrich(null);
+
+        // Then
+        verify(loanOriginatorMappingRepository, 
never()).findByLoanIdWithOriginatorDetails(any());
+    }
+
+    private LoanOriginator createTestOriginator(final Long id, final String 
externalId, final String name) {
+        final LoanOriginator originator = LoanOriginator.create(new 
ExternalId(externalId), name, LoanOriginatorStatus.ACTIVE, null, null);
+        originator.setId(id);
+        return originator;
+    }
+
+    private OriginatorDetailsV1 createOriginatorDetailsV1(final Long id, final 
String externalId, final String name) {
+        return 
OriginatorDetailsV1.newBuilder().setId(id).setExternalId(externalId).setName(name).setStatus("ACTIVE")
+                .setOriginatorType(null).setChannelType(null).build();
+    }
+}


Reply via email to