FINERACT-2418: add originator during the loan application
This commit is contained in:
+35
@@ -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 lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoanApplicationOriginatorData {
|
||||
|
||||
private Long id;
|
||||
private String externalId;
|
||||
private String name;
|
||||
private Long typeId;
|
||||
private Long channelTypeId;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class LoanOriginatorCreationNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public LoanOriginatorCreationNotAllowedException(String externalId) {
|
||||
super("error.msg.loan.originator.creation.not.allowed", "Cannot create originator with externalId '" + externalId
|
||||
+ "' during loan application. Global configuration 'enable-originator-creation-during-loan-application' is disabled.",
|
||||
externalId);
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.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.ORIGINATOR_TYPE_CODE_NAME;
|
||||
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_ID_PARAM;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.apache.fineract.infrastructure.codes.exception.CodeValueNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.loanorigination.data.LoanApplicationOriginatorData;
|
||||
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 LoanApplicationOriginatorDataValidator {
|
||||
|
||||
private static final String RESOURCE_NAME = "loan.originator";
|
||||
private static final String ID_PARAM = "id";
|
||||
private static final String NAME_PARAM = "name";
|
||||
|
||||
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
|
||||
|
||||
public LoanApplicationOriginatorData validateAndExtract(JsonObject jsonObject) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(RESOURCE_NAME);
|
||||
|
||||
final Long id = extractLong(jsonObject, ID_PARAM);
|
||||
final String externalId = extractString(jsonObject, EXTERNAL_ID_PARAM);
|
||||
|
||||
if (id == null && (externalId == null || externalId.isBlank())) {
|
||||
baseDataValidator.reset().parameter(ID_PARAM).failWithCode("or.externalId.required",
|
||||
"Either 'id' or 'externalId' must be provided for originator");
|
||||
}
|
||||
|
||||
final String name = extractString(jsonObject, NAME_PARAM);
|
||||
baseDataValidator.reset().parameter(NAME_PARAM).value(name).ignoreIfNull().notExceedingLengthOf(255);
|
||||
|
||||
final Long typeId = extractLong(jsonObject, ORIGINATOR_TYPE_ID_PARAM);
|
||||
if (typeId != null) {
|
||||
validateCodeValue(typeId, ORIGINATOR_TYPE_CODE_NAME, ORIGINATOR_TYPE_ID_PARAM, baseDataValidator);
|
||||
}
|
||||
|
||||
final Long channelTypeId = extractLong(jsonObject, CHANNEL_TYPE_ID_PARAM);
|
||||
if (channelTypeId != null) {
|
||||
validateCodeValue(channelTypeId, CHANNEL_TYPE_CODE_NAME, CHANNEL_TYPE_ID_PARAM, baseDataValidator);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
|
||||
return new LoanApplicationOriginatorData(id, externalId, name, typeId, channelTypeId);
|
||||
}
|
||||
|
||||
private Long extractLong(JsonObject jsonObject, String paramName) {
|
||||
if (jsonObject.has(paramName)) {
|
||||
JsonElement element = jsonObject.get(paramName);
|
||||
if (!element.isJsonNull()) {
|
||||
try {
|
||||
return element.getAsLong();
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractString(JsonObject jsonObject, String paramName) {
|
||||
if (jsonObject.has(paramName)) {
|
||||
JsonElement element = jsonObject.get(paramName);
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validateCodeValue(Long codeValueId, String codeName, String paramName, DataValidatorBuilder baseDataValidator) {
|
||||
try {
|
||||
this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(codeName, codeValueId);
|
||||
} catch (CodeValueNotFoundException e) {
|
||||
baseDataValidator.reset().parameter(paramName).value(codeValueId).failWithCode("invalid.code.value",
|
||||
"Invalid code value id " + codeValueId + " for " + codeName);
|
||||
}
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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.infrastructure.configuration.api.GlobalConfigurationConstants.ENABLE_ORIGINATOR_CREATION_DURING_LOAN_APPLICATION;
|
||||
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CHANNEL_TYPE_CODE_NAME;
|
||||
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_CODE_NAME;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationProperty;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper;
|
||||
import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanOriginatorLinkingService;
|
||||
import org.apache.fineract.portfolio.loanorigination.data.LoanApplicationOriginatorData;
|
||||
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.LoanOriginatorCreationNotAllowedException;
|
||||
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotActiveException;
|
||||
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanorigination.serialization.LoanApplicationOriginatorDataValidator;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Implementation of {@link LoanOriginatorLinkingService} that handles processing of originators during loan
|
||||
* application. This service is active only when the loan-origination module is enabled.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("loanOriginatorLinkingServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
|
||||
public class LoanOriginatorLinkingServiceImpl implements LoanOriginatorLinkingService {
|
||||
|
||||
private final LoanOriginatorRepository loanOriginatorRepository;
|
||||
private final LoanOriginatorMappingRepository loanOriginatorMappingRepository;
|
||||
private final LoanApplicationOriginatorDataValidator validator;
|
||||
private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
|
||||
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void processOriginatorsForLoanApplication(Long loanId, JsonArray originatorsArray) {
|
||||
if (originatorsArray == null || originatorsArray.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Processing {} originators for loan application {}", originatorsArray.size(), loanId);
|
||||
|
||||
Set<Long> attachedOriginatorIds = new HashSet<>();
|
||||
|
||||
for (JsonElement element : originatorsArray) {
|
||||
if (!element.isJsonObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonObject jsonObject = element.getAsJsonObject();
|
||||
LoanApplicationOriginatorData originatorData = validator.validateAndExtract(jsonObject);
|
||||
LoanOriginator originator = resolveOrCreateOriginator(originatorData);
|
||||
|
||||
if (attachedOriginatorIds.contains(originator.getId())) {
|
||||
log.debug("Originator {} already attached to loan {}, skipping duplicate", originator.getId(), loanId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (originator.getStatus() != LoanOriginatorStatus.ACTIVE) {
|
||||
throw new LoanOriginatorNotActiveException(originator.getId(), originator.getStatus().getValue());
|
||||
}
|
||||
|
||||
if (!loanOriginatorMappingRepository.existsByLoanIdAndOriginatorId(loanId, originator.getId())) {
|
||||
LoanOriginatorMapping mapping = LoanOriginatorMapping.create(loanId, originator);
|
||||
loanOriginatorMappingRepository.save(mapping);
|
||||
log.debug("Attached originator {} to loan {}", originator.getId(), loanId);
|
||||
}
|
||||
|
||||
attachedOriginatorIds.add(originator.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private LoanOriginator resolveOrCreateOriginator(LoanApplicationOriginatorData originatorData) {
|
||||
if (originatorData.getId() != null) {
|
||||
return loanOriginatorRepository.findById(originatorData.getId())
|
||||
.orElseThrow(() -> new LoanOriginatorNotFoundException(originatorData.getId()));
|
||||
}
|
||||
|
||||
String externalId = originatorData.getExternalId();
|
||||
Optional<LoanOriginator> existingOriginator = loanOriginatorRepository.findByExternalId(new ExternalId(externalId));
|
||||
|
||||
if (existingOriginator.isPresent()) {
|
||||
return existingOriginator.get();
|
||||
}
|
||||
|
||||
if (!isOriginatorCreationDuringLoanApplicationEnabled()) {
|
||||
throw new LoanOriginatorCreationNotAllowedException(externalId);
|
||||
}
|
||||
|
||||
return createNewOriginator(originatorData);
|
||||
}
|
||||
|
||||
private boolean isOriginatorCreationDuringLoanApplicationEnabled() {
|
||||
try {
|
||||
GlobalConfigurationProperty config = globalConfigurationRepository
|
||||
.findOneByNameWithNotFoundDetection(ENABLE_ORIGINATOR_CREATION_DURING_LOAN_APPLICATION);
|
||||
return config.isEnabled();
|
||||
} catch (GlobalConfigurationPropertyNotFoundException e) {
|
||||
log.warn("Global configuration '{}' not found, defaulting to disabled", ENABLE_ORIGINATOR_CREATION_DURING_LOAN_APPLICATION);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private LoanOriginator createNewOriginator(LoanApplicationOriginatorData data) {
|
||||
log.info("Creating new originator with externalId: {} during loan application", data.getExternalId());
|
||||
|
||||
CodeValue originatorType = resolveCodeValue(data.getTypeId(), ORIGINATOR_TYPE_CODE_NAME);
|
||||
CodeValue channelType = resolveCodeValue(data.getChannelTypeId(), CHANNEL_TYPE_CODE_NAME);
|
||||
|
||||
LoanOriginator originator = LoanOriginator.create(new ExternalId(data.getExternalId()), data.getName(), LoanOriginatorStatus.ACTIVE,
|
||||
originatorType, channelType);
|
||||
|
||||
return loanOriginatorRepository.saveAndFlush(originator);
|
||||
}
|
||||
|
||||
private CodeValue resolveCodeValue(Long codeValueId, String codeName) {
|
||||
if (codeValueId == null) {
|
||||
return null;
|
||||
}
|
||||
return codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(codeName, codeValueId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user