blob: 5d105b47fdff19072690a8514b70b0b34f48cc17 [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.DateConverter;
import io.mifos.core.lang.ServiceException;
import io.mifos.individuallending.IndividualLendingPatternFactory;
import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
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.repository.CaseParametersRepository;
import io.mifos.individuallending.internal.service.*;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.api.v1.domain.CostComponent;
import io.mifos.portfolio.api.v1.events.EventConstants;
import io.mifos.portfolio.service.internal.mapper.CaseMapper;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
import io.mifos.portfolio.service.internal.repository.CaseRepository;
import io.mifos.portfolio.service.internal.repository.TaskInstanceRepository;
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 javax.annotation.Nullable;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author Myrle Krantz
*/
@SuppressWarnings("unused")
@Aggregate
public class IndividualLoanCommandHandler {
private final CaseRepository caseRepository;
private final DataContextService dataContextService;
private final CostComponentService costComponentService;
private final AccountingAdapter accountingAdapter;
private final TaskInstanceRepository taskInstanceRepository;
private final CaseParametersRepository caseParametersRepository;
@Autowired
public IndividualLoanCommandHandler(
final CaseRepository caseRepository,
final DataContextService dataContextService,
final CostComponentService costComponentService,
final AccountingAdapter accountingAdapter,
final TaskInstanceRepository taskInstanceRepository,
final CaseParametersRepository caseParametersRepository) {
this.caseRepository = caseRepository;
this.dataContextService = dataContextService;
this.costComponentService = costComponentService;
this.accountingAdapter = accountingAdapter;
this.taskInstanceRepository = taskInstanceRepository;
this.caseParametersRepository = caseParametersRepository;
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.OPEN);
checkIfTasksAreOutstanding(dataContextOfAction, Action.OPEN);
final CostComponentsForRepaymentPeriod costComponents
= costComponentService.getCostComponentsForOpen(dataContextOfAction);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges = costComponents.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.OPEN,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.OPEN),
Action.OPEN.getTransactionType());
//Only move to new state if book charges command was accepted.
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.PENDING.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.DENY);
checkIfTasksAreOutstanding(dataContextOfAction, Action.DENY);
final CostComponentsForRepaymentPeriod costComponents
= costComponentService.getCostComponentsForDeny(dataContextOfAction);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges = costComponents.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.DENY,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.APPROVE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.APPROVE);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
//Create the needed account assignments and persist them for the case.
designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
.map(ledger ->
new AccountAssignment(ledger.getDesignator(),
accountingAdapter.createAccountForLedgerAssignment(dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(), ledger)))
.map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
.forEach(caseAccountAssignmentEntity ->
dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
);
caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForApprove(dataContextOfAction);
final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.APPROVE,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.APPROVE),
Action.APPROVE.getTransactionType());
//Only move to new state if book charges command was accepted.
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.APPROVED.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.DISBURSE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.DISBURSE);
final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges =
costComponentsForRepaymentPeriod.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.DISBURSE,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
//Only move to new state if book charges command was accepted.
if (Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()) != Case.State.ACTIVE) {
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
final LocalDateTime endOfTerm
= ScheduledActionHelpers.getRoughEndDate(today.toLocalDate(), dataContextOfAction.getCaseParameters())
.atTime(LocalTime.MIDNIGHT);
customerCase.setEndOfTerm(endOfTerm);
customerCase.setCurrentState(Case.State.ACTIVE.name());
caseRepository.save(customerCase);
}
final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSize(
currentBalance.add(disbursalAmount),
dataContextOfAction);
dataContextOfAction.getCaseParametersEntity().setPaymentSize(newLoanPaymentSize);
caseParametersRepository.save(dataContextOfAction.getCaseParametersEntity());
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(
selectorName = EventConstants.SELECTOR_NAME,
selectorValue = IndividualLoanEventConstants.APPLY_INTEREST_INDIVIDUALLOAN_CASE)
public IndividualLoanCommandEvent process(final ApplyInterestCommand command) {
final String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, null);
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.APPLY_INTEREST);
if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
throw ServiceException.internalError(
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForApplyInterest(dataContextOfAction);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.APPLY_INTEREST,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
accountingAdapter.bookCharges(charges,
"",
dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST),
Action.APPLY_INTEREST.getTransactionType());
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.ACCEPT_PAYMENT);
checkIfTasksAreOutstanding(dataContextOfAction, Action.ACCEPT_PAYMENT);
if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
throw ServiceException.internalError(
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForAcceptPayment(dataContextOfAction, command.getCommand().getPaymentSize());
final BigDecimal sumOfAdjustments = costComponentsForRepaymentPeriod.stream()
.filter(entry -> entry.getKey().getIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
.map(entry -> entry.getValue().getAmount())
.reduce(BigDecimal.ZERO, BigDecimal::add);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.ACCEPT_PAYMENT,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
Action.ACCEPT_PAYMENT.getTransactionType());
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.WRITE_OFF);
checkIfTasksAreOutstanding(dataContextOfAction, Action.WRITE_OFF);
final LocalDateTime today = today();
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.CLOSE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForClose(dataContextOfAction);
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
final List<ChargeInstance> charges =
costComponentsForRepaymentPeriod.stream()
.map(entry -> mapCostComponentEntryToChargeInstance(
Action.DISBURSE,
entry,
designatorToAccountIdentifierMapper))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@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 String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.RECOVER);
checkIfTasksAreOutstanding(dataContextOfAction, Action.RECOVER);
final LocalDateTime today = today();
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
private static Optional<ChargeInstance> mapCostComponentEntryToChargeInstance(
final Action action,
final Map.Entry<ChargeDefinition, CostComponent> costComponentEntry,
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
final ChargeDefinition chargeDefinition = costComponentEntry.getKey();
final BigDecimal chargeAmount = costComponentEntry.getValue().getAmount();
if (CostComponentService.chargeIsAccrued(chargeDefinition)) {
if (Action.valueOf(chargeDefinition.getAccrueAction()) == action)
return Optional.of(new ChargeInstance(
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
chargeAmount));
else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
return Optional.of(new ChargeInstance(
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
chargeAmount));
else
return Optional.empty();
}
else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
return Optional.of(new ChargeInstance(
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
chargeAmount));
else
return Optional.empty();
}
private Map<String, BigDecimal> getRequestedChargeAmounts(final @Nullable List<CostComponent> costComponents) {
if (costComponents == null)
return Collections.emptyMap();
else
return costComponents.stream()
.collect(Collectors.groupingBy(
CostComponent::getChargeIdentifier,
Collectors.reducing(BigDecimal.ZERO,
CostComponent::getAmount,
BigDecimal::add)));
}
private void checkIfTasksAreOutstanding(final DataContextOfAction dataContextOfAction, final Action action) {
final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
final String caseIdentifier = dataContextOfAction.getCustomerCaseEntity().getIdentifier();
final boolean tasksOutstanding = taskInstanceRepository.areTasksOutstanding(
productIdentifier, caseIdentifier, action.name());
if (tasksOutstanding)
throw ServiceException.conflict("Cannot execute action ''{0}'' for case ''{1}.{2}'' because tasks are incomplete.",
action.name(), productIdentifier, caseIdentifier);
}
private LocalDateTime today() {
return LocalDate.now(Clock.systemUTC()).atStartOfDay();
}
}