blob: bea1f31b9db79bac18d4382f23bab9a7dcca1846 [file] [log] [blame]
/**
* 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.guarantor.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
import org.apache.fineract.portfolio.account.domain.AccountAssociations;
import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants.GUARANTOR_JSON_INPUT_PARAMS;
import org.apache.fineract.portfolio.loanaccount.guarantor.command.GuarantorCommand;
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundStatusType;
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingDetails;
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository;
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
import org.apache.fineract.portfolio.loanaccount.guarantor.exception.DuplicateGuarantorException;
import org.apache.fineract.portfolio.loanaccount.guarantor.exception.GuarantorNotFoundException;
import org.apache.fineract.portfolio.loanaccount.guarantor.exception.InvalidGuarantorException;
import org.apache.fineract.portfolio.loanaccount.guarantor.serialization.GuarantorCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements GuarantorWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(GuarantorWritePlatformServiceJpaRepositoryIImpl.class);
private final ClientRepositoryWrapper clientRepositoryWrapper;
private final StaffRepositoryWrapper staffRepositoryWrapper;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final GuarantorRepository guarantorRepository;
private final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
private final GuarantorDomainService guarantorDomainService;
@Autowired
public GuarantorWritePlatformServiceJpaRepositoryIImpl(final LoanRepositoryWrapper loanRepositoryWrapper,
final GuarantorRepository guarantorRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
final StaffRepositoryWrapper staffRepositoryWrapper, final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
final AccountAssociationsRepository accountAssociationsRepository, final GuarantorDomainService guarantorDomainService) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.clientRepositoryWrapper = clientRepositoryWrapper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.guarantorRepository = guarantorRepository;
this.staffRepositoryWrapper = staffRepositoryWrapper;
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.guarantorDomainService = guarantorDomainService;
}
@Override
@Transactional
public CommandProcessingResult createGuarantor(final Long loanId, final JsonCommand command) {
final GuarantorCommand guarantorCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
return createGuarantor(loan, command, guarantorCommand, existGuarantorList);
}
private CommandProcessingResult createGuarantor(final Loan loan, final JsonCommand command, final GuarantorCommand guarantorCommand,
final Collection<Guarantor> existGuarantorList) {
try {
guarantorCommand.validateForCreate();
validateLoanStatus(loan);
final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>();
AccountAssociations accountAssociations = null;
if (guarantorCommand.getSavingsId() != null) {
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(guarantorCommand.getSavingsId());
accountAssociations = AccountAssociations.associateSavingsAccount(loan, savingsAccount,
AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), true);
GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations,
GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount());
guarantorFundingDetails.add(fundingDetails);
if (loan.isDisbursed() || loan.isApproved()
&& (loan.getGuaranteeAmount() != null || loan.loanProduct().isHoldGuaranteeFundsEnabled())) {
this.guarantorDomainService.assignGuarantor(fundingDetails, LocalDate.now());
loan.updateGuaranteeAmount(fundingDetails.getAmount());
}
}
final Long clientRelationshipId = guarantorCommand.getClientRelationshipTypeId();
CodeValue clientRelationshipType = null;
if (clientRelationshipId != null) {
clientRelationshipType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME, clientRelationshipId);
}
final Long entityId = guarantorCommand.getEntityId();
final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId();
Guarantor guarantor = null;
for (final Guarantor avilableGuarantor : existGuarantorList) {
if (entityId != null && avilableGuarantor.getEntityId() != null && avilableGuarantor.getEntityId().equals(entityId)
&& avilableGuarantor.getGurantorType().equals(guarantorTypeId) && avilableGuarantor.isActive()) {
if (guarantorCommand.getSavingsId() == null || avilableGuarantor.hasGuarantor(guarantorCommand.getSavingsId())) {
/** Get the right guarantor based on guarantorType **/
String defaultUserMessage = null;
if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) {
defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName();
} else {
defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
}
defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
final String action = loan.client() != null ? "client.guarantor" : "group.guarantor";
throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId,
loan.getId());
}
guarantor = avilableGuarantor;
break;
}
}
if (guarantor == null) {
guarantor = Guarantor.fromJson(loan, clientRelationshipType, command, guarantorFundingDetails);
} else {
guarantor.addFundingDetails(guarantorFundingDetails);
}
validateGuarantorBusinessRules(guarantor);
for (GuarantorFundingDetails fundingDetails : guarantorFundingDetails) {
fundingDetails.updateGuarantor(guarantor);
}
if (accountAssociations != null) {
this.accountAssociationsRepository.save(accountAssociations);
}
this.guarantorRepository.save(guarantor);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(guarantor.getOfficeId())
.withEntityId(guarantor.getId()).withLoanId(loan.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleGuarantorDataIntegrityIssues(dve);
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult updateGuarantor(final Long loanId, final Long guarantorId, final JsonCommand command) {
try {
final GuarantorCommand guarantorCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
guarantorCommand.validateForUpdate();
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
validateLoanStatus(loan);
final Guarantor guarantorForUpdate = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
if (guarantorForUpdate == null) { throw new GuarantorNotFoundException(loanId, guarantorId); }
final Map<String, Object> changesOnly = guarantorForUpdate.update(command);
if (changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.CLIENT_RELATIONSHIP_TYPE_ID.getValue())) {
final Long clientRelationshipId = guarantorCommand.getClientRelationshipTypeId();
CodeValue clientRelationshipType = null;
if (clientRelationshipId != null) {
clientRelationshipType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
GuarantorConstants.GUARANTOR_RELATIONSHIP_CODE_NAME, clientRelationshipId);
}
guarantorForUpdate.updateClientRelationshipType(clientRelationshipType);
}
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId();
final GuarantorType guarantorType = GuarantorType.fromInt(guarantorTypeId);
if (guarantorType.isCustomer() || guarantorType.isStaff()) {
final Long entityId = guarantorCommand.getEntityId();
for (final Guarantor guarantor : existGuarantorList) {
if (guarantor.getEntityId().equals(entityId) && guarantor.getGurantorType().equals(guarantorTypeId)
&& !guarantorForUpdate.getId().equals(guarantor.getId())) {
String defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
final String action = loan.client() != null ? "client.guarantor" : "group.guarantor";
throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId, loanId);
}
}
}
if (changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.ENTITY_ID)
|| changesOnly.containsKey(GUARANTOR_JSON_INPUT_PARAMS.GUARANTOR_TYPE_ID)) {
validateGuarantorBusinessRules(guarantorForUpdate);
}
if (!changesOnly.isEmpty()) {
this.guarantorRepository.save(guarantorForUpdate);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(guarantorForUpdate.getOfficeId())
.withEntityId(guarantorForUpdate.getId()).withOfficeId(guarantorForUpdate.getLoanId()).with(changesOnly).build();
} catch (final DataIntegrityViolationException dve) {
handleGuarantorDataIntegrityIssues(dve);
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult removeGuarantor(final Long loanId, final Long guarantorId, final Long guarantorFundingId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
validateLoanStatus(loan);
final Guarantor guarantorForDelete = this.guarantorRepository.findByLoanAndId(loan, guarantorId);
if (guarantorForDelete == null || (guarantorFundingId == null && !guarantorForDelete.getGuarantorFundDetails().isEmpty())) { throw new GuarantorNotFoundException(
loanId, guarantorId, guarantorFundingId); }
CommandProcessingResult commandProcessingResult = removeGuarantor(guarantorForDelete, loanId, guarantorFundingId);
if (loan.isApproved() || loan.isDisbursed()) {
this.guarantorDomainService.validateGuarantorBusinessRules(loan);
}
return commandProcessingResult;
}
private CommandProcessingResult removeGuarantor(final Guarantor guarantorForDelete, final Long loanId, final Long guarantorFundingId) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("Guarantor");
if (guarantorFundingId == null) {
if (!guarantorForDelete.isActive()) {
baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
}
guarantorForDelete.updateStatus(false);
} else {
GuarantorFundingDetails guarantorFundingDetails = guarantorForDelete.getGuarantorFundingDetail(guarantorFundingId);
if (guarantorFundingDetails == null) { throw new GuarantorNotFoundException(loanId, guarantorForDelete.getId(),
guarantorFundingId); }
removeguarantorFundDetails(guarantorForDelete, baseDataValidator, guarantorFundingDetails);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
this.guarantorRepository.saveAndFlush(guarantorForDelete);
CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder()
.withEntityId(guarantorForDelete.getId()).withLoanId(guarantorForDelete.getLoanId())
.withOfficeId(guarantorForDelete.getOfficeId());
if (guarantorFundingId != null) {
commandProcessingResultBuilder.withSubEntityId(guarantorFundingId);
}
return commandProcessingResultBuilder.build();
}
private void removeguarantorFundDetails(final Guarantor guarantorForDelete, final DataValidatorBuilder baseDataValidator,
GuarantorFundingDetails guarantorFundingDetails) {
if (!guarantorFundingDetails.getStatus().isActive()) {
baseDataValidator.failWithCodeNoParameterAddedToErrorCode(GuarantorConstants.GUARANTOR_NOT_ACTIVE_ERROR);
}
GuarantorFundStatusType fundStatusType = GuarantorFundStatusType.DELETED;
if (guarantorForDelete.getLoan().isDisbursed() || guarantorForDelete.getLoan().isApproved()) {
fundStatusType = GuarantorFundStatusType.WITHDRAWN;
this.guarantorDomainService.releaseGuarantor(guarantorFundingDetails, LocalDate.now());
}
guarantorForDelete.updateStatus(guarantorFundingDetails, fundStatusType);
}
private void validateGuarantorBusinessRules(final Guarantor guarantor) {
// validate guarantor conditions
if (guarantor.isExistingCustomer()) {
// check client exists
this.clientRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
// validate that the client is not set as a self guarantor
if (guarantor.getClientId() != null && guarantor.getClientId().equals(guarantor.getEntityId())) {
String errorCode = null;
if (guarantor.getGuarantorFundDetails().isEmpty()) {
errorCode = "guarantor.can.not.be.own";
} else if (guarantor.getClientRelationshipType() != null) {
errorCode = "guarantor.relation.should.be.empty.for.own";
}
if (errorCode != null) { throw new InvalidGuarantorException(guarantor.getEntityId(), guarantor.getLoanId(), errorCode); }
}
} else if (guarantor.isExistingEmployee()) {
this.staffRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
}
}
private void validateLoanStatus(Loan loan) {
if (!loan.status().isActiveOrAwaitingApprovalOrDisbursal()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.guarantor");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.closed");
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
private void handleGuarantorDataIntegrityIssues(final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.guarantor.unknown.data.integrity.issue",
"Unknown data integrity issue with resource Guarantor: " + realCause.getMessage());
}
}