blob: 4d424356b7714ebafe104876bf81c868ec525012 [file] [log] [blame]
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed 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 io.mifos.individuallending.internal.command.handler;
import io.mifos.core.command.annotation.Aggregate;
import io.mifos.core.command.annotation.CommandHandler;
import io.mifos.core.command.annotation.CommandLogLevel;
import io.mifos.core.command.annotation.EventEmitter;
import io.mifos.core.lang.ServiceException;
import io.mifos.individuallending.IndividualLendingPatternFactory;
import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
import io.mifos.individuallending.internal.command.*;
import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.individuallending.internal.service.IndividualLoanService;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.api.v1.events.EventConstants;
import io.mifos.portfolio.service.internal.mapper.CaseMapper;
import io.mifos.portfolio.service.internal.mapper.ProductMapper;
import io.mifos.portfolio.service.internal.repository.*;
import io.mifos.portfolio.service.internal.util.AccountingAdapter;
import io.mifos.portfolio.service.internal.util.ChargeInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Myrle Krantz
*/
@SuppressWarnings("unused")
@Aggregate
public class IndividualLoanCommandHandler {
private final ProductRepository productRepository;
private final CaseRepository caseRepository;
private final CaseParametersRepository caseParametersRepository;
private final AccountingAdapter accountingAdapter;
private final IndividualLoanService individualLoanService;
@Autowired
public IndividualLoanCommandHandler(final ProductRepository productRepository,
final CaseRepository caseRepository,
final CaseParametersRepository caseParametersRepository,
final AccountingAdapter accountingAdapter,
final IndividualLoanService individualLoanService) {
this.productRepository = productRepository;
this.caseRepository = caseRepository;
this.caseParametersRepository = caseParametersRepository;
this.accountingAdapter = accountingAdapter;
this.individualLoanService = individualLoanService;
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final OpenCommand command) {
final ProductEntity product = getProductOrThrow(command.getProductIdentifier());
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.OPEN);
final CaseParameters caseParameters =
caseParametersRepository.findByCaseId(customerCase.getId())
.map(CaseParametersMapper::mapEntity)
.orElseThrow(() -> ServiceException.notFound(
"Individual loan with identifier ''{0}''.''{1}'' doesn''t exist.",
command.getProductIdentifier(), command.getCaseIdentifier()));
final Set<ProductAccountAssignmentEntity> productAccountAssignments = product.getAccountAssignments();
final Set<CaseAccountAssignmentEntity> caseAccountAssignments = customerCase.getAccountAssignments();
final List<ChargeInstance> chargesNamedViaAccountDesignators =
individualLoanService.getChargeInstances(command.getProductIdentifier(), caseParameters, BigDecimal.ZERO, Action.OPEN, today(), LocalDate.now());
final List<ChargeInstance> chargesNamedViaAccountIdentifier = chargesNamedViaAccountDesignators.stream().map(x -> new ChargeInstance(
designatorToAccountIdentifierOrThrow(x.getFromAccount(), command.getCommand().getOneTimeAccountAssignments(), caseAccountAssignments, productAccountAssignments),
designatorToAccountIdentifierOrThrow(x.getToAccount(), command.getCommand().getOneTimeAccountAssignments(), caseAccountAssignments, productAccountAssignments),
x.getAmount())).collect(Collectors.toList());
//TODO: Accrual
accountingAdapter.bookCharges(chargesNamedViaAccountIdentifier,
command.getCommand().getNote(),
command.getProductIdentifier() + "." + command.getCaseIdentifier() + "." + Action.OPEN.name(),
Action.OPEN.getTransactionType());
//Only move to pending if book charges command was accepted.
updateCaseState(customerCase, Case.State.PENDING);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
private static LocalDate today() {
return LocalDate.now(ZoneId.of("UTC"));
}
private String designatorToAccountIdentifierOrThrow(final String accountDesignator,
final List<AccountAssignment> oneTimeAccountAssignments,
final Set<CaseAccountAssignmentEntity> caseAccountAssignments,
final Set<ProductAccountAssignmentEntity> productAccountAssignments) {
return allAccountAssignmentsAsStream(oneTimeAccountAssignments, caseAccountAssignments, productAccountAssignments)
.filter(x -> x.getDesignator().equals(accountDesignator))
.findFirst()
.map(AccountAssignment::getAccountIdentifier)
.orElseThrow(() -> ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
}
private Stream<AccountAssignment> allAccountAssignmentsAsStream(
final List<AccountAssignment> oneTimeAccountAssignments,
final Set<CaseAccountAssignmentEntity> caseAccountAssignments,
final Set<ProductAccountAssignmentEntity> productAccountAssignments) {
return Stream.concat(Stream.concat(
oneTimeAccountAssignments.stream(),
caseAccountAssignments.stream().map(CaseMapper::mapAccountAssignmentEntity)),
productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity));
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.DENY_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final DenyCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.DENY);
updateCaseState(customerCase, Case.State.CLOSED);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final ApproveCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.APPROVE);
updateCaseState(customerCase, Case.State.APPROVED);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final DisburseCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.DISBURSE);
updateCaseState(customerCase, Case.State.ACTIVE);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final AcceptPaymentCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.ACCEPT_PAYMENT);
updateCaseState(customerCase, Case.State.ACTIVE);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.WRITE_OFF_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final WriteOffCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.WRITE_OFF);
updateCaseState(customerCase, Case.State.CLOSED);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.CLOSE_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final CloseCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.CLOSE);
updateCaseState(customerCase, Case.State.CLOSED);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.RECOVER_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final RecoverCommand command) {
final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.RECOVER);
updateCaseState(customerCase, Case.State.CLOSED);
return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
}
private CaseEntity getCaseOrThrow(final String productIdentifier, final String caseIdentifier) {
return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
.orElseThrow(() -> ServiceException.notFound("Case not found ''{0}.{1}''.", productIdentifier, caseIdentifier));
}
private ProductEntity getProductOrThrow(final String productIdentifier) {
return productRepository.findByIdentifier(productIdentifier)
.orElseThrow(() -> ServiceException.notFound("Product not found ''{0}''.", productIdentifier));
}
private void checkActionCanBeExecuted(final Case.State state, final Action action) {
if (!IndividualLendingPatternFactory.getAllowedNextActionsForState(state).contains(action))
throw ServiceException.badRequest("Cannot call action {0} from state {1}", action.name(), state.name());
}
private void updateCaseState(final CaseEntity customerCase, final Case.State state) {
customerCase.setCurrentState(state.name());
caseRepository.save(customerCase);
}
}