Added product arrears configuration, and began drawing on it to
calculate loan loss provisioning upon disbursement.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
index 3fd48ff..fb78e89 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
@@ -35,6 +35,8 @@
String INTEREST_ACCRUAL = "ia";
String LATE_FEE_INCOME = "lfi";
String LATE_FEE_ACCRUAL = "lfa";
- String ARREARS_ALLOWANCE = "aa";
+ String PRODUCT_LOSS_ALLOWANCE = "pa";
+ String GENERAL_LOSS_ALLOWANCE = "aa";
+ String GENERAL_EXPENSE = "ge";
String ENTRY = "ey";
}
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
index 2f570ac..8167c85 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
@@ -42,6 +42,8 @@
String REPAY_INTEREST_ID = "repay-interest";
String REPAY_FEES_NAME = "Repay fees";
String REPAY_FEES_ID = "repay-fees";
+ String PROVISION_FOR_LOSSES_NAME = "Provision for losses";
+ String PROVISION_FOR_LOSSES_ID = "loss-provisioning";
static String nameToIdentifier(String name) {
return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index 3a77a72..5054006 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -67,7 +67,9 @@
accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER));
accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, LATE_FEE_INCOME_ACCOUNT_IDENTIFIER));
accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER));
- accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER));
+ accountAssignments.add(new AccountAssignment(PRODUCT_LOSS_ALLOWANCE, ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER));
+ accountAssignments.add(new AccountAssignment(GENERAL_LOSS_ALLOWANCE, ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER));
+ accountAssignments.add(new AccountAssignment(GENERAL_EXPENSE, ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER));
//accountAssignments.add(new AccountAssignment(ENTRY, ...));
// Don't assign entry account in test since it usually will not be assigned IRL.
accountAssignments.add(new AccountAssignment(LOAN_FUNDS_SOURCE, LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER));
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index 36e1023..59c0c31 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -75,7 +75,7 @@
chargeDefinitionToDelete.setDescription("blah blah blah");
chargeDefinitionToDelete.setChargeAction(Action.APPROVE.name());
chargeDefinitionToDelete.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
- chargeDefinitionToDelete.setToAccountDesignator(AccountDesignators.ARREARS_ALLOWANCE);
+ chargeDefinitionToDelete.setToAccountDesignator(AccountDesignators.GENERAL_LOSS_ALLOWANCE);
chargeDefinitionToDelete.setFromAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
portfolioManager.createChargeDefinition(product.getIdentifier(), chargeDefinitionToDelete);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_CHARGE_DEFINITION,
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index cf00030..59c2f47 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -100,8 +100,14 @@
AccountDesignators.LATE_FEE_ACCRUAL,
AccountType.REVENUE.name()));
individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
- AccountDesignators.ARREARS_ALLOWANCE,
- AccountType.LIABILITY.name())); //TODO: type?
+ AccountDesignators.PRODUCT_LOSS_ALLOWANCE,
+ AccountType.ASSET.name()));
+ individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+ AccountDesignators.GENERAL_LOSS_ALLOWANCE,
+ AccountType.EXPENSE.name()));
+ individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+ AccountDesignators.GENERAL_EXPENSE,
+ AccountType.EXPENSE.name()));
individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
AccountDesignators.ENTRY,
AccountType.LIABILITY.name()));
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java
index 413668f..78d8ef5 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java
@@ -102,22 +102,14 @@
disbursePayment.setAmount(BigDecimal.valueOf(100));
disbursePayment.setReadOnly(true);
- //TODO: Make multiple write off allowance defaultCharges.
- /*final ChargeDefinition writeOffAllowanceCharge = charge(
- ALLOW_FOR_WRITE_OFF_NAME,
- Action.MARK_LATE,
- BigDecimal.valueOf(30),
- AccountDesignators.LOAN_FUNDS_SOURCE,
- AccountDesignators.ARREARS_ALLOWANCE);
- writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
- writeOffAllowanceCharge.setReadOnly(true);*/
-
- final ChargeDefinition interestCharge = charge(
- INTEREST_NAME,
- Action.ACCEPT_PAYMENT,
- BigDecimal.valueOf(100),
- AccountDesignators.CUSTOMER_LOAN_INTEREST,
- AccountDesignators.INTEREST_INCOME);
+ final ChargeDefinition interestCharge = new ChargeDefinition();
+ interestCharge.setIdentifier(INTEREST_ID);
+ interestCharge.setName(INTEREST_NAME);
+ interestCharge.setDescription(INTEREST_NAME);
+ interestCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+ interestCharge.setAmount(BigDecimal.valueOf(100));
+ interestCharge.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+ interestCharge.setToAccountDesignator(AccountDesignators.INTEREST_INCOME);
interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
interestCharge.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
@@ -162,7 +154,6 @@
customerPrincipalRepaymentCharge.setReadOnly(true);
ret.add(disbursePayment);
- //ret.add(writeOffAllowanceCharge);
ret.add(interestCharge);
ret.add(customerPrincipalRepaymentCharge);
ret.add(customerInterestRepaymentCharge);
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
index 597bf00..03c1fac 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
@@ -21,6 +21,7 @@
import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
+import io.mifos.portfolio.service.internal.repository.ProductArrearsConfigurationEntity;
import io.mifos.portfolio.service.internal.repository.ProductEntity;
import javax.annotation.Nonnull;
@@ -36,15 +37,20 @@
private final ProductEntity product;
private final CaseEntity customerCase;
private final CaseParametersEntity caseParameters;
+ private final List<ProductArrearsConfigurationEntity> productArrearsConfigurationEntities;
private final List<AccountAssignment> oneTimeAccountAssignments;
- public DataContextOfAction(final @Nonnull ProductEntity product,
- final @Nonnull CaseEntity customerCase,
- final @Nonnull CaseParametersEntity caseParameters,
- final @Nullable List<AccountAssignment> oneTimeAccountAssignments) {
+ public DataContextOfAction(
+ final @Nonnull ProductEntity product,
+ final @Nonnull CaseEntity customerCase,
+ final @Nonnull CaseParametersEntity caseParameters,
+ final List<ProductArrearsConfigurationEntity> productArrearsConfigurationEntities,
+ final @Nullable List<AccountAssignment> oneTimeAccountAssignments)
+ {
this.product = product;
this.customerCase = customerCase;
this.caseParameters = caseParameters;
+ this.productArrearsConfigurationEntities = productArrearsConfigurationEntities;
this.oneTimeAccountAssignments = oneTimeAccountAssignments == null ? Collections.emptyList() : oneTimeAccountAssignments;
}
@@ -64,6 +70,10 @@
return CaseParametersMapper.mapEntity(caseParameters, product.getMinorCurrencyUnitDigits());
}
+ public @Nonnull List<ProductArrearsConfigurationEntity> getProductArrearsConfigurationEntities() {
+ return productArrearsConfigurationEntities;
+ }
+
@Nonnull List<AccountAssignment> getOneTimeAccountAssignments() {
return oneTimeAccountAssignments;
}
@@ -79,4 +89,4 @@
public BigDecimal getInterest() {
return customerCase.getInterest();
}
-}
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
index 388ae0c..eb04895 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
@@ -19,10 +19,7 @@
import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
-import io.mifos.portfolio.service.internal.repository.CaseEntity;
-import io.mifos.portfolio.service.internal.repository.CaseRepository;
-import io.mifos.portfolio.service.internal.repository.ProductEntity;
-import io.mifos.portfolio.service.internal.repository.ProductRepository;
+import io.mifos.portfolio.service.internal.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -37,15 +34,18 @@
private final ProductRepository productRepository;
private final CaseRepository caseRepository;
private final CaseParametersRepository caseParametersRepository;
+ private final ProductArrearsConfigurationRepository productArrearsConfigurationRepository;
@Autowired
public DataContextService(
final ProductRepository productRepository,
final CaseRepository caseRepository,
- final CaseParametersRepository caseParametersRepository) {
+ final CaseParametersRepository caseParametersRepository,
+ final ProductArrearsConfigurationRepository productArrearsConfigurationRepository) {
this.productRepository = productRepository;
this.caseRepository = caseRepository;
this.caseParametersRepository = caseParametersRepository;
+ this.productArrearsConfigurationRepository = productArrearsConfigurationRepository;
}
public DataContextOfAction checkedGetDataContext(
@@ -66,6 +66,14 @@
"Individual loan not found ''{0}.{1}''.",
productIdentifier, caseIdentifier));
- return new DataContextOfAction(product, customerCase, caseParameters, oneTimeAccountAssignments);
+ final List<ProductArrearsConfigurationEntity> productArrearsConfigurationEntities
+ = productArrearsConfigurationRepository.findByProductId(product.getId());
+
+ return new DataContextOfAction(
+ product,
+ customerCase,
+ caseParameters,
+ productArrearsConfigurationEntities,
+ oneTimeAccountAssignments);
}
-}
+}
\ No newline at end of file
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 fd62325..6bb7b51 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
@@ -51,7 +51,7 @@
final BigDecimal contractualRepayment,
final BigDecimal requestedDisbursement,
final BigDecimal requestedRepayment,
- final BigDecimal interest,
+ final BigDecimal percentPoints,
final int minorCurrencyUnitDigits,
final boolean accrualAccounting) {
final PaymentBuilder paymentBuilder = new PaymentBuilder(preChargeBalances, accrualAccounting);
@@ -73,7 +73,7 @@
!x.amountIsWithinRange(amountProportionalTo)).orElse(false))
continue;
- chargeAmount = howToApplyScheduledChargeToAmount(scheduledCharge, interest)
+ chargeAmount = howToApplyScheduledChargeToAmount(scheduledCharge, percentPoints)
.apply(amountProportionalTo)
.setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
}
@@ -156,7 +156,7 @@
}
private static Function<BigDecimal, BigDecimal> howToApplyScheduledChargeToAmount(
- final ScheduledCharge scheduledCharge, final BigDecimal interest)
+ final ScheduledCharge scheduledCharge, final BigDecimal percentPoints)
{
switch (scheduledCharge.getChargeDefinition().getChargeMethod())
{
@@ -168,7 +168,7 @@
return chargeAmountPerPeriod::multiply;
}
case INTEREST: {
- final BigDecimal chargeAmountPerPeriod = PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, interest, RUNNING_CALCULATION_PRECISION);
+ final BigDecimal chargeAmountPerPeriod = PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, percentPoints, RUNNING_CALCULATION_PRECISION);
return chargeAmountPerPeriod::multiply;
}
default: {
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
index 5b909eb..57a80f4 100644
--- 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
@@ -20,10 +20,7 @@
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.ScheduledActionHelpers;
-import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
-import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import io.mifos.individuallending.internal.service.schedule.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -33,6 +30,7 @@
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
/**
* @author Myrle Krantz
@@ -40,11 +38,14 @@
@Service
public class DisbursePaymentBuilderService implements PaymentBuilderService {
private final ScheduledChargesService scheduledChargesService;
+ private final LossProvisioningService lossProvisioningService;
@Autowired
public DisbursePaymentBuilderService(
- final ScheduledChargesService scheduledChargesService) {
+ final ScheduledChargesService scheduledChargesService,
+ final LossProvisioningService lossProvisioningService) {
this.scheduledChargesService = scheduledChargesService;
+ this.lossProvisioningService = lossProvisioningService;
}
@Override
@@ -74,7 +75,9 @@
final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
productIdentifier, scheduledActions);
-
+ final Optional<ScheduledCharge> initialLossProvisionCharge = lossProvisioningService.getScheduledChargeForDisbursement(
+ dataContextOfAction, forDate);
+ initialLossProvisionCharge.ifPresent(scheduledCharges::add);
return CostComponentService.getCostComponentsForScheduledCharges(
scheduledCharges,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
index db7ecb9..31f325f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
@@ -48,8 +48,11 @@
this.put(AccountDesignators.INTEREST_ACCRUAL, positive);
this.put(AccountDesignators.LATE_FEE_INCOME, positive);
this.put(AccountDesignators.LATE_FEE_ACCRUAL, positive);
- this.put(AccountDesignators.ARREARS_ALLOWANCE, positive);
+ this.put(AccountDesignators.PRODUCT_LOSS_ALLOWANCE, negative);
+ this.put(AccountDesignators.GENERAL_LOSS_ALLOWANCE, negative);
+ this.put(AccountDesignators.GENERAL_EXPENSE, negative);
this.put(AccountDesignators.ENTRY, positive);
+ //TODO: derive signs from IndividualLendingPatternFactory.individualLendingRequiredAccounts instead.
}};
BigDecimal getAccountBalance(final String accountDesignator);
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisioningService.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisioningService.java
new file mode 100644
index 0000000..bdfb7fe
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisioningService.java
@@ -0,0 +1,102 @@
+/*
+ * 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.schedule;
+
+import io.mifos.core.lang.ServiceException;
+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.service.DataContextOfAction;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.service.internal.repository.ProductArrearsConfigurationEntity;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROVISION_FOR_LOSSES_ID;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROVISION_FOR_LOSSES_NAME;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class LossProvisioningService {
+ public Optional<ScheduledCharge> getScheduledChargeForMarkLate(
+ final DataContextOfAction dataContextOfAction,
+ final LocalDate forDate,
+ final int daysLate)
+ {
+ return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, daysLate, Action.MARK_LATE);
+ }
+
+
+ public Optional<ScheduledCharge> getScheduledChargeForDisbursement(
+ final DataContextOfAction dataContextOfAction,
+ final LocalDate forDate)
+ {
+ return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, 0, Action.DISBURSE);
+ }
+
+ private Optional<ScheduledCharge> getScheduledLossProvisioningCharge(
+ final DataContextOfAction dataContextOfAction,
+ final LocalDate forDate,
+ final int daysLate, Action action) {
+ final Optional<ChargeDefinition> optionalChargeDefinition = percentProvision(dataContextOfAction, daysLate)
+ .map(this::getLossProvisionCharge);
+
+ return optionalChargeDefinition.map(chargeDefinition -> {
+ final ScheduledAction scheduledAction = new ScheduledAction(action, forDate);
+ return new ScheduledCharge(scheduledAction, chargeDefinition, Optional.empty());
+ });
+ }
+
+ private Optional<BigDecimal> percentProvision(
+ final DataContextOfAction dataContextOfAction,
+ final int daysLate)
+ {
+ final List<ProductArrearsConfigurationEntity> arrearsConfigurationForGivenDaysLate =
+ dataContextOfAction.getProductArrearsConfigurationEntities().stream()
+ .filter(x -> x.getDaysLate() == daysLate)
+ .collect(Collectors.toList());
+
+ if (arrearsConfigurationForGivenDaysLate.size() > 1)
+ throw ServiceException.internalError("There should not be more than one arrears allocation for given # of days late.");
+ if (arrearsConfigurationForGivenDaysLate.size() == 0)
+ return Optional.empty(); //None
+
+ return Optional.of(arrearsConfigurationForGivenDaysLate.get(0).getPercentProvision());
+ }
+
+ private ChargeDefinition getLossProvisionCharge(final BigDecimal percentProvision) {
+ final ChargeDefinition ret = new ChargeDefinition();
+ ret.setChargeAction(Action.MARK_LATE.name());
+ ret.setIdentifier(PROVISION_FOR_LOSSES_ID);
+ ret.setName(PROVISION_FOR_LOSSES_NAME);
+ ret.setDescription(PROVISION_FOR_LOSSES_NAME);
+ ret.setFromAccountDesignator(AccountDesignators.PRODUCT_LOSS_ALLOWANCE);
+ ret.setAccrualAccountDesignator(AccountDesignators.GENERAL_LOSS_ALLOWANCE);
+ ret.setToAccountDesignator(AccountDesignators.GENERAL_EXPENSE);
+ ret.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_DESIGNATOR.getValue());
+ ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+ ret.setAmount(percentProvision);
+ ret.setReadOnly(true);
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledAction.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledAction.java
index bca424a..098877e 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledAction.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledAction.java
@@ -26,32 +26,35 @@
* @author Myrle Krantz
*/
public class ScheduledAction {
- final Action action;
- final LocalDate when;
+ private final Action action;
+ private final LocalDate when;
private final @Nullable Period actionPeriod;
private final @Nullable Period repaymentPeriod;
- public ScheduledAction(@Nonnull final Action action,
- @Nonnull final LocalDate when,
- @Nonnull final Period actionPeriod,
- @Nonnull final Period repaymentPeriod) {
+ public ScheduledAction(
+ @Nonnull final Action action,
+ @Nonnull final LocalDate when,
+ @Nonnull final Period actionPeriod,
+ @Nonnull final Period repaymentPeriod) {
this.action = action;
this.when = when;
this.actionPeriod = actionPeriod;
this.repaymentPeriod = repaymentPeriod;
}
- public ScheduledAction(@Nonnull final Action action,
- @Nonnull final LocalDate when,
- @Nonnull final Period actionPeriod) {
+ public ScheduledAction(
+ @Nonnull final Action action,
+ @Nonnull final LocalDate when,
+ @Nonnull final Period actionPeriod) {
this.action = action;
this.when = when;
this.actionPeriod = actionPeriod;
this.repaymentPeriod = null;
}
- public ScheduledAction(@Nonnull final Action action,
- @Nonnull final LocalDate when) {
+ public ScheduledAction(
+ @Nonnull final Action action,
+ @Nonnull final LocalDate when) {
this.action = action;
this.when = when;
this.actionPeriod = null;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledActionHelpers.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledActionHelpers.java
index af85844..1807136 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledActionHelpers.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledActionHelpers.java
@@ -61,7 +61,7 @@
final LocalDate effectiveEndOfTerm = fromDate.isAfter(endOfTerm) ? fromDate : endOfTerm;
return getHypotheticalScheduledActionsForDisbursedLoan(startOfTerm, effectiveEndOfTerm, caseParameters)
- .filter(x -> x.action.equals(Action.ACCEPT_PAYMENT))
+ .filter(x -> x.getAction().equals(Action.ACCEPT_PAYMENT))
.filter(x -> x.actionIsOnOrAfter(fromDate))
.findFirst()
.orElseGet(() -> new ScheduledAction(Action.ACCEPT_PAYMENT, fromDate));
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargeComparator.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargeComparator.java
index 5840cdb..9478c28 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargeComparator.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargeComparator.java
@@ -33,11 +33,11 @@
}
static int compareScheduledCharges(ScheduledCharge o1, ScheduledCharge o2) {
- int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
+ int ret = o1.getScheduledAction().getWhen().compareTo(o2.getScheduledAction().getWhen());
if (ret != 0)
return ret;
- ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
+ ret = o1.getScheduledAction().getAction().compareTo(o2.getScheduledAction().getAction());
if (ret != 0)
return ret;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
index 9ec708f..bd18737 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
@@ -147,13 +147,13 @@
final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction,
final ScheduledAction scheduledAction) {
final List<ChargeDefinition> chargeMappingList = chargeDefinitionsMappedByChargeAction
- .get(scheduledAction.action.name());
+ .get(scheduledAction.getAction().name());
Stream<ChargeDefinition> chargeMapping = chargeMappingList == null ? Stream.empty() : chargeMappingList.stream();
if (chargeMapping == null)
chargeMapping = Stream.empty();
final List<ChargeDefinition> accrueMappingList = chargeDefinitionsMappedByAccrueAction
- .get(scheduledAction.action.name());
+ .get(scheduledAction.getAction().name());
Stream<ChargeDefinition> accrueMapping = accrueMappingList == null ? Stream.empty() : accrueMappingList.stream();
if (accrueMapping == null)
accrueMapping = Stream.empty();
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationEntity.java
new file mode 100644
index 0000000..f2ff9b9
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationEntity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.portfolio.service.internal.repository;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Entity
+@Table(name = "bastet_p_arrears_config")
+public class ProductArrearsConfigurationEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "product_id", nullable = false)
+ private Long productId;
+
+ @Column(name = "days_late", nullable = false)
+ private Integer daysLate;
+
+ @Column(name = "percent_provision", nullable = false)
+ private BigDecimal percentProvision;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getProductId() {
+ return productId;
+ }
+
+ public void setProductId(Long productId) {
+ this.productId = productId;
+ }
+
+ public Integer getDaysLate() {
+ return daysLate;
+ }
+
+ public void setDaysLate(Integer daysLate) {
+ this.daysLate = daysLate;
+ }
+
+ public BigDecimal getPercentProvision() {
+ return percentProvision;
+ }
+
+ public void setPercentProvision(BigDecimal percentProvision) {
+ this.percentProvision = percentProvision;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProductArrearsConfigurationEntity that = (ProductArrearsConfigurationEntity) o;
+ return Objects.equals(productId, that.productId) &&
+ Objects.equals(daysLate, that.daysLate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(productId, daysLate);
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationRepository.java
new file mode 100644
index 0000000..01bdfb4
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductArrearsConfigurationRepository.java
@@ -0,0 +1,29 @@
+/*
+ * 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.portfolio.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface ProductArrearsConfigurationRepository extends JpaRepository<ProductArrearsConfigurationEntity, Long> {
+ List<ProductArrearsConfigurationEntity> findByProductId(Long id);
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql b/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql
index 2c91530..9fd882f 100644
--- a/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql
+++ b/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql
@@ -25,4 +25,15 @@
CONSTRAINT bastet_case_commands_pk PRIMARY KEY (id),
CONSTRAINT bastet_case_commands_uq UNIQUE (thoth_transaction_uq, action_name, case_id),
CONSTRAINT bastet_case_commands_fk FOREIGN KEY (case_id) REFERENCES bastet_cases (id)
+);
+
+CREATE TABLE bastet_p_arrears_config (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ product_id BIGINT NOT NULL,
+ days_late INT NOT NULL,
+ percent_provision DECIMAL(5,2) NOT NULL,
+
+ CONSTRAINT bastet_p_arrears_config_pk PRIMARY KEY (id),
+ CONSTRAINT bastet_p_arrears_config_uq UNIQUE (product_id, days_late),
+ CONSTRAINT bastet_p_arrears_config_fk FOREIGN KEY (product_id) REFERENCES bastet_products (id)
);
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
index 3fb6007..4bc8051 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
@@ -157,7 +157,12 @@
final CaseEntity customerCase = new CaseEntity();
customerCase.setInterest(interest);
- return new DataContextOfAction(product, customerCase, CaseParametersMapper.map(1L, caseParameters), Collections.emptyList());
+ return new DataContextOfAction(
+ product,
+ customerCase,
+ CaseParametersMapper.map(1L, caseParameters),
+ Collections.emptyList(),
+ Collections.emptyList());
}
@Override
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
index 99a5c17..84b3f38 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
@@ -55,6 +55,7 @@
product,
customerCase,
caseParameters,
+ Collections.emptyList(),
Collections.emptyList());
return testSubject.getPaymentBuilder(
dataContextOfAction,