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);
|
||||
}
|
||||
}
|
||||
+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.loanaccount.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
public interface LoanOriginatorLinkingService {
|
||||
|
||||
/**
|
||||
* Process originators provided during loan application. Creates new originators if allowed by global config, then
|
||||
* attaches them to the loan.
|
||||
*
|
||||
* @param loanId
|
||||
* the loan ID to attach originators to
|
||||
* @param originatorsArray
|
||||
* JSON array of originator data from loan request
|
||||
*/
|
||||
void processOriginatorsForLoanApplication(Long loanId, JsonArray originatorsArray);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.loanaccount.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* No-op implementation of {@link LoanOriginatorLinkingService} that is used when the loan-origination module is
|
||||
* disabled. When originator data is provided during loan application, this implementation silently ignores it.
|
||||
*/
|
||||
@Service
|
||||
@ConditionalOnMissingBean(name = "loanOriginatorLinkingServiceImpl")
|
||||
public class LoanOriginatorLinkingServiceNoOp implements LoanOriginatorLinkingService {
|
||||
|
||||
@Override
|
||||
public void processOriginatorsForLoanApplication(Long loanId, JsonArray originatorsArray) {
|
||||
// No-op when loan-origination module is not enabled
|
||||
}
|
||||
}
|
||||
+8
@@ -129,6 +129,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
|
||||
private final LoanScheduleService loanScheduleService;
|
||||
private final LoanOriginatorLinkingService loanOriginatorLinkingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -166,6 +167,13 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
// Check mandatory datatable entries were created
|
||||
this.entityDatatableChecksWritePlatformService.runTheCheckForProduct(loan.getId(), EntityTables.LOAN.getName(),
|
||||
StatusEnum.CREATE.getValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
|
||||
// Process originators if provided
|
||||
if (command.parameterExists(LoanApiConstants.ORIGINATORS_PARAM)) {
|
||||
final JsonArray originatorsArray = command.arrayOfParameterNamed(LoanApiConstants.ORIGINATORS_PARAM);
|
||||
if (originatorsArray != null && !originatorsArray.isEmpty()) {
|
||||
this.loanOriginatorLinkingService.processOriginatorsForLoanApplication(loan.getId(), originatorsArray);
|
||||
}
|
||||
}
|
||||
// Trigger business event
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanCreatedBusinessEvent(loan));
|
||||
// Building response
|
||||
|
||||
+4
-2
@@ -150,6 +150,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerS
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanMaximumAmountCalculator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanOfficerService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanOriginatorLinkingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
|
||||
@@ -230,13 +231,14 @@ public class LoanAccountConfiguration {
|
||||
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository,
|
||||
LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine, LoanAccrualsProcessingService loanAccrualsProcessingService,
|
||||
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService) {
|
||||
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService,
|
||||
LoanOriginatorLinkingService loanOriginatorLinkingService) {
|
||||
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, loanApplicationTransitionValidator,
|
||||
loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, calendarRepository,
|
||||
calendarInstanceRepository, savingsAccountRepository, accountAssociationsRepository, businessEventNotifierService,
|
||||
loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService,
|
||||
glimRepository, loanRepository, gsimReadPlatformService, loanLifecycleStateMachine, loanAccrualsProcessingService,
|
||||
loanDownPaymentTransactionValidator, loanScheduleService);
|
||||
loanDownPaymentTransactionValidator, loanScheduleService, loanOriginatorLinkingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+18
-3
@@ -48,6 +48,8 @@ import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
@@ -145,8 +147,7 @@ public class SavingsImportHandlerTest {
|
||||
.setCellValue(savingsProductSheet.getRow(1).getCell(10).getStringCellValue());
|
||||
firstSavingsRow.createCell(SavingsConstants.DECIMAL_PLACES_COL)
|
||||
.setCellValue(savingsProductSheet.getRow(1).getCell(11).getNumericCellValue());
|
||||
firstSavingsRow.createCell(SavingsConstants.IN_MULTIPLES_OF_COL)
|
||||
.setCellValue(savingsProductSheet.getRow(1).getCell(12).getNumericCellValue());
|
||||
safeNumericValueSetter(firstSavingsRow, SavingsConstants.IN_MULTIPLES_OF_COL, savingsProductSheet, 1, 12);
|
||||
firstSavingsRow.createCell(SavingsConstants.NOMINAL_ANNUAL_INTEREST_RATE_COL)
|
||||
.setCellValue(savingsProductSheet.getRow(1).getCell(2).getNumericCellValue());
|
||||
firstSavingsRow.createCell(SavingsConstants.INTEREST_COMPOUNDING_PERIOD_COL)
|
||||
@@ -181,7 +182,7 @@ public class SavingsImportHandlerTest {
|
||||
Assertions.assertNotNull(importDocumentId);
|
||||
|
||||
// Wait for the creation of output excel
|
||||
Thread.sleep(10000);
|
||||
Thread.sleep(1000);
|
||||
|
||||
// check status column of output excel
|
||||
String location = savingsAccountHelper.getOutputTemplateLocation(importDocumentId);
|
||||
@@ -196,4 +197,18 @@ public class SavingsImportHandlerTest {
|
||||
Assertions.assertEquals("Imported", row.getCell(SavingsConstants.STATUS_COL).getStringCellValue());
|
||||
Outputworkbook.close();
|
||||
}
|
||||
|
||||
private void safeNumericValueSetter(Row targetRow, int targetColId, Sheet sourceSheet, int rowId, int colId) {
|
||||
Row row = sourceSheet.getRow(rowId);
|
||||
if (row == null) {
|
||||
targetRow.createCell(targetColId).setBlank();
|
||||
} else {
|
||||
Cell cell = row.getCell(colId);
|
||||
if (cell == null || cell.getCellType() == CellType.BLANK) {
|
||||
targetRow.createCell(targetColId).setBlank();
|
||||
} else {
|
||||
targetRow.createCell(targetColId).setCellValue(cell.getNumericCellValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.ok;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.client.feign.FineractFeignClient;
|
||||
import org.apache.fineract.client.models.GetGlobalConfigurationsResponse;
|
||||
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
|
||||
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||
|
||||
public class FeignGlobalConfigurationHelper {
|
||||
|
||||
private final FineractFeignClient fineractClient;
|
||||
|
||||
public FeignGlobalConfigurationHelper(FineractFeignClient fineractClient) {
|
||||
this.fineractClient = fineractClient;
|
||||
}
|
||||
|
||||
public void enableOriginatorCreationDuringLoanApplication() {
|
||||
updateConfigurationByName("enable-originator-creation-during-loan-application", true);
|
||||
}
|
||||
|
||||
public void disableOriginatorCreationDuringLoanApplication() {
|
||||
updateConfigurationByName("enable-originator-creation-during-loan-application", false);
|
||||
}
|
||||
|
||||
public void updateConfigurationByName(String configName, boolean enabled) {
|
||||
Long configId = getConfigurationIdByName(configName);
|
||||
ok(() -> fineractClient.globalConfiguration().updateConfiguration1(configId,
|
||||
new PutGlobalConfigurationsRequest().enabled(enabled)));
|
||||
}
|
||||
|
||||
public Long getConfigurationIdByName(String configName) {
|
||||
List<GlobalConfigurationPropertyData> configs = getConfigurationList();
|
||||
return configs.stream().filter(c -> configName.equals(c.getName())).findFirst().map(GlobalConfigurationPropertyData::getId)
|
||||
.orElseThrow(() -> new RuntimeException("Configuration not found: " + configName));
|
||||
}
|
||||
|
||||
private List<GlobalConfigurationPropertyData> getConfigurationList() {
|
||||
GetGlobalConfigurationsResponse response = ok(() -> fineractClient.globalConfiguration().retrieveConfiguration(false));
|
||||
return response.getGlobalConfiguration();
|
||||
}
|
||||
}
|
||||
+43
@@ -18,16 +18,20 @@
|
||||
*/
|
||||
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.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.client.feign.FineractFeignClient;
|
||||
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansOriginatorData;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
|
||||
@@ -170,4 +174,43 @@ public class FeignLoanHelper {
|
||||
.format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant());
|
||||
return createSubmittedLoan(clientId, productId, todayDate, 10000.0, 12);
|
||||
}
|
||||
|
||||
public Long createSubmittedLoanWithOriginators(Long clientId, List<PostLoansOriginatorData> originators) {
|
||||
PostLoansRequest request = buildSubmittedLoanRequest(clientId);
|
||||
request.setOriginators(originators);
|
||||
PostLoansResponse response = ok(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, (String) null));
|
||||
return response.getLoanId();
|
||||
}
|
||||
|
||||
public CallFailedRuntimeException createSubmittedLoanWithOriginatorsExpectingError(Long clientId,
|
||||
List<PostLoansOriginatorData> originators) {
|
||||
PostLoansRequest request = buildSubmittedLoanRequest(clientId);
|
||||
request.setOriginators(originators);
|
||||
return fail(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, (String) null));
|
||||
}
|
||||
|
||||
private PostLoansRequest buildSubmittedLoanRequest(Long clientId) {
|
||||
Long productId = createSimpleLoanProduct();
|
||||
String todayDate = org.apache.fineract.integrationtests.common.Utils.dateFormatter
|
||||
.format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant());
|
||||
return new PostLoansRequest()//
|
||||
.clientId(clientId)//
|
||||
.productId(productId)//
|
||||
.loanType("individual")//
|
||||
.submittedOnDate(todayDate)//
|
||||
.expectedDisbursementDate(todayDate)//
|
||||
.principal(BigDecimal.valueOf(10000.0))//
|
||||
.loanTermFrequency(12)//
|
||||
.loanTermFrequencyType(2)//
|
||||
.numberOfRepayments(12)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(2)//
|
||||
.interestRatePerPeriod(BigDecimal.ZERO)//
|
||||
.amortizationType(1)//
|
||||
.interestType(0)//
|
||||
.interestCalculationPeriodType(1)//
|
||||
.transactionProcessingStrategyCode("mifos-standard-strategy")//
|
||||
.locale("en")//
|
||||
.dateFormat("dd MMMM yyyy");
|
||||
}
|
||||
}
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 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.FineractFeignClient;
|
||||
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
|
||||
import org.apache.fineract.client.models.PostLoansOriginatorData;
|
||||
import org.apache.fineract.integrationtests.client.FeignIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelper;
|
||||
import org.apache.fineract.integrationtests.client.feign.helpers.FeignGlobalConfigurationHelper;
|
||||
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.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Order(2)
|
||||
public class FeignLoanOriginatorDuringApplicationTest extends FeignIntegrationTest {
|
||||
|
||||
private static FeignLoanOriginatorHelper originatorHelper;
|
||||
private static FeignClientHelper clientHelper;
|
||||
private static FeignLoanHelper loanHelper;
|
||||
private static FeignGlobalConfigurationHelper configHelper;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
FineractFeignClient fineractClient = FineractFeignClientHelper.getFineractFeignClient();
|
||||
originatorHelper = new FeignLoanOriginatorHelper(fineractClient);
|
||||
clientHelper = new FeignClientHelper(fineractClient);
|
||||
loanHelper = new FeignLoanHelper(fineractClient);
|
||||
configHelper = new FeignGlobalConfigurationHelper(fineractClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithExistingOriginatorById() {
|
||||
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId));
|
||||
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
|
||||
|
||||
assertThat(loanId).isNotNull();
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).hasSize(1);
|
||||
assertThat(loanDetails.getOriginators().get(0).getId()).isEqualTo(originatorId);
|
||||
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
|
||||
originatorHelper.deleteOriginator(originatorId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithExistingOriginatorByExternalId() {
|
||||
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().externalId(originatorExternalId));
|
||||
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
|
||||
|
||||
assertThat(loanId).isNotNull();
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).hasSize(1);
|
||||
assertThat(loanDetails.getOriginators().get(0).getExternalId()).isEqualTo(originatorExternalId);
|
||||
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
|
||||
originatorHelper.deleteOriginator(originatorId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithNewOriginatorWhenConfigEnabled() {
|
||||
configHelper.enableOriginatorCreationDuringLoanApplication();
|
||||
|
||||
try {
|
||||
final Long clientId = clientHelper.createClient();
|
||||
final String newOriginatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List
|
||||
.of(new PostLoansOriginatorData().externalId(newOriginatorExternalId).name("New Merchant Created During Loan"));
|
||||
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
|
||||
|
||||
assertThat(loanId).isNotNull();
|
||||
|
||||
final var createdOriginator = originatorHelper.getOriginatorByExternalId(newOriginatorExternalId);
|
||||
assertThat(createdOriginator).isNotNull();
|
||||
assertThat(createdOriginator.getName()).isEqualTo("New Merchant Created During Loan");
|
||||
assertThat(createdOriginator.getStatus()).isEqualTo("ACTIVE");
|
||||
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).hasSize(1);
|
||||
assertThat(loanDetails.getOriginators().get(0).getExternalId()).isEqualTo(newOriginatorExternalId);
|
||||
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, createdOriginator.getId());
|
||||
originatorHelper.deleteOriginator(createdOriginator.getId());
|
||||
} finally {
|
||||
configHelper.disableOriginatorCreationDuringLoanApplication();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithNewOriginatorFailsWhenConfigDisabled() {
|
||||
configHelper.disableOriginatorCreationDuringLoanApplication();
|
||||
|
||||
final Long clientId = clientHelper.createClient();
|
||||
final String nonExistingExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().externalId(nonExistingExternalId));
|
||||
|
||||
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
|
||||
|
||||
assertThat(exception.getStatus()).isIn(403, 404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithMultipleOriginators() {
|
||||
final String externalId1 = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final String externalId2 = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final Long originatorId1 = originatorHelper.createOriginator(externalId1);
|
||||
final Long originatorId2 = originatorHelper.createOriginator(externalId2);
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId1),
|
||||
new PostLoansOriginatorData().externalId(externalId2));
|
||||
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
|
||||
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).hasSize(2);
|
||||
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, originatorId1);
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, originatorId2);
|
||||
originatorHelper.deleteOriginator(originatorId1);
|
||||
originatorHelper.deleteOriginator(originatorId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithInvalidOriginatorDataReturns400() {
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().name("Invalid - no id or externalId"));
|
||||
|
||||
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
|
||||
|
||||
assertThat(exception.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithInactiveOriginatorReturns403() {
|
||||
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final Long originatorId = originatorHelper.createOriginator(externalId, "Inactive Originator", "INACTIVE");
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId));
|
||||
|
||||
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
|
||||
|
||||
assertThat(exception.getStatus()).isEqualTo(403);
|
||||
|
||||
originatorHelper.deleteOriginator(originatorId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithNonExistingOriginatorIdReturns404() {
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(999999L));
|
||||
|
||||
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
|
||||
|
||||
assertThat(exception.getStatus()).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithoutOriginatorsStillWorks() {
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final Long loanId = loanHelper.createSubmittedLoan(clientId);
|
||||
|
||||
assertThat(loanId).isNotNull();
|
||||
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLoanWithDuplicateOriginatorInListAttachesOnce() {
|
||||
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
|
||||
final Long originatorId = originatorHelper.createOriginator(externalId);
|
||||
final Long clientId = clientHelper.createClient();
|
||||
|
||||
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId),
|
||||
new PostLoansOriginatorData().externalId(externalId));
|
||||
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
|
||||
|
||||
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
|
||||
assertThat(loanDetails.getOriginators()).hasSize(1);
|
||||
|
||||
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
|
||||
originatorHelper.deleteOriginator(originatorId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user