diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/interestratechart/domain/InterestRateChartSlabFields.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/interestratechart/domain/InterestRateChartSlabFields.java index e9949ae83..d632147ef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/interestratechart/domain/InterestRateChartSlabFields.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/interestratechart/domain/InterestRateChartSlabFields.java @@ -210,4 +210,8 @@ public class InterestRateChartSlabFields { } return actualDepositPeriod; } + + public boolean isAmountBetween(final BigDecimal depositAmount){ + return depositAmount.compareTo(amountRangeFrom) >= 0 && depositAmount.compareTo(amountRangeTo) <= 0; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/DepositsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/DepositsApiConstants.java index 0ea7ae729..a62b84522 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/DepositsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/DepositsApiConstants.java @@ -69,6 +69,7 @@ public class DepositsApiConstants { public static final String approvedOnDateParamName = "approvedOnDate"; public static final String activatedOnDateParamName = "activatedOnDate"; public static final String closedOnDateParamName = "closedOnDate"; + public static final String expectedFirstDepositOnDateParamName = "expectedFirstDepositOnDate"; public static final String activeParamName = "active"; public static final String nameParamName = "name"; @@ -118,6 +119,8 @@ public class DepositsApiConstants { public static final String inMultiplesOfDepositTermType = "inMultiplesOfDepositTermType"; public static final String depositAmountParamName = "depositAmount"; + public static final String depositMinAmountParamName = "minDepositAmount"; + public static final String depositMaxAmountParamName = "maxDepositAmount"; public static final String depositPeriodParamName = "depositPeriod"; public static final String depositPeriodFrequencyIdParamName = "depositPeriodFrequencyId"; @@ -211,11 +214,13 @@ public class DepositsApiConstants { private static final Set DEPOSIT_TERM_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(minDepositTermParamName, maxDepositTermParamName, minDepositTermTypeIdParamName, maxDepositTermTypeIdParamName, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermTypeIdParamName)); + inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, + depositMinAmountParamName, depositMaxAmountParamName)); private static final Set DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(minDepositTermParamName, maxDepositTermParamName, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermType)); + inMultiplesOfDepositTermType, depositAmountParamName, + depositMinAmountParamName, depositMaxAmountParamName)); private static final Set RECURRING_DETAILS_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( recurringDepositAmountParamName, recurringDepositTypeIdParamName, recurringDepositFrequencyTypeIdParamName, @@ -277,7 +282,7 @@ public class DepositsApiConstants { interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName, - savingsAccounts)); + savingsAccounts, expectedFirstDepositOnDateParamName)); public static final Set FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData(); public static final Set FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java index 1d6088bd6..4a6ffa201 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java @@ -20,11 +20,11 @@ public enum SavingsCompoundingInterestPeriodType { DAILY(1, "savingsCompoundingInterestPeriodType.daily"), // // WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), // // BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), // - MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"); + MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"), - // QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), // - // BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), // - // ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"), // + QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), // + BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), // + ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"); // // NO_COMPOUNDING_SIMPLE_INTEREST(8, // "savingsCompoundingInterestPeriodType.nocompounding"); @@ -74,16 +74,16 @@ public enum SavingsCompoundingInterestPeriodType { repaymentFrequencyType = SavingsCompoundingInterestPeriodType.MONTHLY; break; case 5: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.QUATERLY; + repaymentFrequencyType = + SavingsCompoundingInterestPeriodType.QUATERLY; break; case 6: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.BI_ANNUAL; + repaymentFrequencyType = + SavingsCompoundingInterestPeriodType.BI_ANNUAL; break; case 7: - // repaymentFrequencyType = - // SavingsCompoundingInterestPeriodType.ANNUAL; + repaymentFrequencyType = + SavingsCompoundingInterestPeriodType.ANNUAL; break; case 8: // repaymentFrequencyType = diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java index d2e829eaf..6bad21040 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java @@ -17,6 +17,7 @@ public enum SavingsPostingInterestPeriodType { INVALID(0, "savingsPostingInterestPeriodType.invalid"), // MONTHLY(4, "savingsPostingInterestPeriodType.monthly"), // QUATERLY(5, "savingsPostingInterestPeriodType.quarterly"), // + BIANNUAL(6, "savingsPostingInterestPeriodType.biannual"), ANNUAL(7, "savingsPostingInterestPeriodType.annual"); private final Integer value; @@ -56,6 +57,9 @@ public enum SavingsPostingInterestPeriodType { case 5: repaymentFrequencyType = SavingsPostingInterestPeriodType.QUATERLY; break; + case 6: + repaymentFrequencyType = SavingsPostingInterestPeriodType.BIANNUAL; + break; case 7: repaymentFrequencyType = SavingsPostingInterestPeriodType.ANNUAL; break; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/DepositProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/DepositProductDataValidator.java index c4937ce85..4292ea031 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/DepositProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/DepositProductDataValidator.java @@ -27,6 +27,9 @@ import static org.mifosplatform.portfolio.savings.DepositsApiConstants.preClosur import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyTypeIdParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositTypeIdParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositAmountParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositMinAmountParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositMaxAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; @@ -48,6 +51,8 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -109,6 +114,8 @@ public class DepositProductDataValidator { validateDepositTermDeatilForCreate(element, baseDataValidator); validateChartsData(element, baseDataValidator); + + validateDepositAmountForCreate(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -131,6 +138,8 @@ public class DepositProductDataValidator { validateDepositTermDetailForUpdate(element, baseDataValidator); validateChartsData(element, baseDataValidator); + + validateDepositAmountForUpdate(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -156,6 +165,8 @@ public class DepositProductDataValidator { validateRecurringDetailForCreate(element, baseDataValidator); validateChartsData(element, baseDataValidator); + + validateDepositAmountForCreate(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -180,6 +191,8 @@ public class DepositProductDataValidator { validateRecurringDepositUpdate(element, baseDataValidator); validateChartsData(element, baseDataValidator); + + validateDepositAmountForUpdate(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -733,4 +746,73 @@ public class DepositProductDataValidator { .isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues()); } } + + private void validateDepositAmountForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) { + final BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element); + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount(); + + BigDecimal depositMinAmount = null; + if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) { + depositMinAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMinAmountParamName, element); + baseDataValidator.reset().parameter(depositMinAmountParamName).value(depositMinAmount).notNull().positiveAmount(); + } + + BigDecimal depositMaxAmount = null; + if (this.fromApiJsonHelper.parameterExists(depositMaxAmountParamName, element)) { + depositMaxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMaxAmountParamName, element); + baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notNull().positiveAmount(); + } + + if (depositMaxAmount != null && depositMaxAmount.compareTo(BigDecimal.ZERO) != -1) { + if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notLessThanMin(depositMinAmount); + if (depositMinAmount.compareTo(depositMaxAmount) <= 0) { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount) + .inMinAndMaxAmountRange(depositMinAmount, depositMaxAmount); + } + } else { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notGreaterThanMax(depositMaxAmount); + } + } else if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notLessThanMin(depositMinAmount); + } + } + + private void validateDepositAmountForUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) { + BigDecimal depositAmount = null; + + if (this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)) { + depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element); + baseDataValidator.reset().parameter(depositMinAmountParamName).value(depositAmount).notNull().positiveAmount(); + } + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount(); + + BigDecimal depositMinAmount = null; + if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) { + depositMinAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMinAmountParamName, element); + baseDataValidator.reset().parameter(depositMinAmountParamName).value(depositMinAmount).notNull().positiveAmount(); + } + + BigDecimal depositMaxAmount = null; + if (this.fromApiJsonHelper.parameterExists(depositMaxAmountParamName, element)) { + depositMaxAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositMaxAmountParamName, element); + baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notNull().positiveAmount(); + } + + if(depositAmount != null) { + if (depositMaxAmount != null && depositMaxAmount.compareTo(BigDecimal.ZERO) != -1) { + if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(depositMaxAmountParamName).value(depositMaxAmount).notLessThanMin(depositMinAmount); + if (depositMinAmount.compareTo(depositMaxAmount) <= 0) { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount) + .inMinAndMaxAmountRange(depositMinAmount, depositMaxAmount); + } + } else { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notGreaterThanMax(depositMaxAmount); + } + } else if (depositMinAmount != null && depositMinAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notLessThanMin(depositMinAmount); + } + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositAccountData.java index 13b3cdb1c..5dcbeb73a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositAccountData.java @@ -47,6 +47,7 @@ public class RecurringDepositAccountData extends DepositAccountData { private EnumOptionData recurringDepositType; private Integer recurringDepositFrequency; private EnumOptionData recurringDepositFrequencyType; + private LocalDate expectedFirstDepositOnDate; // used for account close private EnumOptionData onAccountClosure; @@ -73,7 +74,8 @@ public class RecurringDepositAccountData extends DepositAccountData { final BigDecimal maturityAmount, final LocalDate maturityDate, final Integer depositPeriod, final EnumOptionData depositPeriodFrequency, final BigDecimal recurringDepositAmount, final EnumOptionData recurringDepositType, final Integer recurringDepositFrequency, - final EnumOptionData recurringDepositFrequencyType, final EnumOptionData onAccountClosure) { + final EnumOptionData recurringDepositFrequencyType, final EnumOptionData onAccountClosure, + final LocalDate expectedFirstDepositOnDate) { final Collection interestFreePeriodTypeOptions = null; final Collection preClosurePenalInterestOnTypeOptions = null; @@ -108,7 +110,7 @@ public class RecurringDepositAccountData extends DepositAccountData { depositPeriod, depositPeriodFrequency, recurringDepositAmount, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, recurringDepositFrequencyTypeOptions, depositPeriodFrequencyOptions, depositType, onAccountClosure, - onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); + onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate); } public static RecurringDepositAccountData withInterestChart(final RecurringDepositAccountData account, @@ -132,7 +134,8 @@ public class RecurringDepositAccountData extends DepositAccountData { account.depositTermTypeOptions, account.inMultiplesOfDepositTermTypeOptions, account.recurringDepositType, account.recurringDepositFrequency, account.recurringDepositFrequencyType, account.recurringDepositTypeOptions, account.recurringDepositFrequencyTypeOptions, account.depositPeriodFrequencyOptions, account.depositType, - account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts); + account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts, + account.expectedFirstDepositOnDate); } public static RecurringDepositAccountData withTemplateOptions(final RecurringDepositAccountData account, @@ -186,7 +189,7 @@ public class RecurringDepositAccountData extends DepositAccountData { account.recurringDepositType, account.recurringDepositFrequency, account.recurringDepositFrequencyType, template.recurringDepositTypeOptions, template.recurringDepositFrequencyTypeOptions, template.depositPeriodFrequencyOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions, - account.paymentTypeOptions, account.savingsAccounts); + account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate); } @@ -223,7 +226,7 @@ public class RecurringDepositAccountData extends DepositAccountData { depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, account.recurringDepositType, account.recurringDepositFrequency, account.recurringDepositFrequencyType, recurringDepositTypeOptions, recurringDepositFrequencyTypeOptions, depositPeriodFrequencyOptions, account.depositType, account.onAccountClosure, - account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts); + account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate); } public static RecurringDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -301,6 +304,7 @@ public class RecurringDepositAccountData extends DepositAccountData { final Collection onAccountClosureOptions = null; final Collection paymentTypeOptions = null; final Collection savingsAccountDatas = null; + final LocalDate expectedFirstDepositOnDate = null; return new RecurringDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -316,7 +320,7 @@ public class RecurringDepositAccountData extends DepositAccountData { depositPeriodFrequency, recurringDepositAmount, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, recurringDepositFrequencyTypeOptions, depositPeriodFrequencyOptions, depositType, onAccountClosure, - onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); + onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate); } public static RecurringDepositAccountData preClosureDetails(final Long accountId, BigDecimal maturityAmount, @@ -394,6 +398,7 @@ public class RecurringDepositAccountData extends DepositAccountData { final String groupName = null; final Long clientId = null; final String clientName = null; + final LocalDate expectedFirstDepositOnDate = null; return new RecurringDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -409,7 +414,7 @@ public class RecurringDepositAccountData extends DepositAccountData { depositPeriodFrequency, recurringDepositAmount, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, recurringDepositFrequencyTypeOptions, depositPeriodFrequencyOptions, depositType, onAccountClosure, - onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); + onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate); } public static RecurringDepositAccountData withClosureTemplateDetails(final RecurringDepositAccountData account, @@ -434,7 +439,8 @@ public class RecurringDepositAccountData extends DepositAccountData { account.depositTermTypeOptions, account.inMultiplesOfDepositTermTypeOptions, account.recurringDepositType, account.recurringDepositFrequency, account.recurringDepositFrequencyType, account.recurringDepositTypeOptions, account.recurringDepositFrequencyTypeOptions, account.depositPeriodFrequencyOptions, account.depositType, - account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); + account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, + account.expectedFirstDepositOnDate); } private RecurringDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, @@ -469,7 +475,8 @@ public class RecurringDepositAccountData extends DepositAccountData { final Collection recurringDepositFrequencyTypeOptions, final Collection depositPeriodFrequencyOptions, final EnumOptionData depositType, final EnumOptionData onAccountClosure, final Collection onAccountClosureOptions, - final Collection paymentTypeOptions, final Collection savingsAccountDatas) { + final Collection paymentTypeOptions, final Collection savingsAccountDatas, + final LocalDate expectedFirstDepositOnDate) { super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId, fieldofficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, @@ -501,6 +508,7 @@ public class RecurringDepositAccountData extends DepositAccountData { this.recurringDepositFrequencyType = recurringDepositFrequencyType; this.recurringDepositFrequency = recurringDepositFrequency; this.recurringDepositType = recurringDepositType; + this.expectedFirstDepositOnDate = expectedFirstDepositOnDate; // template data this.interestFreePeriodTypeOptions = interestFreePeriodTypeOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositProductData.java index 0309a65a7..9a95747e3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/RecurringDepositProductData.java @@ -40,6 +40,9 @@ public class RecurringDepositProductData extends DepositProductData { private EnumOptionData recurringDepositType; private Integer recurringDepositFrequency; private EnumOptionData recurringDepositFrequencyType; + private BigDecimal minDepositAmount; + private BigDecimal depositAmount; + private BigDecimal maxDepositAmount; private Collection interestFreePeriodTypeOptions; private Collection preClosurePenalInterestOnTypeOptions; @@ -96,7 +99,10 @@ public class RecurringDepositProductData extends DepositProductData { final EnumOptionData maxDepositTermType = null; final Integer inMultiplesOfDepositTerm = null; final EnumOptionData inMultiplesOfDepositTermType = null; - + final BigDecimal minDepositAmount = null; + final BigDecimal depositAmount = null; + final BigDecimal maxDepositAmount = null; + return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRule, accountingMappings, paymentChannelToFundSourceMappings, @@ -106,9 +112,10 @@ public class RecurringDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, interestFreePeriodApplicable, interestFreeFromPeriod, interestFreeToPeriod, interestFreePeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, interestFreePeriodTypes, preClosurePenalInterestOnTypeOptions, minDepositTerm, - maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, - recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, - recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions); + maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, recurringDepositType, + recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, + recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, + minDepositAmount, depositAmount, maxDepositAmount); } public static RecurringDepositProductData withCharges(final RecurringDepositProductData existingProduct, @@ -134,7 +141,8 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.recurringDepositType, existingProduct.recurringDepositFrequency, existingProduct.recurringDepositFrequencyType, existingProduct.recurringDepositTypeOptions, existingProduct.recurringDepositFrequencyTypeOptions, existingProduct.depositTermTypeOptions, - existingProduct.inMultiplesOfDepositTermTypeOptions); + existingProduct.inMultiplesOfDepositTermTypeOptions, existingProduct.minDepositAmount, + existingProduct.depositAmount, existingProduct.maxDepositAmount); } /** @@ -175,7 +183,8 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.recurringDepositType, existingProduct.recurringDepositFrequency, existingProduct.recurringDepositFrequencyType, recurringDepositTypeOptions, - recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions); + recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, existingProduct.minDepositAmount, + existingProduct.depositAmount, existingProduct.maxDepositAmount); } public static RecurringDepositProductData withAccountingDetails(final RecurringDepositProductData existingProduct, @@ -216,7 +225,8 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.recurringDepositType, existingProduct.recurringDepositFrequency, existingProduct.recurringDepositFrequencyType, existingProduct.recurringDepositTypeOptions, existingProduct.recurringDepositFrequencyTypeOptions, existingProduct.depositTermTypeOptions, - existingProduct.inMultiplesOfDepositTermTypeOptions); + existingProduct.inMultiplesOfDepositTermTypeOptions, existingProduct.minDepositAmount, + existingProduct.depositAmount, existingProduct.maxDepositAmount); } public static RecurringDepositProductData instance(final DepositProductData depositProductData, @@ -226,7 +236,9 @@ public class RecurringDepositProductData extends DepositProductData { final Integer maxDepositTerm, final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm, final EnumOptionData inMultiplesOfDepositTermType, final EnumOptionData recurringDepositType, final Integer recurringDepositFrequency, - final EnumOptionData recurringDepositFrequencyType) { + final EnumOptionData recurringDepositFrequencyType, + final BigDecimal minDepositAmount,final BigDecimal depositAmount, + final BigDecimal maxDepositAmount) { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; @@ -269,7 +281,8 @@ public class RecurringDepositProductData extends DepositProductData { preClosurePenalInterestOnType, interestFreePeriodTypes, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, - recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions); + recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, + minDepositAmount, depositAmount, maxDepositAmount); } public static RecurringDepositProductData lookup(final Long id, final String name) { @@ -329,6 +342,9 @@ public class RecurringDepositProductData extends DepositProductData { final EnumOptionData inMultiplesOfDepositTermType = null; final Collection depositTermTypeOptions = null; final Collection inMultiplesOfDepositTermTypeOptions = null; + final BigDecimal minDepositAmount = null; + final BigDecimal depositAmount = null; + final BigDecimal maxDepositAmount = null; return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, @@ -341,7 +357,8 @@ public class RecurringDepositProductData extends DepositProductData { preClosurePenalInterestOnType, interestFreePeriodTypes, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, recurringDepositTypeOptions, - recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions); + recurringDepositFrequencyTypeOptions, depositTermTypeOptions, inMultiplesOfDepositTermTypeOptions, + minDepositAmount, depositAmount, maxDepositAmount); } public static RecurringDepositProductData withInterestChart(final RecurringDepositProductData product, @@ -362,7 +379,8 @@ public class RecurringDepositProductData extends DepositProductData { product.minDepositTerm, product.maxDepositTerm, product.minDepositTermType, product.maxDepositTermType, product.inMultiplesOfDepositTerm, product.inMultiplesOfDepositTermType, product.recurringDepositType, product.recurringDepositFrequency, product.recurringDepositFrequencyType, product.recurringDepositTypeOptions, - product.recurringDepositFrequencyTypeOptions, product.depositTermTypeOptions, product.inMultiplesOfDepositTermTypeOptions); + product.recurringDepositFrequencyTypeOptions, product.depositTermTypeOptions, product.inMultiplesOfDepositTermTypeOptions, product.minDepositAmount, + product.depositAmount, product.maxDepositAmount); } private RecurringDepositProductData(final Long id, final String name, final String shortName, final String description, @@ -393,7 +411,8 @@ public class RecurringDepositProductData extends DepositProductData { final EnumOptionData recurringDepositType, final Integer recurringDepositFrequency, final EnumOptionData recurringDepositFrequencyType, final Collection recurringDepositTypeOptions, final Collection recurringDepositFrequencyTypeOptions, final Collection depositTermTypeOptions, - final Collection inMultiplesOfDepositTermTypeOptions) { + final Collection inMultiplesOfDepositTermTypeOptions,final BigDecimal minDepositAmount, + final BigDecimal depositAmount, final BigDecimal maxDepositAmount) { super(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -419,6 +438,9 @@ public class RecurringDepositProductData extends DepositProductData { this.recurringDepositFrequencyType = recurringDepositFrequencyType; this.recurringDepositFrequency = recurringDepositFrequency; this.recurringDepositType = recurringDepositType; + this.minDepositAmount = minDepositAmount; + this.depositAmount = depositAmount; + this.maxDepositAmount = maxDepositAmount; // template data this.interestFreePeriodTypeOptions = interestFreePeriodTypeOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java index 287d0a529..084b4db31 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountAssembler.java @@ -13,6 +13,7 @@ import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurring import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyTypeIdParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositTypeIdParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; @@ -312,6 +313,7 @@ public class DepositAccountAssembler { final Integer depositPeriodFrequencyId = command.integerValueOfParameterNamed(depositPeriodFrequencyIdParamName); final SavingsPeriodFrequencyType depositPeriodFrequency = SavingsPeriodFrequencyType.fromInt(depositPeriodFrequencyId); final SavingsAccount account = null; + final LocalDate expectedFirstDepositOnDate = command.localDateValueOfParameterNamed(expectedFirstDepositOnDateParamName); // calculate maturity amount final BigDecimal maturityAmount = null;// calculated and updated in @@ -319,7 +321,7 @@ public class DepositAccountAssembler { final LocalDate maturityDate = null;// calculated and updated in account final DepositAccountOnClosureType accountOnClosureType = null; return DepositAccountTermAndPreClosure.createNew(updatedProductPreClosure, updatedProductTerm, account, depositAmount, - maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, accountOnClosureType); + maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType); } public DepositAccountRecurringDetail assembleAccountRecurringDetail(final JsonCommand command, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 202460ae0..0d0b29495 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -179,8 +179,15 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final LocalDate transactionDate = withdrawal.transactionLocalDate(); if (rdAccount.isReinvestOnClosure()) { RecurringDepositAccount reinvestedDeposit = rdAccount.reInvest(transactionAmount); - final LocalDate transactionStartDate = rdAccount.depositStartDate(); - rdAccount.updateMaturityDateAndAmount(mc, transactionStartDate); + depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate transactionStartDate = reinvestedDeposit.getActivationLocalDate(); + final LocalDate nextTransactionStartDate = reinvestedDeposit.nextDepositDate(transactionStartDate); + reinvestedDeposit.updateMaturityDateAndAmount(mc, nextTransactionStartDate); + reinvestedDeposit.processAccountUponActivation(fmt); + reinvestedDeposit.updateMaturityDateAndAmount(mc, nextTransactionStartDate); + this.savingsAccountRepository.save(reinvestedDeposit); autoGenerateAccountNumber(reinvestedDeposit); } else if (rdAccount.isTransferToSavingsOnClosure()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountInterestRateChart.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountInterestRateChart.java index 9b21775e9..42634af43 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountInterestRateChart.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountInterestRateChart.java @@ -119,10 +119,10 @@ public class DepositAccountInterestRateChart extends AbstractPersistable { this.account = account; } - public BigDecimal getApplicableInterestRate(@SuppressWarnings("unused") final BigDecimal depositAmount, final LocalDate periodStartDate, final LocalDate periodEndDate){ + public BigDecimal getApplicableInterestRate(final BigDecimal depositAmount, final LocalDate periodStartDate, final LocalDate periodEndDate){ BigDecimal effectiveInterestRate = BigDecimal.ZERO; for (DepositAccountInterestRateChartSlabs slab : setOfChartSlabs()) { - if(slab.slabFields().isBetweenPeriod(periodStartDate, periodEndDate)){ + if(slab.slabFields().isBetweenPeriod(periodStartDate, periodEndDate) && slab.slabFields().isAmountBetween(depositAmount)){ effectiveInterestRate = slab.slabFields().annualInterestRate(); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountTermAndPreClosure.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountTermAndPreClosure.java index 2a6d96cb7..09e8584d2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountTermAndPreClosure.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountTermAndPreClosure.java @@ -49,6 +49,10 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { @Temporal(TemporalType.DATE) @Column(name = "maturity_date", nullable = true) private Date maturityDate; + + @Temporal(TemporalType.DATE) + @Column(name = "expected_firstdepositon_date") + private Date expectedFirstDepositOnDate; @Column(name = "deposit_period", nullable = true) private Integer depositPeriod; @@ -75,16 +79,16 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { public static DepositAccountTermAndPreClosure createNew(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail, SavingsAccount account, BigDecimal depositAmount, BigDecimal maturityAmount, final LocalDate maturityDate, - Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, + Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, final LocalDate expectedFirstDepositOnDate, final DepositAccountOnClosureType accountOnClosureType) { return new DepositAccountTermAndPreClosure(preClosureDetail, depositTermDetail, account, depositAmount, maturityAmount, - maturityDate, depositPeriod, depositPeriodFrequency, accountOnClosureType); + maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType); } private DepositAccountTermAndPreClosure(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail, SavingsAccount account, BigDecimal depositAmount, BigDecimal maturityAmount, final LocalDate maturityDate, - Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, + Integer depositPeriod, final SavingsPeriodFrequencyType depositPeriodFrequency, final LocalDate expectedFirstDepositOnDate, final DepositAccountOnClosureType accountOnClosureType) { this.depositAmount = depositAmount; this.maturityAmount = maturityAmount; @@ -94,6 +98,7 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { this.preClosureDetail = preClosureDetail; this.depositTermDetail = depositTermDetail; this.account = account; + this.expectedFirstDepositOnDate = expectedFirstDepositOnDate == null ? null : expectedFirstDepositOnDate.toDate(); this.onAccountClosureType = (accountOnClosureType == null) ? null : accountOnClosureType.getValue(); } @@ -174,6 +179,14 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { } return maturityLocalDate; } + + public LocalDate getExpectedFirstDepositOnDate() { + LocalDate expectedFirstDepositOnLocalDate = null; + if (this.expectedFirstDepositOnDate != null) { + expectedFirstDepositOnLocalDate = new LocalDate(this.expectedFirstDepositOnDate); + } + return expectedFirstDepositOnLocalDate; + } public boolean isPreClosurePenalApplicable() { if (this.preClosureDetail != null) { return this.preClosureDetail.preClosurePenalApplicable(); } @@ -181,7 +194,11 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { } public Integer getActualDepositPeriod(final LocalDate interestPostingUpToDate, final SavingsPeriodFrequencyType periodFrequencyType) { - final LocalDate depositFromDate = this.account.accountSubmittedOrActivationDate(); + LocalDate depositFromDate = getExpectedFirstDepositOnDate(); + + if(depositFromDate == null) + depositFromDate = this.account.accountSubmittedOrActivationDate(); + Integer actualDepositPeriod = this.depositPeriod; if (depositFromDate == null || getMaturityLocalDate() == null || interestPostingUpToDate.isEqual(getMaturityLocalDate())) { return actualDepositPeriod; } @@ -225,14 +242,20 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable { public DepositAccountTermAndPreClosure deepCopy(Money depositAmount) { final SavingsAccount account = null; final BigDecimal maturityAmount = null; + final BigDecimal actualDepositAmount = null; final LocalDate maturityDate = null; final Integer depositPeriod = this.depositPeriod; final SavingsPeriodFrequencyType depositPeriodFrequency = SavingsPeriodFrequencyType.fromInt(this.depositPeriodFrequency); final DepositPreClosureDetail preClosureDetail = this.preClosureDetail.copy(); final DepositTermDetail depositTermDetail = this.depositTermDetail.copy(); + final LocalDate expectedFirstDepositOnDate = null; final DepositAccountOnClosureType accountOnClosureType = null; - return DepositAccountTermAndPreClosure.createNew(preClosureDetail, depositTermDetail, account, depositAmount.getAmount(), maturityAmount, - maturityDate, depositPeriod, depositPeriodFrequency, accountOnClosureType); + return DepositAccountTermAndPreClosure.createNew(preClosureDetail, depositTermDetail, account, actualDepositAmount, maturityAmount, + maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType); + } + + public void updateExpectedFirstDepositDate(final LocalDate expectedFirstDepositOnDate) { + this.expectedFirstDepositOnDate = expectedFirstDepositOnDate.toDate(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAmountDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAmountDetails.java new file mode 100644 index 000000000..c4cc51930 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAmountDetails.java @@ -0,0 +1,98 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; + +/** + * RecurringDepositProductAmountDetails encapsulates all recurring Deposit Amount of a + * {@link RecurringDepositProduct}. + */ +@Embeddable +public class DepositProductAmountDetails { + + @Column(name = "min_deposit_amount", scale = 6, precision = 19, nullable = true) + private BigDecimal minDepositAmount; + + @Column(name = "max_deposit_amount", scale = 6, precision = 19, nullable = true) + private BigDecimal maxDepositAmount; + + @Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false) + private BigDecimal depositAmount; + + + + public static DepositProductAmountDetails createFrom(final BigDecimal minDepositAmount, + final BigDecimal depositAmount, final BigDecimal maxDepositAmount) { + + return new DepositProductAmountDetails(minDepositAmount, depositAmount, maxDepositAmount); + } + + protected DepositProductAmountDetails() { + // + } + + public DepositProductAmountDetails(final BigDecimal minDepositAmount, final BigDecimal depositAmount, final BigDecimal maxDepositAmount) { + this.minDepositAmount = minDepositAmount; + this.depositAmount = depositAmount; + this.maxDepositAmount = maxDepositAmount; + } + + public Map update(final JsonCommand command, final DataValidatorBuilder baseDataValidator) { + + final Map actualChanges = new LinkedHashMap(20); + + final String localeAsInput = command.locale(); + + final String minDepositAmountParamName = "minDepositAmount"; + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minDepositAmountParamName, this.minDepositAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minDepositAmountParamName); + actualChanges.put(minDepositAmountParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.minDepositAmount = newValue; + } + + final String maxDepositAmountParamName = "maxDepositAmount"; + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxDepositAmountParamName, this.maxDepositAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxDepositAmountParamName); + actualChanges.put(maxDepositAmountParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.maxDepositAmount = newValue; + } + + final String depositAmountParamName = "depositAmount"; + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(depositAmountParamName, this.depositAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(depositAmountParamName); + actualChanges.put(depositAmountParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.depositAmount = newValue; + } + + return actualChanges; + } + + public BigDecimal getMinDepositAmount() { + return this.minDepositAmount; + } + + public BigDecimal getMaxDepositAmount() { + return this.maxDepositAmount; + } + + public BigDecimal getDepositAmount() { + return this.depositAmount; + } + + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAssembler.java index cfe81b91f..4e5a82593 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductAssembler.java @@ -24,6 +24,9 @@ import static org.mifosplatform.portfolio.savings.DepositsApiConstants.preClosur import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositFrequencyTypeIdParamName; import static org.mifosplatform.portfolio.savings.DepositsApiConstants.recurringDepositTypeIdParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositAmountParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositMinAmountParamName; +import static org.mifosplatform.portfolio.savings.DepositsApiConstants.depositMaxAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -133,8 +136,9 @@ public class DepositProductAssembler { final DepositPreClosureDetail preClosureDetail = this.assemblePreClosureDetail(command); final DepositTermDetail depositTermDetail = this.assembleDepositTermDetail(command); + final DepositProductAmountDetails depositProductAmountDetails = this.assembleDepositAmountDetails(command); final DepositProductTermAndPreClosure productTermAndPreClosure = DepositProductTermAndPreClosure.createNew(preClosureDetail, - depositTermDetail, null); + depositTermDetail, depositProductAmountDetails, null); // Savings product charges final Set charges = assembleListOfSavingsProductCharges(command, currencyCode); @@ -167,7 +171,7 @@ public class DepositProductAssembler { .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); } fixedDepositProduct.validateCharts(baseDataValidator); - + fixedDepositProduct.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -220,8 +224,9 @@ public class DepositProductAssembler { final DepositPreClosureDetail preClosureDetail = this.assemblePreClosureDetail(command); final DepositTermDetail depositTermDetail = this.assembleDepositTermDetail(command); + final DepositProductAmountDetails depositProductAmountDetails = this.assembleDepositAmountDetails(command); final DepositProductTermAndPreClosure productTermAndPreClosure = DepositProductTermAndPreClosure.createNew(preClosureDetail, - depositTermDetail, null); + depositTermDetail, depositProductAmountDetails, null); final DepositRecurringDetail recurringDetail = this.assembleRecurringDetail(command); final DepositProductRecurringDetail productRecurringDetail = DepositProductRecurringDetail.createNew(recurringDetail, null); @@ -260,6 +265,7 @@ public class DepositProductAssembler { .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); } recurringDepositProduct.validateCharts(baseDataValidator); + recurringDepositProduct.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -535,4 +541,27 @@ public class DepositProductAssembler { private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + + public DepositProductAmountDetails assembleDepositAmountDetails(final JsonCommand command) { + + BigDecimal minDepositAmount = null; + if(command.parameterExists(depositMinAmountParamName)) { + minDepositAmount = command.bigDecimalValueOfParameterNamed(depositMinAmountParamName); + } + + BigDecimal maxDepositAmount = null; + if(command.parameterExists(depositMaxAmountParamName)) { + maxDepositAmount = command.bigDecimalValueOfParameterNamed(depositMaxAmountParamName); + } + + BigDecimal depositAmount = null; + if(command.parameterExists(depositAmountParamName)) { + depositAmount = command.bigDecimalValueOfParameterNamed(depositAmountParamName); + } + + final DepositProductAmountDetails depositRecurringDetail = new DepositProductAmountDetails(minDepositAmount, + depositAmount, maxDepositAmount); + + return depositRecurringDetail; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductTermAndPreClosure.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductTermAndPreClosure.java index 5e7b6f4e5..5618b367f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductTermAndPreClosure.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositProductTermAndPreClosure.java @@ -31,21 +31,26 @@ public class DepositProductTermAndPreClosure extends AbstractPersistable { @OneToOne @JoinColumn(name = "savings_product_id", nullable = false) private FixedDepositProduct product; + + @Embedded + private DepositProductAmountDetails depositProductAmountDetails; protected DepositProductTermAndPreClosure() { super(); } public static DepositProductTermAndPreClosure createNew(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail, + DepositProductAmountDetails depositProductMinMaxAmountDetails, SavingsProduct product) { - return new DepositProductTermAndPreClosure(preClosureDetail, depositTermDetail, product); + return new DepositProductTermAndPreClosure(preClosureDetail, depositTermDetail, depositProductMinMaxAmountDetails, product); } private DepositProductTermAndPreClosure(DepositPreClosureDetail preClosureDetail, DepositTermDetail depositTermDetail, - SavingsProduct product) { + DepositProductAmountDetails depositProductMinMaxAmountDetails, SavingsProduct product) { this.preClosureDetail = preClosureDetail; this.depositTermDetail = depositTermDetail; + this.depositProductAmountDetails = depositProductMinMaxAmountDetails; this.product = (FixedDepositProduct)product; } @@ -58,6 +63,10 @@ public class DepositProductTermAndPreClosure extends AbstractPersistable { if (this.depositTermDetail != null) { actualChanges.putAll(this.depositTermDetail.update(command, baseDataValidator)); } + + if(this.depositProductAmountDetails != null) { + actualChanges.putAll(this.depositProductAmountDetails.update(command, baseDataValidator)); + } return actualChanges; } @@ -69,6 +78,10 @@ public class DepositProductTermAndPreClosure extends AbstractPersistable { return this.depositTermDetail; } + public DepositProductAmountDetails depositProductAmountDetails() { + return this.depositProductAmountDetails; + } + public void updateProductReference(final SavingsProduct product){ this.product = (FixedDepositProduct)product; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositProduct.java index 986c97de6..ad8a63dd1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositProduct.java @@ -11,6 +11,7 @@ import static org.mifosplatform.portfolio.savings.DepositsApiConstants.FIXED_DEP import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -260,5 +261,37 @@ public class FixedDepositProduct extends SavingsProduct { public DepositProductTermAndPreClosure depositProductTermAndPreClosure() { return this.productTermAndPreClosure; } + + @SuppressWarnings("unchecked") + public void validateInterestPostingAndCompoundingPeriodTypes(final DataValidatorBuilder baseDataValidator) { + Map> postingtoCompoundMap = + new HashMap>(); + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.MONTHLY, + Arrays.asList(new SavingsCompoundingInterestPeriodType[]{SavingsCompoundingInterestPeriodType.DAILY,SavingsCompoundingInterestPeriodType.MONTHLY})); + + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.QUATERLY, + Arrays.asList(new SavingsCompoundingInterestPeriodType[]{SavingsCompoundingInterestPeriodType.DAILY + ,SavingsCompoundingInterestPeriodType.MONTHLY,SavingsCompoundingInterestPeriodType.QUATERLY})); + + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.BIANNUAL, + Arrays.asList(new SavingsCompoundingInterestPeriodType[]{SavingsCompoundingInterestPeriodType.DAILY, + SavingsCompoundingInterestPeriodType.MONTHLY,SavingsCompoundingInterestPeriodType.QUATERLY,SavingsCompoundingInterestPeriodType.BI_ANNUAL})); + + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.ANNUAL, + Arrays.asList(new SavingsCompoundingInterestPeriodType[]{SavingsCompoundingInterestPeriodType.DAILY, + SavingsCompoundingInterestPeriodType.MONTHLY,SavingsCompoundingInterestPeriodType.QUATERLY + ,SavingsCompoundingInterestPeriodType.BI_ANNUAL,SavingsCompoundingInterestPeriodType.ANNUAL})); + + SavingsPostingInterestPeriodType savingsPostingInterestPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodType); + SavingsCompoundingInterestPeriodType savingsCompoundingInterestPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestCompoundingPeriodType); + + if(postingtoCompoundMap.get(savingsPostingInterestPeriodType) == null || + !postingtoCompoundMap.get(savingsPostingInterestPeriodType).contains(savingsCompoundingInterestPeriodType)) { + baseDataValidator.failWithCodeNoParameterAddedToErrorCode("posting.period.type.is.less.than.compound.period.type", + savingsPostingInterestPeriodType.name(),savingsCompoundingInterestPeriodType.name()); + + } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java index 96bb950bd..97d956970 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java @@ -99,6 +99,28 @@ public class RecurringDepositAccount extends SavingsAccount { lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, recurringDetail, chart, allowOverdraft, overdraftLimit); } + + public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, + final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId, + final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; + return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit); + } private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType, @@ -159,7 +181,7 @@ public class RecurringDepositAccount extends SavingsAccount { } } - final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); + final BigDecimal depositAmount = recurringDetail.recurringDepositAmount(); BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate); @@ -251,7 +273,7 @@ public class RecurringDepositAccount extends SavingsAccount { .fromInt(this.interestCalculationDaysInYearType); final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( - accountSubmittedOrActivationDate(), maturityDate, postingPeriodType); + depositStartDate(), maturityDate, postingPeriodType); final List allPostingPeriods = new ArrayList(); @@ -321,7 +343,7 @@ public class RecurringDepositAccount extends SavingsAccount { return allTransactions; } - private LocalDate nextDepositDate(LocalDate depositDate) { + public LocalDate nextDepositDate(LocalDate depositDate) { final LocalDate depositStartDate = depositStartDate(); LocalDate nextDepositDate = depositStartDate; while (!depositDate.isBefore(nextDepositDate)) { @@ -357,7 +379,9 @@ public class RecurringDepositAccount extends SavingsAccount { public LocalDate depositStartDate() { // TODO: Support to add deposit start date which can be a date after // account activation date. - final LocalDate depositStartDate = accountSubmittedOrActivationDate(); + final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + if(depositStartDate == null) + return accountSubmittedOrActivationDate(); return depositStartDate; } @@ -436,18 +460,19 @@ public class RecurringDepositAccount extends SavingsAccount { } @Override - protected void processAccountUponActivation(final DateTimeFormatter fmt) { - final Money recurringDepositAmount = Money.of(this.currency, this.recurringDetail.recurringDepositAmount()); - if (recurringDepositAmount.isGreaterThanZero()) { - + protected void processAccountUponActivation(final DateTimeFormatter fmt) { + final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); + + if (minRequiredOpeningBalance.isGreaterThanZero()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), - recurringDepositAmount.getAmount(), null, new Date()); + minRequiredOpeningBalance.getAmount(), null, new Date()); deposit(transactionDTO); - final LocalDate interestPostingUpToDate = interestPostingUpToDate(DateUtils.getLocalDateOfTenant()); + // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate); + recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant()); } + } public SavingsAccountTransaction close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, @@ -778,6 +803,25 @@ public class RecurringDepositAccount extends SavingsAccount { if (!chartInterval.contains(accountSubmittedOrActivationDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range"); } + + final boolean firstDepositDateBeforeMaturityAndOnOrAfterActivationDate = firstDepositDateBeforeMaturityAndOnOrAfterActivationDate(); + + if(!firstDepositDateBeforeMaturityAndOnOrAfterActivationDate) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("expected.first.deposit.date.is.not.between.maturity.date.and.activation.or.submitted.date"); + } + + final BigDecimal recurringDepositAmount = recurringDetail.recurringDepositAmount(); + BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(recurringDepositAmount, depositStartDate(), calculateMaturityDate()); + + if(applicableInterestRate.equals(BigDecimal.ZERO)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); + } + + final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); + + if(!recurringFrequencyBeforeDepositPeriod) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("recurring.frequency.not.before.deposit.period"); + } } public boolean isReinvestOnClosure() { @@ -795,7 +839,7 @@ public class RecurringDepositAccount extends SavingsAccount { final SavingsProduct product = this.product; final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); - final String accountNumber = this.accountNumber; + final String accountNumber = null; final String externalId = this.externalId; final AccountType accountType = AccountType.fromInt(this.accountType); final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); @@ -804,17 +848,78 @@ public class RecurringDepositAccount extends SavingsAccount { final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType .fromInt(this.interestCalculationDaysInYearType); - final BigDecimal minRequiredOpeningBalance = null; - final BigDecimal interestRate = null; + final BigDecimal minRequiredOpeningBalance = depositAmount.getAmount(); + final BigDecimal interestRate = BigDecimal.ZERO; final Set savingsAccountCharges = null; final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; final boolean withdrawalFeeApplicableForTransfer = false; + + LocalDate now = LocalDate.now(); + + newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); - return RecurringDepositAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNumber, externalId, + RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, fieldOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, recurringDetail, newChart); + + rdAccount.setDatesFrom(now); + newAccountTermAndPreClosure.updateAccountReference(rdAccount); + recurringDetail.updateAccountReference(rdAccount); + + return rdAccount; } + + private boolean firstDepositDateBeforeMaturityAndOnOrAfterActivationDate() { + LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + LocalDate maturityLocalDate = calculateMaturityDate(); + LocalDate activateOnSubmittedLocalDate = accountSubmittedOrActivationDate(); + + return expectedFirstDepositLocalDate.isBefore(maturityLocalDate) && + (activateOnSubmittedLocalDate.isEqual(expectedFirstDepositLocalDate) || activateOnSubmittedLocalDate.isBefore(expectedFirstDepositLocalDate)); + } + + public void setDatesFrom(final LocalDate now) { + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + + this.activatedBy = null; + this.lockedInUntilDate = null; + + this.activatedOnDate = now.toDate(); + } + + private boolean recurringFrequencyBeforeDepositPeriod() { + final int recurringFrequency = recurringDetail.recurringDepositFrequency(); + final SavingsPeriodFrequencyType recurringfrequencyType = recurringDetail.recurringDepositFrequencyType(); + + LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + + LocalDate endDateWithFirstRecurringFrequency = null; + + switch (recurringfrequencyType) { + case DAYS: + endDateWithFirstRecurringFrequency = depositStartDate.plusDays(recurringFrequency); + break; + case MONTHS: + endDateWithFirstRecurringFrequency = depositStartDate.plusMonths(recurringFrequency); + break; + case WEEKS: + endDateWithFirstRecurringFrequency = depositStartDate.plusWeeks(recurringFrequency); + break; + case YEARS: + endDateWithFirstRecurringFrequency = depositStartDate.plusYears(recurringFrequency); + break; + default: + break; + } + + return endDateWithFirstRecurringFrequency != null && endDateWithFirstRecurringFrequency.isBefore(calculateMaturityDate()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java index a0b1df57f..15e14d8eb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java @@ -69,6 +69,15 @@ public final class SavingsHelper { periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); } break; + case BIANNUAL: + final int byear = periodStartDate.getYearOfEra(); + final int bmonthofYear = periodStartDate.getMonthOfYear(); + if (bmonthofYear <= 6) { + periodEndDate = new DateTime().withDate(byear, 6, 30).toLocalDate(); + } else if (bmonthofYear <= 12) { + periodEndDate = new DateTime().withDate(byear, 12, 31).toLocalDate(); + } + break; case ANNUAL: periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java new file mode 100644 index 000000000..d2c4df42e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/AnnualCompoundingPeriod.java @@ -0,0 +1,154 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public class AnnualCompoundingPeriod implements CompoundingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final List endOfDayBalances; + + public static AnnualCompoundingPeriod create(final LocalDateInterval periodInterval, final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances, upToInterestCalculationDate); + + return new AnnualCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod); + } + + @Override + public BigDecimal calculateInterest(final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalance(BigDecimal.ZERO, interestRateAsFraction, + daysInYear); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } + + @Override + public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + switch (interestCalculationType) { + case DAILY_BALANCE: + interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, + interestRateAsFraction, daysInYear); + break; + case AVERAGE_DAILY_BALANCE: + interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear); + break; + case INVALID: + break; + } + + return interestEarned; + } + + private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal cumulativeBalance = BigDecimal.ZERO; + Integer numberOfDays = Integer.valueOf(0); + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound); + cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance); + + final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays(); + numberOfDays = numberOfDays + balanceExistsForNumberOfDays; + } + + BigDecimal interestEarned = BigDecimal.ZERO; + if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) { + final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64) + .setScale(9, RoundingMode.HALF_EVEN); + + final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64); + final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64); + + interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + return interestEarned; + } + + private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO; + for (final EndOfDayBalance balance : this.endOfDayBalances) { + + switch (compoundingInterestPeriodType) { + case DAILY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction, + daysInYear); + break; + case MONTHLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case QUATERLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case WEEKLY: + // break; + // case BIWEEKLY: + // break; + case BI_ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; + case INVALID: + break; + } + + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + return interestEarned; + } + + private static List endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval, + final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesForPeriodInterval = new ArrayList(); + + for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) { + + if (compoundingPeriodInterval.contains(endOfDayBalance.date())) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } else if (endOfDayBalance.contains(compoundingPeriodInterval)) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } + } + + return endOfDayBalancesForPeriodInterval; + } + + private AnnualCompoundingPeriod(final LocalDateInterval periodInterval, final List endOfDayBalances) { + this.periodInterval = periodInterval; + this.endOfDayBalances = endOfDayBalances; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java new file mode 100644 index 000000000..98c030d7e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/BiAnnualCompoundingPeriod.java @@ -0,0 +1,154 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public class BiAnnualCompoundingPeriod implements CompoundingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final List endOfDayBalances; + + public static BiAnnualCompoundingPeriod create(final LocalDateInterval periodInterval, final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances, upToInterestCalculationDate); + + return new BiAnnualCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod); + } + + @Override + public BigDecimal calculateInterest(final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalance(BigDecimal.ZERO, interestRateAsFraction, + daysInYear); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } + + @Override + public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + switch (interestCalculationType) { + case DAILY_BALANCE: + interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, + interestRateAsFraction, daysInYear); + break; + case AVERAGE_DAILY_BALANCE: + interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear); + break; + case INVALID: + break; + } + + return interestEarned; + } + + private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal cumulativeBalance = BigDecimal.ZERO; + Integer numberOfDays = Integer.valueOf(0); + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound); + cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance); + + final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays(); + numberOfDays = numberOfDays + balanceExistsForNumberOfDays; + } + + BigDecimal interestEarned = BigDecimal.ZERO; + if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) { + final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64) + .setScale(9, RoundingMode.HALF_EVEN); + + final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64); + final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64); + + interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + return interestEarned; + } + + private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO; + for (final EndOfDayBalance balance : this.endOfDayBalances) { + + switch (compoundingInterestPeriodType) { + case DAILY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction, + daysInYear); + break; + case MONTHLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case QUATERLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case WEEKLY: + // break; + // case BIWEEKLY: + // break; + case BI_ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; + case INVALID: + break; + } + + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + return interestEarned; + } + + private static List endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval, + final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesForPeriodInterval = new ArrayList(); + + for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) { + + if (compoundingPeriodInterval.contains(endOfDayBalance.date())) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } else if (endOfDayBalance.contains(compoundingPeriodInterval)) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } + } + + return endOfDayBalancesForPeriodInterval; + } + + private BiAnnualCompoundingPeriod(final LocalDateInterval periodInterval, final List endOfDayBalances) { + this.periodInterval = periodInterval; + this.endOfDayBalances = endOfDayBalances; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java index b6d57f6d2..17496966e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java @@ -6,6 +6,7 @@ import java.math.RoundingMode; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.Money; public class EndOfDayBalance { @@ -89,14 +90,19 @@ public class EndOfDayBalance { Money startingBalance = this.openingBalance; LocalDate balanceStartDate = this.date; + + LocalDate oldBalanceEndDate = this.date.plusDays(this.numberOfDays - 1); + + int daysOfBalance = this.numberOfDays; if (this.date.isBefore(compoundingPeriodInterval.startDate())) { balanceStartDate = compoundingPeriodInterval.startDate(); startingBalance = this.endOfDayBalance; + final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, oldBalanceEndDate); + daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate(); } - int daysOfBalance = this.numberOfDays; - LocalDate balanceEndDate = balanceStartDate.plusDays(this.numberOfDays); + LocalDate balanceEndDate = balanceStartDate.plusDays(daysOfBalance - 1); if (balanceEndDate.isAfter(compoundingPeriodInterval.endDate())) { balanceEndDate = compoundingPeriodInterval.endDate(); final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate); @@ -113,7 +119,7 @@ public class EndOfDayBalance { public boolean contains(final LocalDateInterval compoundingPeriodInterval) { - final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays); + final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays - 1); final LocalDateInterval balanceInterval = LocalDateInterval.create(this.date, balanceUpToDate); return balanceInterval.containsPortionOf(compoundingPeriodInterval); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java index 63d72f4ce..06d948531 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java @@ -4,6 +4,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -185,14 +186,83 @@ public class PostingPeriod { // break; // case BIWEEKLY: // break; - // case QUATERLY: - // break; - // case BI_ANNUAL: - // break; - // case ANNUAL: - // break; - // case NO_COMPOUNDING_SIMPLE_INTEREST: - // break; + case QUATERLY: + final LocalDate qPostingPeriodEndDate = postingPeriodInterval.endDate(); + + periodStartDate = postingPeriodInterval.startDate(); + periodEndDate = periodStartDate; + + while (!periodStartDate.isAfter(qPostingPeriodEndDate) && !periodEndDate.isAfter(qPostingPeriodEndDate)) { + + periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate); + if (periodEndDate.isAfter(qPostingPeriodEndDate)) { + periodEndDate = qPostingPeriodEndDate; + } + + final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); + if (postingPeriodInterval.contains(compoundingPeriodInterval)) { + + compoundingPeriod = QuarterlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances, + upToInterestCalculationDate); + compoundingPeriods.add(compoundingPeriod); + } + + // move periodStartDate forward to day after this period + periodStartDate = periodEndDate.plusDays(1); + } + break; + case BI_ANNUAL: + final LocalDate bPostingPeriodEndDate = postingPeriodInterval.endDate(); + + periodStartDate = postingPeriodInterval.startDate(); + periodEndDate = periodStartDate; + + while (!periodStartDate.isAfter(bPostingPeriodEndDate) && !periodEndDate.isAfter(bPostingPeriodEndDate)) { + + periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate); + if (periodEndDate.isAfter(bPostingPeriodEndDate)) { + periodEndDate = bPostingPeriodEndDate; + } + + final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); + if (postingPeriodInterval.contains(compoundingPeriodInterval)) { + + compoundingPeriod = BiAnnualCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances, + upToInterestCalculationDate); + compoundingPeriods.add(compoundingPeriod); + } + + // move periodStartDate forward to day after this period + periodStartDate = periodEndDate.plusDays(1); + } + break; + case ANNUAL: + final LocalDate aPostingPeriodEndDate = postingPeriodInterval.endDate(); + + periodStartDate = postingPeriodInterval.startDate(); + periodEndDate = periodStartDate; + + while (!periodStartDate.isAfter(aPostingPeriodEndDate) && !periodEndDate.isAfter(aPostingPeriodEndDate)) { + + periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType, upToInterestCalculationDate); + if (periodEndDate.isAfter(aPostingPeriodEndDate)) { + periodEndDate = aPostingPeriodEndDate; + } + + final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); + if (postingPeriodInterval.contains(compoundingPeriodInterval)) { + + compoundingPeriod = AnnualCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances, + upToInterestCalculationDate); + compoundingPeriods.add(compoundingPeriod); + } + + // move periodStartDate forward to day after this period + periodStartDate = periodEndDate.plusDays(1); + } + break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; } return compoundingPeriods; @@ -221,39 +291,41 @@ public class PostingPeriod { // produce period end date on last day of current month periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); break; - // case QUATERLY: - // // jan 1st to mar 31st, 1st apr to jun 30, jul 1st to sept 30, - // // oct 1st to dec 31 - // int year = periodStartDate.getYearOfEra(); - // int monthofYear = periodStartDate.getMonthOfYear(); - // if (monthofYear <= 3) { - // periodEndDate = new DateTime().withDate(year, 3, 31).toLocalDate(); - // } else if (monthofYear <= 6) { - // periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); - // } else if (monthofYear <= 9) { - // periodEndDate = new DateTime().withDate(year, 9, 30).toLocalDate(); - // } else if (monthofYear <= 12) { - // periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); - // } - // break; - // case BI_ANNUAL: - // // jan 1st to 30, jul 1st to dec 31 - // year = periodStartDate.getYearOfEra(); - // monthofYear = periodStartDate.getMonthOfYear(); - // if (monthofYear <= 6) { - // periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); - // } else if (monthofYear <= 12) { - // periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); - // } - // break; - // case ANNUAL: - // periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - // break; - // case NO_COMPOUNDING_SIMPLE_INTEREST: - // periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - // break; + case QUATERLY: + // // jan 1st to mar 31st, 1st apr to jun 30, jul 1st to sept + // 30, + // // oct 1st to dec 31 + int year = periodStartDate.getYearOfEra(); + int monthofYear = periodStartDate.getMonthOfYear(); + if (monthofYear <= 3) { + periodEndDate = new DateTime().withDate(year, 3, 31).toLocalDate(); + } else if (monthofYear <= 6) { + periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); + } else if (monthofYear <= 9) { + periodEndDate = new DateTime().withDate(year, 9, 30).toLocalDate(); + } else if (monthofYear <= 12) { + periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); + } + break; + case BI_ANNUAL: + // // jan 1st to 30, jul 1st to dec 31 + year = periodStartDate.getYearOfEra(); + monthofYear = periodStartDate.getMonthOfYear(); + if (monthofYear <= 6) { + periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); + } else if (monthofYear <= 12) { + periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); + } + break; + case ANNUAL: + periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + break; + + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + // break; } return periodEndDate; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java new file mode 100644 index 000000000..0c53c6f1e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/QuarterlyCompoundingPeriod.java @@ -0,0 +1,154 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public class QuarterlyCompoundingPeriod implements CompoundingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final List endOfDayBalances; + + public static QuarterlyCompoundingPeriod create(final LocalDateInterval periodInterval, final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances, upToInterestCalculationDate); + + return new QuarterlyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod); + } + + @Override + public BigDecimal calculateInterest(final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalance(BigDecimal.ZERO, interestRateAsFraction, + daysInYear); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } + + @Override + public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + switch (interestCalculationType) { + case DAILY_BALANCE: + interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, + interestRateAsFraction, daysInYear); + break; + case AVERAGE_DAILY_BALANCE: + interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear); + break; + case INVALID: + break; + } + + return interestEarned; + } + + private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal cumulativeBalance = BigDecimal.ZERO; + Integer numberOfDays = Integer.valueOf(0); + + for (final EndOfDayBalance balance : this.endOfDayBalances) { + final BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound); + cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance); + + final Integer balanceExistsForNumberOfDays = balance.getNumberOfDays(); + numberOfDays = numberOfDays + balanceExistsForNumberOfDays; + } + + BigDecimal interestEarned = BigDecimal.ZERO; + if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) { + final BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64) + .setScale(9, RoundingMode.HALF_EVEN); + + final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64); + final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64); + + interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + return interestEarned; + } + + private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO; + for (final EndOfDayBalance balance : this.endOfDayBalances) { + + switch (compoundingInterestPeriodType) { + case DAILY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction, + daysInYear); + break; + case MONTHLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case QUATERLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case WEEKLY: + // break; + // case BIWEEKLY: + // break; + case BI_ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + case ANNUAL: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; + case INVALID: + break; + } + + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + return interestEarned; + } + + private static List endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval, + final List allEndOfDayBalances, final LocalDate upToInterestCalculationDate) { + + final List endOfDayBalancesForPeriodInterval = new ArrayList(); + + for (final EndOfDayBalance endOfDayBalance : allEndOfDayBalances) { + + if (compoundingPeriodInterval.contains(endOfDayBalance.date())) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } else if (endOfDayBalance.contains(compoundingPeriodInterval)) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval, upToInterestCalculationDate); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } + } + + return endOfDayBalancesForPeriodInterval; + } + + private QuarterlyCompoundingPeriod(final LocalDateInterval periodInterval, final List endOfDayBalances) { + this.periodInterval = periodInterval; + this.endOfDayBalances = endOfDayBalances; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index c773d7d4e..2d38c4478 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -783,6 +783,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); sqlBuilder.append("datp.deposit_amount as depositAmount, "); sqlBuilder.append("datp.maturity_amount as maturityAmount, "); + sqlBuilder.append("datp.expected_firstdepositon_date as expectedFirstDepositOnDate, "); sqlBuilder.append("datp.maturity_date as maturityDate, "); sqlBuilder.append("datp.deposit_period as depositPeriod, "); sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); @@ -847,12 +848,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final Integer onAccountClosureId = JdbcSupport.getInteger(rs, "onAccountClosureId"); final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null : SavingsEnumerations .depositAccountOnClosureType(onAccountClosureId); + final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstDepositOnDate"); return RecurringDepositAccountData.instance(depositAccountData, interestFreePeriodApplicable, interestFreeFromPeriod, interestFreeToPeriod, interestFreePeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, recurringDepositAmount, recurringDepositType, recurringDepositFrequency, - recurringDepositFrequencyType, onAccountClosureType); + recurringDepositFrequencyType, onAccountClosureType, expectedFirstDepositOnDate); } } @@ -1266,13 +1268,14 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final Integer depositPeriod = null; final EnumOptionData depositPeriodFrequencyType = null; final BigDecimal recurringDepositAmount = null; + final LocalDate expectedFirstDepositOnDate = null; final EnumOptionData onAccountClosureType = null; return RecurringDepositAccountData.instance(depositAccountData, interestFreePeriodApplicable, interestFreeFromPeriod, interestFreeToPeriod, interestFreePeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, recurringDepositAmount, recurringDepositType, recurringDepositFrequency, - recurringDepositFrequencyType, onAccountClosureType); + recurringDepositFrequencyType, onAccountClosureType, expectedFirstDepositOnDate); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index ec520aa4a..dbe230c86 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -151,7 +151,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl ((FixedDepositAccount) account).updateMaturityDateAndAmountBeforeAccountActivation(mc); this.fixedDepositAccountRepository.saveAndFlush((FixedDepositAccount) account); } else if (depositAccountType.isRecurringDeposit()) { - final LocalDate transactionStartDate = account.accountSubmittedOrActivationDate(); + final LocalDate transactionStartDate = ((RecurringDepositAccount) account).depositStartDate(); ((RecurringDepositAccount) account).updateMaturityDateAndAmount(mc, transactionStartDate); this.recurringDepositAccountRepository.save((RecurringDepositAccount) account); } @@ -254,7 +254,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl if (depositAccountType.isFixedDeposit()) { ((FixedDepositAccount) account).updateMaturityDateAndAmountBeforeAccountActivation(mc); } else if (depositAccountType.isRecurringDeposit()) { - final LocalDate transactionStartDate = account.accountSubmittedOrActivationDate(); + final LocalDate transactionStartDate = ((RecurringDepositAccount) account).depositStartDate(); ((RecurringDepositAccount) account).updateMaturityDateAndAmount(mc, transactionStartDate); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java index d2005827d..52cf0f0c3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java @@ -275,6 +275,9 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("dprd.recurring_deposit_type_enum as recurringDepositTypeId, "); sqlBuilder.append("dprd.recurring_deposit_frequency as recurringDepositFrequency, "); sqlBuilder.append("dprd.recurring_deposit_frequency_type_enum as recurringDepositFrequencyTypeId, "); + sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, "); + sqlBuilder.append("dptp.deposit_amount as depositAmount, "); + sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, "); sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); @@ -315,6 +318,9 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead .recurringDepositType(recurringDepositTypeId); final Integer recurringDepositFrequency = JdbcSupport.getInteger(rs, "recurringDepositFrequency"); final Integer recurringDepositFrequencyTypeId = JdbcSupport.getInteger(rs, "recurringDepositFrequencyTypeId"); + final BigDecimal minDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minDepositAmount"); + final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount"); + final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount"); final EnumOptionData recurringDepositFrequencyType = (recurringDepositFrequencyTypeId == null) ? null : SavingsEnumerations .recurringDepositFrequencyType(recurringDepositFrequencyTypeId); @@ -334,7 +340,8 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead return RecurringDepositProductData.instance(depositProductData, interestFreePeriodApplicable, interestFreeFromPeriod, interestFreeToPeriod, interestFreePeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, - inMultiplesOfDepositTermType, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType); + inMultiplesOfDepositTermType, recurringDepositType, recurringDepositFrequency, recurringDepositFrequencyType, + minDepositAmount, depositAmount, maxDepositAmount); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java index c3daa0a16..249fb94cf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java @@ -51,12 +51,10 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe // // // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BIWEEKLY), // // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY) - // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY), - // // - // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL), - // // - // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL), + SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY), + SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY), + SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL), + SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL) // // // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST) // // @@ -70,6 +68,7 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe final List allowedOptions = Arrays.asList( // SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY), // SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.QUATERLY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.BIANNUAL), // SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.ANNUAL) // ); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java index 4af80bfe5..c96d2158f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java @@ -232,6 +232,10 @@ public class SavingsEnumerations { optionData = new EnumOptionData(SavingsPostingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix + SavingsPostingInterestPeriodType.QUATERLY.getCode(), "Quarterly"); break; + case BIANNUAL: + optionData = new EnumOptionData(SavingsPostingInterestPeriodType.BIANNUAL.getValue().longValue(), codePrefix + + SavingsPostingInterestPeriodType.BIANNUAL.getCode(), "BiAnnual"); + break; case ANNUAL: optionData = new EnumOptionData(SavingsPostingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix + SavingsPostingInterestPeriodType.ANNUAL.getCode(), "Annually"); @@ -276,26 +280,18 @@ public class SavingsEnumerations { optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix + SavingsCompoundingInterestPeriodType.MONTHLY.getCode(), "Monthly"); break; - // case QUATERLY: - // optionData = new - // EnumOptionData(SavingsCompoundingInterestPeriodType.QUATERLY.getValue().longValue(), - // codePrefix - // + SavingsCompoundingInterestPeriodType.QUATERLY.getCode(), - // "Quarterly"); - // break; - // case BI_ANNUAL: - // optionData = new - // EnumOptionData(SavingsCompoundingInterestPeriodType.BI_ANNUAL.getValue().longValue(), - // codePrefix - // + SavingsCompoundingInterestPeriodType.BI_ANNUAL.getCode(), - // "Semi-Annual"); - // break; - // case ANNUAL: - // optionData = new - // EnumOptionData(SavingsCompoundingInterestPeriodType.ANNUAL.getValue().longValue(), - // codePrefix - // + SavingsCompoundingInterestPeriodType.ANNUAL.getCode(), "Annually"); - // break; + case QUATERLY: + optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix + + SavingsCompoundingInterestPeriodType.QUATERLY.getCode(), "Quarterly"); + break; + case BI_ANNUAL: + optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.BI_ANNUAL.getValue().longValue(), codePrefix + + SavingsCompoundingInterestPeriodType.BI_ANNUAL.getCode(), "Semi-Annual"); + break; + case ANNUAL: + optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix + + SavingsCompoundingInterestPeriodType.ANNUAL.getCode(), "Annually"); + break; // case NO_COMPOUNDING_SIMPLE_INTEREST: // optionData = new // EnumOptionData(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getValue().longValue(),