* Splitting out PaymentBuilderServices for each command to facilitate
unit testing of each payment builder.
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index a55bcea..31bbc67 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -28,10 +28,9 @@
import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.individuallending.internal.repository.CreditWorthinessFactorType;
-import io.mifos.individuallending.internal.service.costcomponent.CostComponentService;
+import io.mifos.individuallending.internal.service.costcomponent.*;
import io.mifos.individuallending.internal.service.DataContextOfAction;
import io.mifos.individuallending.internal.service.DataContextService;
-import io.mifos.individuallending.internal.service.costcomponent.PaymentBuilder;
import io.mifos.portfolio.api.v1.domain.*;
import io.mifos.portfolio.service.ServiceConstants;
import io.mifos.products.spi.PatternFactory;
@@ -42,6 +41,7 @@
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
@@ -108,13 +108,24 @@
AccountType.LIABILITY.name()));
INDIVIDUAL_LENDING_PATTERN.setAccountAssignmentsRequired(individualLendingRequiredAccounts);
}
+
+
public static Pattern individualLendingPattern() {
return INDIVIDUAL_LENDING_PATTERN;
}
private final CaseParametersRepository caseParametersRepository;
private final DataContextService dataContextService;
- private final CostComponentService costComponentService;
+ private final OpenPaymentBuilderService openPaymentBuilderService;
+ private final ApprovePaymentBuilderService approvePaymentBuilderService;
+ private final DenyPaymentBuilderService denyPaymentBuilderService;
+ private final DisbursePaymentBuilderService disbursePaymentBuilderService;
+ private final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService;
+ private final AcceptPaymentBuilderService acceptPaymentBuilderService;
+ private final ClosePaymentBuilderService closePaymentBuilderService;
+ private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+ private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
+ private final RecoverPaymentBuilderService recoverPaymentBuilderService;
private final CustomerManager customerManager;
private final IndividualLendingCommandDispatcher individualLendingCommandDispatcher;
private final Gson gson;
@@ -123,14 +134,33 @@
IndividualLendingPatternFactory(
final CaseParametersRepository caseParametersRepository,
final DataContextService dataContextService,
- final CostComponentService costComponentService,
+ final OpenPaymentBuilderService openPaymentBuilderService,
+ final ApprovePaymentBuilderService approvePaymentBuilderService,
+ final DenyPaymentBuilderService denyPaymentBuilderService,
+ final DisbursePaymentBuilderService disbursePaymentBuilderService,
+ final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService,
+ final AcceptPaymentBuilderService acceptPaymentBuilderService,
+ final ClosePaymentBuilderService closePaymentBuilderService,
+ final MarkLatePaymentBuilderService markLatePaymentBuilderService,
+ final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
+ final RecoverPaymentBuilderService recoverPaymentBuilderService,
final CustomerManager customerManager,
final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
@Qualifier(ServiceConstants.GSON_NAME) final Gson gson)
{
this.caseParametersRepository = caseParametersRepository;
this.dataContextService = dataContextService;
- this.costComponentService = costComponentService;
+ this.openPaymentBuilderService = openPaymentBuilderService;
+ this.approvePaymentBuilderService = approvePaymentBuilderService;
+ this.denyPaymentBuilderService = denyPaymentBuilderService;
+ this.disbursePaymentBuilderService = disbursePaymentBuilderService;
+ this.applyInterestPaymentBuilderService = applyInterestPaymentBuilderService;
+ this.acceptPaymentBuilderService = acceptPaymentBuilderService;
+ this.closePaymentBuilderService = closePaymentBuilderService;
+ this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+ this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
+ this.recoverPaymentBuilderService = recoverPaymentBuilderService;
+
this.customerManager = customerManager;
this.individualLendingCommandDispatcher = individualLendingCommandDispatcher;
this.gson = gson;
@@ -386,11 +416,60 @@
final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState());
checkActionCanBeExecuted(caseState, action);
- final PaymentBuilder paymentBuilder = costComponentService.getCostComponentsForAction(
+ return getPaymentForAction(
action,
dataContextOfAction,
+ forAccountDesignators,
forPaymentSize,
forDateTime.toLocalDate());
+ }
+
+ private Payment getPaymentForAction(
+ final Action action,
+ final DataContextOfAction dataContextOfAction,
+ final Set<String> forAccountDesignators,
+ final BigDecimal forPaymentSize,
+ final LocalDate forDate) {
+ final PaymentBuilderService paymentBuilderService;
+ switch (action) {
+ case OPEN:
+ paymentBuilderService = openPaymentBuilderService;
+ break;
+ case APPROVE:
+ paymentBuilderService = approvePaymentBuilderService;
+ break;
+ case DENY:
+ paymentBuilderService = denyPaymentBuilderService;
+ break;
+ case DISBURSE:
+ paymentBuilderService = disbursePaymentBuilderService;
+ break;
+ case APPLY_INTEREST:
+ paymentBuilderService = applyInterestPaymentBuilderService;
+ break;
+ case ACCEPT_PAYMENT:
+ paymentBuilderService = acceptPaymentBuilderService;
+ break;
+ case CLOSE:
+ paymentBuilderService = closePaymentBuilderService;
+ break;
+ case MARK_LATE:
+ paymentBuilderService = markLatePaymentBuilderService;
+ break;
+ case WRITE_OFF:
+ paymentBuilderService = writeOffPaymentBuilderService;
+ break;
+ case RECOVER:
+ paymentBuilderService = recoverPaymentBuilderService;
+ break;
+ default:
+ throw ServiceException.internalError("Invalid action: ''{0}''.", action.name());
+ }
+
+ final PaymentBuilder paymentBuilder = paymentBuilderService.getPaymentBuilder(
+ dataContextOfAction,
+ forPaymentSize,
+ forDate);
return paymentBuilder.buildPayment(action, forAccountDesignators);
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
index f3a3d9f..f980983 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
@@ -30,9 +30,8 @@
import io.mifos.individuallending.internal.command.*;
import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.individuallending.internal.service.*;
-import io.mifos.individuallending.internal.service.costcomponent.CostComponentService;
+import io.mifos.individuallending.internal.service.costcomponent.*;
import io.mifos.individuallending.internal.service.DataContextOfAction;
-import io.mifos.individuallending.internal.service.costcomponent.PaymentBuilder;
import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
@@ -67,7 +66,16 @@
public class IndividualLoanCommandHandler {
private final CaseRepository caseRepository;
private final DataContextService dataContextService;
- private final CostComponentService costComponentService;
+ private final OpenPaymentBuilderService openPaymentBuilderService;
+ private final ApprovePaymentBuilderService approvePaymentBuilderService;
+ private final DenyPaymentBuilderService denyPaymentBuilderService;
+ private final DisbursePaymentBuilderService disbursePaymentBuilderService;
+ private final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService;
+ private final AcceptPaymentBuilderService acceptPaymentBuilderService;
+ private final ClosePaymentBuilderService closePaymentBuilderService;
+ private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+ private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
+ private final RecoverPaymentBuilderService recoverPaymentBuilderService;
private final AccountingAdapter accountingAdapter;
private final TaskInstanceRepository taskInstanceRepository;
private final CaseParametersRepository caseParametersRepository;
@@ -76,13 +84,31 @@
public IndividualLoanCommandHandler(
final CaseRepository caseRepository,
final DataContextService dataContextService,
- final CostComponentService costComponentService,
+ final OpenPaymentBuilderService openPaymentBuilderService,
+ final ApprovePaymentBuilderService approvePaymentBuilderService,
+ final DenyPaymentBuilderService denyPaymentBuilderService,
+ final DisbursePaymentBuilderService disbursePaymentBuilderService,
+ final ApplyInterestPaymentBuilderService applyInterestPaymentBuilderService,
+ final AcceptPaymentBuilderService acceptPaymentBuilderService,
+ final ClosePaymentBuilderService closePaymentBuilderService,
+ final MarkLatePaymentBuilderService markLatePaymentBuilderService,
+ final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
+ final RecoverPaymentBuilderService recoverPaymentBuilderService,
final AccountingAdapter accountingAdapter,
final TaskInstanceRepository taskInstanceRepository,
final CaseParametersRepository caseParametersRepository) {
this.caseRepository = caseRepository;
this.dataContextService = dataContextService;
- this.costComponentService = costComponentService;
+ this.openPaymentBuilderService = openPaymentBuilderService;
+ this.approvePaymentBuilderService = approvePaymentBuilderService;
+ this.denyPaymentBuilderService = denyPaymentBuilderService;
+ this.disbursePaymentBuilderService = disbursePaymentBuilderService;
+ this.applyInterestPaymentBuilderService = applyInterestPaymentBuilderService;
+ this.acceptPaymentBuilderService = acceptPaymentBuilderService;
+ this.closePaymentBuilderService = closePaymentBuilderService;
+ this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+ this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
+ this.recoverPaymentBuilderService = recoverPaymentBuilderService;
this.accountingAdapter = accountingAdapter;
this.taskInstanceRepository = taskInstanceRepository;
this.caseParametersRepository = caseParametersRepository;
@@ -103,7 +129,7 @@
checkIfTasksAreOutstanding(dataContextOfAction, Action.OPEN);
final PaymentBuilder paymentBuilder
- = costComponentService.getCostComponentsForOpen(dataContextOfAction);
+ = openPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -140,7 +166,7 @@
checkIfTasksAreOutstanding(dataContextOfAction, Action.DENY);
final PaymentBuilder paymentBuilder
- = costComponentService.getCostComponentsForDeny(dataContextOfAction);
+ = denyPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -222,7 +248,7 @@
caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForApprove(dataContextOfAction);
+ approvePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
final List<ChargeInstance> charges = paymentBuilder.buildCharges(Action.APPROVE, designatorToAccountIdentifierMapper);
@@ -256,7 +282,7 @@
final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
+ disbursePaymentBuilderService.getPaymentBuilder(dataContextOfAction, disbursalAmount, CostComponentService.today());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -285,7 +311,7 @@
final String customerLoanFeesAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_FEES);
final BigDecimal currentBalance = accountingAdapter.getTotalOfCurrentAccountBalances(customerLoanPrinicipalAccountIdentifier, customerLoanInterestAccountIdentifier, customerLoanFeesAccountIdentifier);
- final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSizeForSingleDisbursement(
+ final BigDecimal newLoanPaymentSize = disbursePaymentBuilderService.getLoanPaymentSizeForSingleDisbursement(
currentBalance.add(disbursalAmount),
dataContextOfAction);
@@ -312,7 +338,7 @@
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForApplyInterest(dataContextOfAction);
+ applyInterestPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -347,7 +373,7 @@
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForAcceptPayment(
+ acceptPaymentBuilderService.getPaymentBuilder(
dataContextOfAction,
command.getCommand().getPaymentSize(),
DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate());
@@ -387,7 +413,7 @@
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForMarkLate(dataContextOfAction, DateConverter.fromIsoString(command.getForTime()));
+ markLatePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, DateConverter.fromIsoString(command.getForTime()).toLocalDate());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -439,7 +465,7 @@
checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
final PaymentBuilder paymentBuilder =
- costComponentService.getCostComponentsForClose(dataContextOfAction);
+ closePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today());
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
new file mode 100644
index 0000000..22bcb37
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class AcceptPaymentBuilderService implements PaymentBuilderService {
+ private final CostComponentService costComponentService;
+ private final ScheduledChargesService scheduledChargesService;
+ private final AccountingAdapter accountingAdapter;
+
+ @Autowired
+ public AcceptPaymentBuilderService(
+ final CostComponentService costComponentService,
+ final ScheduledChargesService scheduledChargesService,
+ final AccountingAdapter accountingAdapter) {
+ this.costComponentService = costComponentService;
+ this.scheduledChargesService = scheduledChargesService;
+ this.accountingAdapter = accountingAdapter;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal requestedLoanPaymentSize,
+ final LocalDate forDate)
+ {
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+ final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final ScheduledAction scheduledAction
+ = ScheduledActionHelpers.getNextScheduledPayment(
+ startOfTerm,
+ forDate,
+ dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
+ dataContextOfAction.getCaseParameters()
+ );
+
+ final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+ productIdentifier,
+ Collections.singletonList(scheduledAction));
+
+ final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
+ .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.ACCEPT_PAYMENT)));
+
+ final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+ .stream()
+ .map(ScheduledCharge::getChargeDefinition)
+ .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+ chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+
+ final BigDecimal loanPaymentSize;
+
+ if (requestedLoanPaymentSize != null) {
+ loanPaymentSize = requestedLoanPaymentSize;
+ }
+ else {
+ if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
+ loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
+ }
+ else {
+ final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
+ .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
+
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
+ .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
+ .map(entry -> entry.getValue().getAmount())
+ .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
+
+ loanPaymentSize = paymentSizeIncludingOnTopCharges;
+ }
+ }
+
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ accruedCostComponents,
+ chargesSplitIntoScheduledAndAccrued.get(false),
+ caseParameters.getBalanceRangeMaximum(),
+ runningBalances,
+ dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+ BigDecimal.ZERO,
+ loanPaymentSize,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java
new file mode 100644
index 0000000..620bc32
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.Period;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ApplyInterestPaymentBuilderService implements PaymentBuilderService {
+ private final CostComponentService costComponentService;
+ private final ScheduledChargesService scheduledChargesService;
+ private final AccountingAdapter accountingAdapter;
+
+ @Autowired
+ public ApplyInterestPaymentBuilderService(
+ final CostComponentService costComponentService,
+ final ScheduledChargesService scheduledChargesService,
+ final AccountingAdapter accountingAdapter) {
+ this.costComponentService = costComponentService;
+ this.scheduledChargesService = scheduledChargesService;
+ this.accountingAdapter = accountingAdapter;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+ final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, forDate, new Period(1, forDate));
+
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier,
+ Collections.singletonList(interestAction));
+
+ final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+ .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.APPLY_INTEREST)));
+
+ final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+ .stream()
+ .map(ScheduledCharge::getChargeDefinition)
+ .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+ chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ accruedCostComponents,
+ chargesSplitIntoScheduledAndAccrued.get(false),
+ caseParameters.getBalanceRangeMaximum(),
+ runningBalances,
+ dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java
new file mode 100644
index 0000000..3f7566d
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ApprovePaymentBuilderService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ApprovePaymentBuilderService implements PaymentBuilderService {
+ private final ScheduledChargesService scheduledChargesService;
+
+ @Autowired
+ public ApprovePaymentBuilderService(
+ final ScheduledChargesService scheduledChargesService) {
+ this.scheduledChargesService = scheduledChargesService;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ //Charge the approval fee if applicable.
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, forDate));
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier, scheduledActions);
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ Collections.emptyMap(),
+ scheduledCharges,
+ caseParameters.getBalanceRangeMaximum(),
+ new SimulatedRunningBalances(),
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java
new file mode 100644
index 0000000..cfa00e8
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/ClosePaymentBuilderService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.Period;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ClosePaymentBuilderService implements PaymentBuilderService {
+ private final CostComponentService costComponentService;
+ private final ScheduledChargesService scheduledChargesService;
+ private final AccountingAdapter accountingAdapter;
+
+ @Autowired
+ public ClosePaymentBuilderService(
+ final CostComponentService costComponentService,
+ final ScheduledChargesService scheduledChargesService,
+ final AccountingAdapter accountingAdapter) {
+ this.costComponentService = costComponentService;
+ this.scheduledChargesService = scheduledChargesService;
+ this.accountingAdapter = accountingAdapter;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+ if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP).compareTo(BigDecimal.ZERO) != 0)
+ throw ServiceException.conflict("Cannot close loan until the balance is zero.");
+
+ final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, forDate, new Period(1, forDate));
+
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier,
+ Collections.singletonList(closeAction));
+
+ final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+ .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.CLOSE)));
+
+ final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+ .stream()
+ .map(ScheduledCharge::getChargeDefinition)
+ .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+ chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ accruedCostComponents,
+ chargesSplitIntoScheduledAndAccrued.get(false),
+ caseParameters.getBalanceRangeMaximum(),
+ runningBalances,
+ dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
index 8aa2afb..3852772 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/CostComponentService.java
@@ -19,8 +19,10 @@
import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
-import io.mifos.individuallending.internal.repository.CaseParametersEntity;
-import io.mifos.individuallending.internal.service.*;
+import io.mifos.individuallending.internal.service.AnnuityPayment;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.RateCollectors;
import io.mifos.individuallending.internal.service.schedule.*;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.api.v1.domain.CostComponent;
@@ -30,8 +32,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import javax.money.MonetaryAmount;
import java.math.BigDecimal;
import java.time.Clock;
@@ -49,380 +49,13 @@
private static final int EXTRA_PRECISION = 4;
private static final int RUNNING_CALCULATION_PRECISION = 8;
- private final ScheduledChargesService scheduledChargesService;
private final AccountingAdapter accountingAdapter;
@Autowired
- public CostComponentService(
- final ScheduledChargesService scheduledChargesService,
- final AccountingAdapter accountingAdapter) {
- this.scheduledChargesService = scheduledChargesService;
+ public CostComponentService(final AccountingAdapter accountingAdapter) {
this.accountingAdapter = accountingAdapter;
}
- public PaymentBuilder getCostComponentsForAction(
- final Action action,
- final DataContextOfAction dataContextOfAction,
- final BigDecimal forPaymentSize,
- final LocalDate forDate) {
- switch (action) {
- case OPEN:
- return getCostComponentsForOpen(dataContextOfAction);
- case APPROVE:
- return getCostComponentsForApprove(dataContextOfAction);
- case DENY:
- return getCostComponentsForDeny(dataContextOfAction);
- case DISBURSE:
- return getCostComponentsForDisburse(dataContextOfAction, forPaymentSize);
- case APPLY_INTEREST:
- return getCostComponentsForApplyInterest(dataContextOfAction);
- case ACCEPT_PAYMENT:
- return getCostComponentsForAcceptPayment(dataContextOfAction, forPaymentSize, forDate);
- case CLOSE:
- return getCostComponentsForClose(dataContextOfAction);
- case MARK_LATE:
- return getCostComponentsForMarkLate(dataContextOfAction, today().atStartOfDay());
- case WRITE_OFF:
- return getCostComponentsForWriteOff(dataContextOfAction);
- case RECOVER:
- return getCostComponentsForRecover(dataContextOfAction);
- default:
- throw ServiceException.internalError("Invalid action: ''{0}''.", action.name());
- }
- }
-
- public PaymentBuilder getCostComponentsForOpen(final DataContextOfAction dataContextOfAction) {
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, today()));
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier, scheduledActions);
-
- return getCostComponentsForScheduledCharges(
- Collections.emptyMap(),
- scheduledCharges,
- caseParameters.getBalanceRangeMaximum(),
- new SimulatedRunningBalances(),
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, today()));
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier, scheduledActions);
-
- return getCostComponentsForScheduledCharges(
- Collections.emptyMap(),
- scheduledCharges,
- caseParameters.getBalanceRangeMaximum(),
- new SimulatedRunningBalances(),
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
- //Charge the approval fee if applicable.
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, today()));
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier, scheduledActions);
-
- return getCostComponentsForScheduledCharges(
- Collections.emptyMap(),
- scheduledCharges,
- caseParameters.getBalanceRangeMaximum(),
- new SimulatedRunningBalances(),
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForDisburse(
- final @Nonnull DataContextOfAction dataContextOfAction,
- final @Nullable BigDecimal requestedDisbursalSize) {
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
- = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
- final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
- final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
- final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
-
- if (requestedDisbursalSize != null &&
- dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
- currentBalance.add(requestedDisbursalSize)) < 0)
- throw ServiceException.conflict("Cannot disburse over the maximum balance.");
-
- final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
- customerLoanPrincipalAccountIdentifier,
- dataContextOfAction.getMessageForCharge(Action.DISBURSE));
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
-
- final BigDecimal disbursalSize;
- if (requestedDisbursalSize == null)
- disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum();
- else
- disbursalSize = requestedDisbursalSize;
-
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier, scheduledActions);
-
-
- final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
- .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.DISBURSE)));
-
- final Map<ChargeDefinition, CostComponent> accruedCostComponents =
- optionalStartOfTerm.map(startOfTerm ->
- chargesSplitIntoScheduledAndAccrued.get(true)
- .stream()
- .map(ScheduledCharge::getChargeDefinition)
- .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
- chargeDefinition -> getAccruedCostComponentToApply(
- dataContextOfAction,
- designatorToAccountIdentifierMapper,
- startOfTerm.toLocalDate(),
- chargeDefinition)))).orElse(Collections.emptyMap());
-
- return getCostComponentsForScheduledCharges(
- accruedCostComponents,
- chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getBalanceRangeMaximum(),
- runningBalances,
- dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
- disbursalSize,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForApplyInterest(
- final DataContextOfAction dataContextOfAction)
- {
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
- = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
- final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
- final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final LocalDate today = today();
- final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, today, new Period(1, today));
-
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier,
- Collections.singletonList(interestAction));
-
- final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
- .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.APPLY_INTEREST)));
-
- final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
- .stream()
- .map(ScheduledCharge::getChargeDefinition)
- .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
- chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
- return getCostComponentsForScheduledCharges(
- accruedCostComponents,
- chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getBalanceRangeMaximum(),
- runningBalances,
- dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForAcceptPayment(
- final DataContextOfAction dataContextOfAction,
- final @Nullable BigDecimal requestedLoanPaymentSize,
- final LocalDate forDate)
- {
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
- = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
- final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
- final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final ScheduledAction scheduledAction
- = ScheduledActionHelpers.getNextScheduledPayment(
- startOfTerm,
- forDate,
- dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
- dataContextOfAction.getCaseParameters()
- );
-
- final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
- productIdentifier,
- Collections.singletonList(scheduledAction));
-
- final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
- .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.ACCEPT_PAYMENT)));
-
- final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
- .stream()
- .map(ScheduledCharge::getChargeDefinition)
- .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
- chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-
- final BigDecimal loanPaymentSize;
-
- if (requestedLoanPaymentSize != null) {
- loanPaymentSize = requestedLoanPaymentSize;
- }
- else {
- if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
- loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
- }
- else {
- final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
- .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
-
- @SuppressWarnings("UnnecessaryLocalVariable")
- final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
- .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
- .map(entry -> entry.getValue().getAmount())
- .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
-
- loanPaymentSize = paymentSizeIncludingOnTopCharges;
- }
- }
-
-
- return getCostComponentsForScheduledCharges(
- accruedCostComponents,
- chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getBalanceRangeMaximum(),
- runningBalances,
- dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
- BigDecimal.ZERO,
- loanPaymentSize,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
- = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
- final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
- if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP).compareTo(BigDecimal.ZERO) != 0)
- throw ServiceException.conflict("Cannot close loan until the balance is zero.");
-
- final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final LocalDate today = today();
- final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, today, new Period(1, today));
-
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier,
- Collections.singletonList(closeAction));
-
- final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
- .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.CLOSE)));
-
- final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
- .stream()
- .map(ScheduledCharge::getChargeDefinition)
- .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
- chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
- return getCostComponentsForScheduledCharges(
- accruedCostComponents,
- chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getBalanceRangeMaximum(),
- runningBalances,
- dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- public PaymentBuilder getCostComponentsForMarkLate(
- final DataContextOfAction dataContextOfAction,
- final LocalDateTime forTime)
- {
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
- = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
- final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-
- final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
-
- final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
- final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
- final ScheduledAction scheduledAction = new ScheduledAction(Action.MARK_LATE, forTime.toLocalDate());
-
- final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
-
- final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
- productIdentifier,
- Collections.singletonList(scheduledAction));
-
- final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
- .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.MARK_LATE)));
-
- final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
- .stream()
- .map(ScheduledCharge::getChargeDefinition)
- .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
- chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
-
-
- return getCostComponentsForScheduledCharges(
- accruedCostComponents,
- chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getBalanceRangeMaximum(),
- runningBalances,
- loanPaymentSize,
- BigDecimal.ZERO,
- BigDecimal.ZERO,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- true);
- }
-
- private PaymentBuilder getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
- return null;
- }
-
- private PaymentBuilder getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
- return null;
- }
-
public static PaymentBuilder getCostComponentsForScheduledCharges(
final Map<ChargeDefinition, CostComponent> accruedCostComponents,
final Collection<ScheduledCharge> scheduledCharges,
@@ -556,23 +189,6 @@
}
}
- public BigDecimal getLoanPaymentSizeForSingleDisbursement(
- final BigDecimal disbursementSize,
- final DataContextOfAction dataContextOfAction) {
- final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
- today(),
- dataContextOfAction.getCaseParameters());
- final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
- dataContextOfAction.getProductEntity().getIdentifier(),
- hypotheticalScheduledActions);
- return getLoanPaymentSize(
- disbursementSize,
- disbursementSize,
- dataContextOfAction.getInterest(),
- dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
- hypotheticalScheduledCharges);
- }
-
public static BigDecimal getLoanPaymentSize(
final BigDecimal maximumBalanceSize,
final BigDecimal disbursementSize,
@@ -616,7 +232,7 @@
return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact()).setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
}
- private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+ static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
return chargeDefinition.getAccrueAction() != null &&
chargeDefinition.getChargeAction().equals(action.name());
}
@@ -626,10 +242,10 @@
chargeDefinition.getAccrueAction().equals(action.name());
}
- private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
- final LocalDate startOfTerm,
- final ChargeDefinition chargeDefinition) {
+ CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
+ final LocalDate startOfTerm,
+ final ChargeDefinition chargeDefinition) {
final CostComponent ret = new CostComponent();
final String accrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator());
@@ -648,8 +264,8 @@
return ret;
}
- private LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
- final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+ LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
@@ -663,7 +279,7 @@
dataContextOfAction.getCompoundIdentifer()));
}
- private static LocalDate today() {
+ public static LocalDate today() {
return LocalDate.now(Clock.systemUTC());
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java
new file mode 100644
index 0000000..1912617
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DenyPaymentBuilderService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class DenyPaymentBuilderService implements PaymentBuilderService {
+ private final ScheduledChargesService scheduledChargesService;
+
+ @Autowired
+ public DenyPaymentBuilderService(
+ final ScheduledChargesService scheduledChargesService) {
+ this.scheduledChargesService = scheduledChargesService;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, forDate));
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier, scheduledActions);
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ Collections.emptyMap(),
+ scheduledCharges,
+ caseParameters.getBalanceRangeMaximum(),
+ new SimulatedRunningBalances(),
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java
new file mode 100644
index 0000000..044d79c
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderService.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class DisbursePaymentBuilderService implements PaymentBuilderService {
+ private final CostComponentService costComponentService;
+ private final ScheduledChargesService scheduledChargesService;
+ private final AccountingAdapter accountingAdapter;
+
+ @Autowired
+ public DisbursePaymentBuilderService(
+ final CostComponentService costComponentService,
+ final ScheduledChargesService scheduledChargesService,
+ final AccountingAdapter accountingAdapter) {
+ this.costComponentService = costComponentService;
+ this.scheduledChargesService = scheduledChargesService;
+ this.accountingAdapter = accountingAdapter;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final @Nonnull DataContextOfAction dataContextOfAction,
+ final @Nullable BigDecimal requestedDisbursalSize,
+ final LocalDate forDate)
+ {
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+ final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+ final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+
+ if (requestedDisbursalSize != null &&
+ dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
+ currentBalance.add(requestedDisbursalSize)) < 0)
+ throw ServiceException.conflict("Cannot disburse over the maximum balance.");
+
+ final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
+ customerLoanPrincipalAccountIdentifier,
+ dataContextOfAction.getMessageForCharge(Action.DISBURSE));
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, forDate));
+
+ final BigDecimal disbursalSize;
+ if (requestedDisbursalSize == null)
+ disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum();
+ else
+ disbursalSize = requestedDisbursalSize;
+
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier, scheduledActions);
+
+
+ final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+ .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.DISBURSE)));
+
+ final Map<ChargeDefinition, CostComponent> accruedCostComponents =
+ optionalStartOfTerm.map(startOfTerm ->
+ chargesSplitIntoScheduledAndAccrued.get(true)
+ .stream()
+ .map(ScheduledCharge::getChargeDefinition)
+ .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+ chargeDefinition -> costComponentService.getAccruedCostComponentToApply(
+ dataContextOfAction,
+ designatorToAccountIdentifierMapper,
+ startOfTerm.toLocalDate(),
+ chargeDefinition)))).orElse(Collections.emptyMap());
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ accruedCostComponents,
+ chargesSplitIntoScheduledAndAccrued.get(false),
+ caseParameters.getBalanceRangeMaximum(),
+ runningBalances,
+ dataContextOfAction.getCaseParametersEntity().getPaymentSize(),
+ disbursalSize,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+
+ public BigDecimal getLoanPaymentSizeForSingleDisbursement(
+ final BigDecimal disbursementSize,
+ final DataContextOfAction dataContextOfAction) {
+ final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
+ CostComponentService.today(),
+ dataContextOfAction.getCaseParameters());
+ final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
+ dataContextOfAction.getProductEntity().getIdentifier(),
+ hypotheticalScheduledActions);
+ return CostComponentService.getLoanPaymentSize(
+ disbursementSize,
+ disbursementSize,
+ dataContextOfAction.getInterest(),
+ dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
+ hypotheticalScheduledCharges);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
new file mode 100644
index 0000000..a1667b5
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class MarkLatePaymentBuilderService implements PaymentBuilderService {
+ private final CostComponentService costComponentService;
+ private final ScheduledChargesService scheduledChargesService;
+ private final AccountingAdapter accountingAdapter;
+
+ @Autowired
+ public MarkLatePaymentBuilderService(
+ final CostComponentService costComponentService,
+ final ScheduledChargesService scheduledChargesService,
+ final AccountingAdapter accountingAdapter) {
+ this.costComponentService = costComponentService;
+ this.scheduledChargesService = scheduledChargesService;
+ this.accountingAdapter = accountingAdapter;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final @Nonnull DataContextOfAction dataContextOfAction,
+ final @Nullable BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
+
+ final LocalDate startOfTerm = costComponentService.getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
+
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final ScheduledAction scheduledAction = new ScheduledAction(Action.MARK_LATE, forDate);
+
+ final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+ final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+ productIdentifier,
+ Collections.singletonList(scheduledAction));
+
+ final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
+ .collect(Collectors.partitioningBy(x -> CostComponentService.isAccruedChargeForAction(x.getChargeDefinition(), Action.MARK_LATE)));
+
+ final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+ .stream()
+ .map(ScheduledCharge::getChargeDefinition)
+ .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+ chargeDefinition -> costComponentService.getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ accruedCostComponents,
+ chargesSplitIntoScheduledAndAccrued.get(false),
+ caseParameters.getBalanceRangeMaximum(),
+ runningBalances,
+ loanPaymentSize,
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java
new file mode 100644
index 0000000..a1e05c6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/OpenPaymentBuilderService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class OpenPaymentBuilderService implements PaymentBuilderService {
+ private final ScheduledChargesService scheduledChargesService;
+
+ @Autowired
+ public OpenPaymentBuilderService(
+ final ScheduledChargesService scheduledChargesService) {
+ this.scheduledChargesService = scheduledChargesService;
+ }
+
+ public PaymentBuilder getPaymentBuilder(
+ final DataContextOfAction dataContextOfAction,
+ final BigDecimal ignored,
+ final LocalDate forDate)
+ {
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+ final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, forDate));
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+ productIdentifier, scheduledActions);
+
+ return CostComponentService.getCostComponentsForScheduledCharges(
+ Collections.emptyMap(),
+ scheduledCharges,
+ caseParameters.getBalanceRangeMaximum(),
+ new SimulatedRunningBalances(),
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ BigDecimal.ZERO,
+ dataContextOfAction.getInterest(),
+ minorCurrencyUnitDigits,
+ true);
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
new file mode 100644
index 0000000..79914a2
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
@@ -0,0 +1,15 @@
+package io.mifos.individuallending.internal.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+public interface PaymentBuilderService {
+ PaymentBuilder getPaymentBuilder(
+ final @Nonnull DataContextOfAction dataContextOfAction,
+ final @Nullable BigDecimal requestedDisbursalSize,
+ final LocalDate forDate);
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
new file mode 100644
index 0000000..e3b01a6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class RecoverPaymentBuilderService implements PaymentBuilderService {
+ @Override
+ public PaymentBuilder getPaymentBuilder(@Nonnull DataContextOfAction dataContextOfAction, @Nullable BigDecimal requestedDisbursalSize, LocalDate forDate) {
+ return null;
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
new file mode 100644
index 0000000..8d392b9
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.service.costcomponent;
+
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class WriteOffPaymentBuilderService implements PaymentBuilderService {
+ @Override
+ public PaymentBuilder getPaymentBuilder(@Nonnull DataContextOfAction dataContextOfAction, @Nullable BigDecimal requestedDisbursalSize, LocalDate forDate) {
+ return null;
+ }
+}