From 28f63cc20e4bfb4d79fdc390bfe95b4fddbac33a Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Thu, 5 Feb 2026 08:59:23 -0500 Subject: [PATCH] FINERACT-2418: Loan Originator data with codevalues --- .../data/LoanOriginatorData.java | 5 +-- .../api/LoanOriginatorApiResource.java | 14 ++++++++ .../api/LoanOriginatorApiResourceSwagger.java | 19 ++++++++-- .../data/LoanOriginatorTemplateData.java | 35 +++++++++++++++++++ .../domain/LoanOriginatorStatus.java | 15 ++++++++ .../mapper/LoanOriginatorMapper.java | 7 ++-- .../LoanOriginatorReadPlatformService.java | 3 ++ ...LoanOriginatorReadPlatformServiceImpl.java | 18 ++++++++++ .../helpers/FeignLoanOriginatorHelper.java | 5 +++ .../tests/FeignLoanOriginatorApiTest.java | 31 ++++++++++++++++ 10 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorTemplateData.java diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorData.java index 35fbfc55a..d19e762d4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorData.java @@ -24,6 +24,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; @Data @Builder @@ -38,6 +39,6 @@ public class LoanOriginatorData implements Serializable { private String externalId; private String name; private String status; - private Long originatorTypeId; - private Long channelTypeId; + private CodeValueData originatorType; + private CodeValueData channelType; } 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 7a437755d..76fa0fa41 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 @@ -43,6 +43,7 @@ import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformS import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; +import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorTemplateData; import org.apache.fineract.portfolio.loanorigination.service.LoanOriginatorReadPlatformService; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @@ -83,6 +84,19 @@ public class LoanOriginatorApiResource { return this.loanOriginatorReadPlatformService.retrieveAll(); } + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Get loan originator template data", description = "Retrieves the Loan Originator template data") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorTemplateResponse.class))) + @ApiResponse(responseCode = "403", description = "Insufficient permissions") + public LoanOriginatorTemplateData retrieveLoanOriginatorTemplate() { + this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME); + + return this.loanOriginatorReadPlatformService.retrieveTemplate(); + } + @GET @Path("{originatorId}") @Consumes({ MediaType.APPLICATION_JSON }) 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 index c80b0d281..6c650b8fa 100644 --- 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 @@ -19,6 +19,9 @@ package org.apache.fineract.portfolio.loanorigination.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.Set; +import org.apache.fineract.infrastructure.codes.api.CodeValuesApiResourceSwagger.GetCodeValuesDataResponse; final class LoanOriginatorApiResourceSwagger { @@ -38,9 +41,21 @@ final class LoanOriginatorApiResourceSwagger { @Schema(example = "ACTIVE") public String status; @Schema(example = "1") - public Long originatorTypeId; + public GetCodeValuesDataResponse originatorType; @Schema(example = "1") - public Long channelTypeId; + public GetCodeValuesDataResponse channelType; + } + + @Schema(description = "GetLoanOriginatorTemplateResponse") + public static final class GetLoanOriginatorTemplateResponse { + + private GetLoanOriginatorTemplateResponse() {} + + @Schema(example = "EXT-001") + public String externalId; + public Set statusOptions; + public List originatorTypeOptions; + public List channelTypeOptions; } @Schema(description = "PostLoanOriginatorsRequest") diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorTemplateData.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorTemplateData.java new file mode 100644 index 000000000..7872d805c --- /dev/null +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/data/LoanOriginatorTemplateData.java @@ -0,0 +1,35 @@ +/** + * 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.data; + +import java.util.List; +import java.util.Set; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; + +@Data +@RequiredArgsConstructor +public class LoanOriginatorTemplateData { + + private final String externalId; + private final Set statusOptions; + private final List originatorTypeOptions; + private final List channelTypeOptions; +} diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorStatus.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorStatus.java index 9a97a741e..ae693028c 100644 --- a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorStatus.java +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/domain/LoanOriginatorStatus.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.portfolio.loanorigination.domain; +import java.util.HashSet; +import java.util.Set; + public enum LoanOriginatorStatus { ACTIVE("ACTIVE"), PENDING("PENDING"), INACTIVE("INACTIVE"); @@ -28,10 +31,22 @@ public enum LoanOriginatorStatus { this.value = value; } + private static final Set values = new HashSet<>(); + + static { + for (final LoanOriginatorStatus type : LoanOriginatorStatus.values()) { + values.add(type.value); + } + } + public String getValue() { return value; } + public static Set getAllValues() { + return values; + } + public static LoanOriginatorStatus fromString(String text) { for (LoanOriginatorStatus status : LoanOriginatorStatus.values()) { if (status.value.equalsIgnoreCase(text)) { diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java index c891c6e0c..e267b97e1 100644 --- a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/mapper/LoanOriginatorMapper.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanorigination.mapper; import java.util.List; +import org.apache.fineract.infrastructure.codes.mapper.CodeValueMapper; import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator; @@ -26,12 +27,12 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -@Mapper(config = MapstructMapperConfig.class) +@Mapper(config = MapstructMapperConfig.class, uses = { CodeValueMapper.class }) @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true") public interface LoanOriginatorMapper { - @Mapping(target = "originatorTypeId", source = "originatorType.id") - @Mapping(target = "channelTypeId", source = "channelType.id") + @Mapping(target = "originatorType", source = "originatorType") + @Mapping(target = "channelType", source = "channelType") @Mapping(target = "externalId", source = "externalId") @Mapping(target = "status", expression = "java(entity.getStatus().getValue())") LoanOriginatorData toData(LoanOriginator entity); diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformService.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformService.java index cace22688..2002378a3 100644 --- a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformService.java +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/service/LoanOriginatorReadPlatformService.java @@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanorigination.service; import java.util.List; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; +import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorTemplateData; public interface LoanOriginatorReadPlatformService { @@ -32,4 +33,6 @@ public interface LoanOriginatorReadPlatformService { Long resolveIdByExternalId(String externalId); List retrieveByLoanId(Long loanId); + + LoanOriginatorTemplateData retrieveTemplate(); } 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 b2dd31ff3..d628d28fc 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 @@ -21,12 +21,18 @@ package org.apache.fineract.portfolio.loanorigination.service; import java.util.Collections; import java.util.List; import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; +import org.apache.fineract.infrastructure.codes.mapper.CodeValueMapper; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; +import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorTemplateData; 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.LoanOriginatorRepository; +import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus; import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException; import org.apache.fineract.portfolio.loanorigination.mapper.LoanOriginatorMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -42,6 +48,8 @@ public class LoanOriginatorReadPlatformServiceImpl implements LoanOriginatorRead private final LoanOriginatorRepository loanOriginatorRepository; private final LoanOriginatorMappingRepository loanOriginatorMappingRepository; private final LoanOriginatorMapper loanOriginatorMapper; + private final CodeValueRepository codeValueRepository; + private final CodeValueMapper codeValueMapper; @Override public List retrieveAll() { @@ -78,4 +86,14 @@ public class LoanOriginatorReadPlatformServiceImpl implements LoanOriginatorRead } return mappings.stream().map(LoanOriginatorMapping::getOriginator).map(this.loanOriginatorMapper::toData).toList(); } + + @Override + public LoanOriginatorTemplateData retrieveTemplate() { + final List originationTypeOptions = codeValueMapper + .map(codeValueRepository.findByCodeName(LoanOriginatorApiConstants.ORIGINATOR_TYPE_CODE_NAME)); + final List channelTypeOptions = codeValueMapper + .map(codeValueRepository.findByCodeName(LoanOriginatorApiConstants.CHANNEL_TYPE_CODE_NAME)); + return new LoanOriginatorTemplateData(ExternalId.generate().getValue(), LoanOriginatorStatus.getAllValues(), originationTypeOptions, + channelTypeOptions); + } } 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 index 6283e2f66..59f2089c1 100644 --- 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 @@ -25,6 +25,7 @@ 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.GetLoanOriginatorTemplateResponse; import org.apache.fineract.client.models.GetLoanOriginatorsResponse; import org.apache.fineract.client.models.PostLoanOriginatorsRequest; import org.apache.fineract.client.models.PostLoanOriginatorsResponse; @@ -133,4 +134,8 @@ public class FeignLoanOriginatorHelper { return null; }); } + + public GetLoanOriginatorTemplateResponse retrieveLoanOriginatorTemplate() { + return ok(() -> fineractClient.loanOriginators().retrieveLoanOriginatorTemplate()); + } } 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 index cac02101f..cfc9291ae 100644 --- 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 @@ -19,8 +19,11 @@ package org.apache.fineract.integrationtests.client.feign.tests; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.GetCodeValuesDataResponse; +import org.apache.fineract.client.models.GetLoanOriginatorTemplateResponse; import org.apache.fineract.client.models.GetLoanOriginatorsResponse; import org.apache.fineract.client.models.PostLoanOriginatorsRequest; import org.apache.fineract.client.models.PutLoanOriginatorsRequest; @@ -30,10 +33,12 @@ import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelp import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanHelper; import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanOriginatorHelper; import org.apache.fineract.integrationtests.common.FineractFeignClientHelper; +import org.apache.fineract.integrationtests.common.Utils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +@Slf4j @Order(1) public class FeignLoanOriginatorApiTest extends FeignIntegrationTest { @@ -84,6 +89,32 @@ public class FeignLoanOriginatorApiTest extends FeignIntegrationTest { originatorHelper.deleteOriginator(originatorId); } + @Test + public void testCreateOriginatorWithAllFieldsUsingTemplate() { + final String name = Utils.randomStringGenerator("Originator ", 30); + + final GetLoanOriginatorTemplateResponse originatorTemplate = originatorHelper.retrieveLoanOriginatorTemplate(); + assertThat(originatorTemplate).isNotNull(); + + final String status = originatorTemplate.getStatusOptions().iterator().next(); + final GetCodeValuesDataResponse originatorTypeCode = originatorTemplate.getOriginatorTypeOptions().get(0); + final GetCodeValuesDataResponse channelTypeCode = originatorTemplate.getChannelTypeOptions().get(0); + + final Long originatorId = originatorHelper + .createOriginator(new PostLoanOriginatorsRequest().externalId(originatorTemplate.getExternalId()).name(name).status(status) + .originatorTypeId(originatorTypeCode.getId()).channelTypeId(channelTypeCode.getId())); + + final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorById(originatorId); + + assertThat(originator.getExternalId()).isEqualTo(originatorTemplate.getExternalId()); + assertThat(originator.getName()).isEqualTo(name); + assertThat(originator.getStatus()).isEqualTo(status); + assertThat(originator.getOriginatorType().getName()).isEqualTo(originatorTypeCode.getName()); + assertThat(originator.getChannelType().getName()).isEqualTo(channelTypeCode.getName()); + + originatorHelper.deleteOriginator(originatorId); + } + @Test public void testRetrieveOriginatorById() { final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();