From cf3c030db7d2e2c4e4050442816edef54e1bd2a1 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 2 May 2013 02:20:23 +0100 Subject: [PATCH] MIFOSX-246: add support for annual fees on product and savings account. need to introduce overnight process to check and update annual fees. --- ...add-support-for-annual-fees-on-savings.sql | 13 + .../core/api/JodaDateTimeAdapter.java | 2 +- .../core/api/JodaLocalDateAdapter.java | 2 +- .../core/api/JodaMonthDayAdapter.java | 37 ++ .../infrastructure/core/api/JsonCommand.java | 5 + ...CommandProcessingResultJsonSerializer.java | 3 + ...tyPrintingOffJsonSerializerGoogleGson.java | 3 + ...ttyPrintingOnJsonSerializerGoogleGson.java | 3 + .../core/serialization/FromJsonHelper.java | 5 + .../GoogleGsonSerializerHelper.java | 4 + .../core/serialization/JsonParserHelper.java | 50 ++ .../loanproduct/domain/LoanProduct.java | 490 ++++++++---------- .../savings/api/SavingsApiConstants.java | 37 +- .../savings/data/SavingsAccountData.java | 35 +- .../data/SavingsAccountDataValidator.java | 29 +- .../data/SavingsAccountSummaryData.java | 6 +- .../savings/data/SavingsProductData.java | 45 +- .../data/SavingsProductDataValidator.java | 23 + .../savings/domain/SavingsAccount.java | 158 +++++- .../domain/SavingsAccountAssembler.java | 21 +- .../savings/domain/SavingsAccountSummary.java | 6 +- .../domain/SavingsAccountTransaction.java | 10 + ...vingsAccountTransactionSummaryWrapper.java | 10 + .../domain/SavingsAccountTransactionType.java | 10 +- .../savings/domain/SavingsProduct.java | 81 ++- .../domain/SavingsProductAssembler.java | 9 +- ...SavingsAccountReadPlatformServiceImpl.java | 42 +- .../savings/service/SavingsEnumerations.java | 5 +- ...SavingsProductReadPlatformServiceImpl.java | 18 +- 29 files changed, 809 insertions(+), 353 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaMonthDayAdapter.java diff --git a/mifosng-db/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql b/mifosng-db/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql new file mode 100644 index 000000000..14c209cc6 --- /dev/null +++ b/mifosng-db/migrations/core_db/V29__add-support-for-annual-fees-on-savings.sql @@ -0,0 +1,13 @@ +ALTER TABLE `m_savings_product` +ADD COLUMN `annual_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `withdrawal_fee_type_enum`, +ADD COLUMN `annual_fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_amount`, +ADD COLUMN `annual_fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_on_month`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `annual_fee_amount` DECIMAL(19,6) NULL DEFAULT NULL AFTER `withdrawal_fee_type_enum`, +ADD COLUMN `annual_fee_on_month` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_amount`, +ADD COLUMN `annual_fee_on_day` SMALLINT(5) NULL DEFAULT NULL AFTER `annual_fee_on_month`, +ADD COLUMN `annual_fee_next_due_date` DATE NULL DEFAULT NULL AFTER `annual_fee_on_day`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `total_annual_fees_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_withdrawal_fees_derived`; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaDateTimeAdapter.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaDateTimeAdapter.java index bbba7e818..346f9f032 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaDateTimeAdapter.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaDateTimeAdapter.java @@ -22,7 +22,7 @@ public class JodaDateTimeAdapter implements JsonSerializer { @SuppressWarnings("unused") @Override - public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(final DateTime src, final Type typeOfSrc, final JsonSerializationContext context) { JsonElement element = null; if (src != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaLocalDateAdapter.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaLocalDateAdapter.java index 4fe5cabaf..ff339fe1a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaLocalDateAdapter.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaLocalDateAdapter.java @@ -23,7 +23,7 @@ public class JodaLocalDateAdapter implements JsonSerializer { @SuppressWarnings("unused") @Override - public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(final LocalDate src, final Type typeOfSrc, final JsonSerializationContext context) { JsonArray array = null; if (src != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaMonthDayAdapter.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaMonthDayAdapter.java new file mode 100644 index 000000000..e3a597c22 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JodaMonthDayAdapter.java @@ -0,0 +1,37 @@ +/** + * 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.infrastructure.core.api; + +import java.lang.reflect.Type; + +import org.joda.time.MonthDay; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * Serializer for joda time {@link MonthDay} that returns date in array format + * to match previous jackson functionality. + */ +public class JodaMonthDayAdapter implements JsonSerializer { + + @SuppressWarnings("unused") + @Override + public JsonElement serialize(final MonthDay src, final Type typeOfSrc, final JsonSerializationContext context) { + + JsonArray array = null; + if (src != null) { + array = new JsonArray(); + array.add(new JsonPrimitive(src.getMonthOfYear())); + array.add(new JsonPrimitive(src.getDayOfMonth())); + } + + return array; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index f96d71562..aaa423eac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -13,6 +13,7 @@ import java.util.Locale; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.domain.BasicPasswordEncodablePlatformUser; import org.mifosplatform.infrastructure.security.domain.PlatformUser; @@ -261,6 +262,10 @@ public final class JsonCommand { return this.fromApiJsonHelper.extractLocalDateNamed(parameterName, parsedCommand); } + public MonthDay extractMonthDayNamed(final String parameterName) { + return this.fromApiJsonHelper.extractMonthDayNamed(parameterName, parsedCommand); + } + public Date DateValueOfParameterNamed(final String parameterName) { LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(parameterName, parsedCommand); if (localDate == null) { return null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java index 02f7d02ff..094edcf25 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/CommandProcessingResultJsonSerializer.java @@ -7,8 +7,10 @@ package org.mifosplatform.infrastructure.core.serialization; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter; import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter; +import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter; import org.springframework.stereotype.Component; import com.google.gson.Gson; @@ -28,6 +30,7 @@ public final class CommandProcessingResultJsonSerializer { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); builder.serializeNulls(); this.gson = builder.create(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java index 9b7cd0e3c..6630662e7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson.java @@ -7,8 +7,10 @@ package org.mifosplatform.infrastructure.core.serialization; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter; import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter; +import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter; import org.springframework.stereotype.Component; import com.google.gson.Gson; @@ -28,6 +30,7 @@ public final class ExcludeNothingWithPrettyPrintingOffJsonSerializerGoogleGson { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); this.gson = builder.create(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java index cd9e8f406..cb301db97 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson.java @@ -7,8 +7,10 @@ package org.mifosplatform.infrastructure.core.serialization; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter; import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter; +import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter; import org.springframework.stereotype.Component; import com.google.gson.Gson; @@ -28,6 +30,7 @@ public final class ExcludeNothingWithPrettyPrintingOnJsonSerializerGoogleGson { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); builder.setPrettyPrinting(); this.gson = builder.create(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java index 96991676a..b861aeb16 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/FromJsonHelper.java @@ -16,6 +16,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; import org.springframework.stereotype.Component; @@ -114,6 +115,10 @@ public class FromJsonHelper { return helperDelegator.extractBooleanNamed(parameterName, element, parametersPassedInRequest); } + public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) { + return helperDelegator.extractMonthDayNamed(parameterName, element); + } + public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element) { return helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index b70b5215d..13794f0cc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -11,8 +11,10 @@ import java.util.Set; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter; import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter; +import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter; import org.mifosplatform.infrastructure.core.api.ParameterListExclusionStrategy; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; import org.springframework.stereotype.Service; @@ -31,6 +33,7 @@ public final class GoogleGsonSerializerHelper { final GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); if (prettyPrint) { builder.setPrettyPrinting(); } @@ -61,6 +64,7 @@ public final class GoogleGsonSerializerHelper { final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy); builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); if (prettyPrint) { builder.setPrettyPrinting(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java index bbe91d628..875a31202 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/JsonParserHelper.java @@ -18,7 +18,9 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.springframework.format.number.NumberFormatter; @@ -181,6 +183,20 @@ public class JsonParserHelper { return value; } + public String extractMonthDayFormatParameter(final JsonObject element) { + String value = null; + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + + final String monthDayFormatParameter = "monthDayFormat"; + if (object.has(monthDayFormatParameter) && object.get(monthDayFormatParameter).isJsonPrimitive()) { + final JsonPrimitive primitive = object.get(monthDayFormatParameter).getAsJsonPrimitive(); + value = primitive.getAsString(); + } + } + return value; + } + public Locale extractLocaleParameter(final JsonObject element) { Locale clientApplicationLocale = null; if (element.isJsonObject()) { @@ -252,6 +268,40 @@ public class JsonParserHelper { return value; } + public MonthDay extractMonthDayNamed(final String parameterName, final JsonElement element) { + + MonthDay value = null; + + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + + final String monthDayFormat = extractMonthDayFormatParameter(object); + final Locale clientApplicationLocale = extractLocaleParameter(object); + value = extractMonthDayNamed(parameterName, object, monthDayFormat, clientApplicationLocale); + } + return value; + } + + public MonthDay extractMonthDayNamed(final String parameterName, final JsonObject element, final String dateFormat, + final Locale clientApplicationLocale) { + MonthDay value = null; + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + + if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) { + + final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive(); + final String valueAsString = primitive.getAsString(); + if (StringUtils.isNotBlank(valueAsString)) { + DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat).withLocale(clientApplicationLocale); + value = MonthDay.parse(valueAsString.toLowerCase(clientApplicationLocale), formatter); + } + } + + } + return value; + } + public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final Set parametersPassedInCommand) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 72f19367d..d1e781211 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -45,335 +45,261 @@ import com.google.gson.JsonArray; @Table(name = "m_product_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") }) public class LoanProduct extends AbstractPersistable { - @ManyToOne - @JoinColumn(name = "fund_id", nullable = true) - private Fund fund; + @ManyToOne + @JoinColumn(name = "fund_id", nullable = true) + private Fund fund; - @ManyToOne - @JoinColumn(name = "loan_transaction_strategy_id", nullable = true) - private LoanTransactionProcessingStrategy transactionProcessingStrategy; + @ManyToOne + @JoinColumn(name = "loan_transaction_strategy_id", nullable = true) + private LoanTransactionProcessingStrategy transactionProcessingStrategy; - @Column(name = "name", nullable = false, unique = true) - private String name; + @Column(name = "name", nullable = false, unique = true) + private String name; - @Column(name = "description") - private String description; + @Column(name = "description") + private String description; - @ManyToMany - @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) - private List charges; + @ManyToMany + @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) + private List charges; - @Embedded - private final LoanProductRelatedDetail loanProductRelatedDetail; + @Embedded + private final LoanProductRelatedDetail loanProductRelatedDetail; - @Embedded - private LoanProductMinMaxConstraints loanProductMinMaxConstraints; + @Embedded + private LoanProductMinMaxConstraints loanProductMinMaxConstraints; - @Column(name = "accounting_type", nullable = false) - private Integer accountingRule; + @Column(name = "accounting_type", nullable = false) + private Integer accountingRule; - public static LoanProduct assembleFromJson( - final Fund fund, - final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, - final List productCharges, final JsonCommand command, - final AprCalculator aprCalculator) { + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, + final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) { - final String name = command.stringValueOfParameterNamed("name"); - final String description = command - .stringValueOfParameterNamed("description"); - final String currencyCode = command - .stringValueOfParameterNamed("currencyCode"); - final Integer digitsAfterDecimal = command - .integerValueOfParameterNamed("digitsAfterDecimal"); + final String name = command.stringValueOfParameterNamed("name"); + final String description = command.stringValueOfParameterNamed("description"); + final String currencyCode = command.stringValueOfParameterNamed("currencyCode"); + final Integer digitsAfterDecimal = command.integerValueOfParameterNamed("digitsAfterDecimal"); - final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, - digitsAfterDecimal); - final BigDecimal principal = command - .bigDecimalValueOfParameterNamed("principal"); - final BigDecimal minPrincipal = command - .bigDecimalValueOfParameterNamed("minPrincipal"); - final BigDecimal maxPrincipal = command - .bigDecimalValueOfParameterNamed("maxPrincipal"); + final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); + final BigDecimal principal = command.bigDecimalValueOfParameterNamed("principal"); + final BigDecimal minPrincipal = command.bigDecimalValueOfParameterNamed("minPrincipal"); + final BigDecimal maxPrincipal = command.bigDecimalValueOfParameterNamed("maxPrincipal"); - final InterestMethod interestMethod = InterestMethod.fromInt(command - .integerValueOfParameterNamed("interestType")); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod - .fromInt(command - .integerValueOfParameterNamed("interestCalculationPeriodType")); - final AmortizationMethod amortizationMethod = AmortizationMethod - .fromInt(command - .integerValueOfParameterNamed("amortizationType")); - final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType - .fromInt(command - .integerValueOfParameterNamed("repaymentFrequencyType")); - final PeriodFrequencyType interestFrequencyType = PeriodFrequencyType - .fromInt(command - .integerValueOfParameterNamed("interestRateFrequencyType")); - final BigDecimal interestRatePerPeriod = command - .bigDecimalValueOfParameterNamed("interestRatePerPeriod"); - final BigDecimal minInterestRatePerPeriod = command - .bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); - final BigDecimal maxInterestRatePerPeriod = command - .bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); - final BigDecimal annualInterestRate = aprCalculator.calculateFrom( - interestFrequencyType, interestRatePerPeriod); + final InterestMethod interestMethod = InterestMethod.fromInt(command.integerValueOfParameterNamed("interestType")); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command + .integerValueOfParameterNamed("interestCalculationPeriodType")); + final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.integerValueOfParameterNamed("amortizationType")); + final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command + .integerValueOfParameterNamed("repaymentFrequencyType")); + final PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.fromInt(command + .integerValueOfParameterNamed("interestRateFrequencyType")); + final BigDecimal interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod"); + final BigDecimal minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); + final BigDecimal maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); + final BigDecimal annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod); - final Integer repaymentEvery = command - .integerValueOfParameterNamed("repaymentEvery"); - final Integer numberOfRepayments = command - .integerValueOfParameterNamed("numberOfRepayments"); - final Integer minNumberOfRepayments = command - .integerValueOfParameterNamed("minNumberOfRepayments"); - final Integer maxNumberOfRepayments = command - .integerValueOfParameterNamed("maxNumberOfRepayments"); - final BigDecimal inArrearsTolerance = command - .bigDecimalValueOfParameterNamed("inArrearsTolerance"); - final AccountingRuleType accountingRuleType = AccountingRuleType - .fromInt(command.integerValueOfParameterNamed("accountingRule")); + final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery"); + final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); + final Integer minNumberOfRepayments = command.integerValueOfParameterNamed("minNumberOfRepayments"); + final Integer maxNumberOfRepayments = command.integerValueOfParameterNamed("maxNumberOfRepayments"); + final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance"); + final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); - return new LoanProduct(fund, loanTransactionProcessingStrategy, name, - description, currency, principal, minPrincipal, maxPrincipal, - interestRatePerPeriod, minInterestRatePerPeriod, - maxInterestRatePerPeriod, interestFrequencyType, - annualInterestRate, interestMethod, - interestCalculationPeriodMethod, repaymentEvery, - repaymentFrequencyType, numberOfRepayments, - minNumberOfRepayments, maxNumberOfRepayments, - amortizationMethod, inArrearsTolerance, productCharges, - accountingRuleType); - } + return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal, + interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, + interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments, + minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType); + } - protected LoanProduct() { - this.loanProductRelatedDetail = null; - this.loanProductMinMaxConstraints = null; - } + protected LoanProduct() { + this.loanProductRelatedDetail = null; + this.loanProductMinMaxConstraints = null; + } - public LoanProduct( - final Fund fund, - final LoanTransactionProcessingStrategy transactionProcessingStrategy, - final String name, - final String description, - final MonetaryCurrency currency, - final BigDecimal defaultPrincipal, - final BigDecimal defaultMinPrincipal, - final BigDecimal defaultMaxPrincipal, - final BigDecimal defaultNominalInterestRatePerPeriod, - final BigDecimal defaultMinNominalInterestRatePerPeriod, - final BigDecimal defaultMaxNominalInterestRatePerPeriod, - final PeriodFrequencyType interestPeriodFrequencyType, - final BigDecimal defaultAnnualNominalInterestRate, - final InterestMethod interestMethod, - final InterestCalculationPeriodMethod interestCalculationPeriodMethod, - final Integer repayEvery, - final PeriodFrequencyType repaymentFrequencyType, - final Integer defaultNumberOfInstallments, - final Integer defaultMinNumberOfInstallments, - final Integer defaultMaxNumberOfInstallments, - final AmortizationMethod amortizationMethod, - final BigDecimal inArrearsTolerance, final List charges, - final AccountingRuleType accountingRuleType) { - this.fund = fund; - this.transactionProcessingStrategy = transactionProcessingStrategy; - this.name = name.trim(); - if (StringUtils.isNotBlank(description)) { - this.description = description.trim(); - } else { - this.description = null; - } + public LoanProduct(final Fund fund, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final String name, + final String description, final MonetaryCurrency currency, final BigDecimal defaultPrincipal, + final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal, + final BigDecimal defaultNominalInterestRatePerPeriod, final BigDecimal defaultMinNominalInterestRatePerPeriod, + final BigDecimal defaultMaxNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, + final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery, + final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, + final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments, + final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List charges, + final AccountingRuleType accountingRuleType) { + this.fund = fund; + this.transactionProcessingStrategy = transactionProcessingStrategy; + this.name = name.trim(); + if (StringUtils.isNotBlank(description)) { + this.description = description.trim(); + } else { + this.description = null; + } - if (charges != null) { - this.charges = charges; - } + if (charges != null) { + this.charges = charges; + } - this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, - defaultPrincipal, defaultNominalInterestRatePerPeriod, - interestPeriodFrequencyType, defaultAnnualNominalInterestRate, - interestMethod, interestCalculationPeriodMethod, repayEvery, - repaymentFrequencyType, defaultNumberOfInstallments, - amortizationMethod, inArrearsTolerance); + this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance); - this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints( - defaultMinPrincipal, defaultMaxPrincipal, - defaultMinNominalInterestRatePerPeriod, - defaultMaxNominalInterestRatePerPeriod, - defaultMinNumberOfInstallments, defaultMaxNumberOfInstallments); + this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal, + defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments, + defaultMaxNumberOfInstallments); - if (accountingRuleType != null) { - this.accountingRule = accountingRuleType.getValue(); - } - } + if (accountingRuleType != null) { + this.accountingRule = accountingRuleType.getValue(); + } + } - public MonetaryCurrency getCurrency() { - return this.loanProductRelatedDetail.getCurrency(); - } + public MonetaryCurrency getCurrency() { + return this.loanProductRelatedDetail.getCurrency(); + } - public void update(final Fund fund) { - this.fund = fund; - } + public void update(final Fund fund) { + this.fund = fund; + } - public void update(final LoanTransactionProcessingStrategy strategy) { - this.transactionProcessingStrategy = strategy; - } + public void update(final LoanTransactionProcessingStrategy strategy) { + this.transactionProcessingStrategy = strategy; + } - public boolean update(final List newProductCharges) { - if (newProductCharges == null) - return false; + public boolean update(final List newProductCharges) { + if (newProductCharges == null) return false; - boolean updated = false; - if (this.charges != null) { - final Set currentSetOfCharges = new HashSet( - this.charges); - final Set newSetOfCharges = new HashSet( - newProductCharges); + boolean updated = false; + if (this.charges != null) { + final Set currentSetOfCharges = new HashSet(this.charges); + final Set newSetOfCharges = new HashSet(newProductCharges); - if (!(currentSetOfCharges.equals(newSetOfCharges))) { - updated = true; - this.charges = newProductCharges; - } - } else { - updated = true; - this.charges = newProductCharges; - } - return updated; - } + if (!(currentSetOfCharges.equals(newSetOfCharges))) { + updated = true; + this.charges = newProductCharges; + } + } else { + updated = true; + this.charges = newProductCharges; + } + return updated; + } - public Integer getAccountingType() { - return this.accountingRule; - } + public Integer getAccountingType() { + return this.accountingRule; + } - public Map update(final JsonCommand command, - final AprCalculator aprCalculator) { + public Map update(final JsonCommand command, final AprCalculator aprCalculator) { - final Map actualChanges = this.loanProductRelatedDetail - .update(command, aprCalculator); - actualChanges.putAll(this.loanProductMinMaxConstraints() - .update(command)); + final Map actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator); + actualChanges.putAll(this.loanProductMinMaxConstraints().update(command)); - final String accountingTypeParamName = "accountingRule"; - if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, - this.accountingRule)) { - final Integer newValue = command - .integerValueOfParameterNamed(accountingTypeParamName); - actualChanges.put(accountingTypeParamName, newValue); - this.accountingRule = newValue; - } + final String accountingTypeParamName = "accountingRule"; + if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) { + final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName); + actualChanges.put(accountingTypeParamName, newValue); + this.accountingRule = newValue; + } - final String nameParamName = "name"; - if (command.isChangeInStringParameterNamed(nameParamName, this.name)) { - final String newValue = command - .stringValueOfParameterNamed(nameParamName); - actualChanges.put(nameParamName, newValue); - this.name = newValue; - } + final String nameParamName = "name"; + if (command.isChangeInStringParameterNamed(nameParamName, this.name)) { + final String newValue = command.stringValueOfParameterNamed(nameParamName); + actualChanges.put(nameParamName, newValue); + this.name = newValue; + } - final String descriptionParamName = "description"; - if (command.isChangeInStringParameterNamed(descriptionParamName, - this.description)) { - final String newValue = command - .stringValueOfParameterNamed(descriptionParamName); - actualChanges.put(descriptionParamName, newValue); - this.description = newValue; - } + final String descriptionParamName = "description"; + if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) { + final String newValue = command.stringValueOfParameterNamed(descriptionParamName); + actualChanges.put(descriptionParamName, newValue); + this.description = newValue; + } - Long existingFundId = null; - if (this.fund != null) { - existingFundId = this.fund.getId(); - } - final String fundIdParamName = "fundId"; - if (command.isChangeInLongParameterNamed(fundIdParamName, - existingFundId)) { - final Long newValue = command - .longValueOfParameterNamed(fundIdParamName); - actualChanges.put(fundIdParamName, newValue); - } + Long existingFundId = null; + if (this.fund != null) { + existingFundId = this.fund.getId(); + } + final String fundIdParamName = "fundId"; + if (command.isChangeInLongParameterNamed(fundIdParamName, existingFundId)) { + final Long newValue = command.longValueOfParameterNamed(fundIdParamName); + actualChanges.put(fundIdParamName, newValue); + } - Long existingStrategyId = null; - if (this.transactionProcessingStrategy != null) { - existingStrategyId = this.transactionProcessingStrategy.getId(); - } - final String transactionProcessingStrategyParamName = "transactionProcessingStrategyId"; - if (command.isChangeInLongParameterNamed( - transactionProcessingStrategyParamName, existingStrategyId)) { - final Long newValue = command - .longValueOfParameterNamed(transactionProcessingStrategyParamName); - actualChanges.put(transactionProcessingStrategyParamName, newValue); - } + Long existingStrategyId = null; + if (this.transactionProcessingStrategy != null) { + existingStrategyId = this.transactionProcessingStrategy.getId(); + } + final String transactionProcessingStrategyParamName = "transactionProcessingStrategyId"; + if (command.isChangeInLongParameterNamed(transactionProcessingStrategyParamName, existingStrategyId)) { + final Long newValue = command.longValueOfParameterNamed(transactionProcessingStrategyParamName); + actualChanges.put(transactionProcessingStrategyParamName, newValue); + } - final String chargesParamName = "charges"; - if (command.hasParameter(chargesParamName)) { - JsonArray jsonArray = command - .arrayOfParameterNamed(chargesParamName); - if (jsonArray != null) { + final String chargesParamName = "charges"; + if (command.hasParameter(chargesParamName)) { + JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName); + if (jsonArray != null) { - } - actualChanges.put(chargesParamName, - command.jsonFragment(chargesParamName)); - } + } + actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName)); + } - return actualChanges; - } + return actualChanges; + } - public boolean isCashBasedAccountingEnabled() { - return AccountingRuleType.CASH_BASED.getValue().equals( - this.accountingRule); - } + public boolean isCashBasedAccountingEnabled() { + return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule); + } - public boolean isAccrualBasedAccountingEnabled() { - return AccountingRuleType.ACCRUAL_BASED.getValue().equals( - this.accountingRule); - } + public boolean isAccrualBasedAccountingEnabled() { + return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule); + } - public Money getPrincipalAmount() { - return this.loanProductRelatedDetail.getPrincipal(); - } + public Money getPrincipalAmount() { + return this.loanProductRelatedDetail.getPrincipal(); + } - public Money getMinPrincipalAmount() { - return Money.of(this.loanProductRelatedDetail.getCurrency(), this - .loanProductMinMaxConstraints().getMinPrincipal()); - } + public Money getMinPrincipalAmount() { + return Money.of(this.loanProductRelatedDetail.getCurrency(), this.loanProductMinMaxConstraints().getMinPrincipal()); + } - public Money getMaxPrincipalAmount() { - return Money.of(this.loanProductRelatedDetail.getCurrency(), this - .loanProductMinMaxConstraints().getMaxPrincipal()); - } + public Money getMaxPrincipalAmount() { + return Money.of(this.loanProductRelatedDetail.getCurrency(), this.loanProductMinMaxConstraints().getMaxPrincipal()); + } - public BigDecimal getNominalInterestRatePerPeriod() { - return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod(); - } + public BigDecimal getNominalInterestRatePerPeriod() { + return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod(); + } - public BigDecimal getMinNominalInterestRatePerPeriod() { - return this.loanProductMinMaxConstraints() - .getMinNominalInterestRatePerPeriod(); - } + public BigDecimal getMinNominalInterestRatePerPeriod() { + return this.loanProductMinMaxConstraints().getMinNominalInterestRatePerPeriod(); + } - public BigDecimal getMaxNominalInterestRatePerPeriod() { - return this.loanProductMinMaxConstraints() - .getMaxNominalInterestRatePerPeriod(); - } + public BigDecimal getMaxNominalInterestRatePerPeriod() { + return this.loanProductMinMaxConstraints().getMaxNominalInterestRatePerPeriod(); + } - public Integer getNumberOfRepayments() { - return this.loanProductRelatedDetail().getNumberOfRepayments(); - } + public Integer getNumberOfRepayments() { + return this.loanProductRelatedDetail().getNumberOfRepayments(); + } - public Integer getMinNumberOfRepayments() { - return this.loanProductMinMaxConstraints().getMinNumberOfRepayments(); - } + public Integer getMinNumberOfRepayments() { + return this.loanProductMinMaxConstraints().getMinNumberOfRepayments(); + } - public Integer getMaxNumberOfRepayments() { - return this.loanProductMinMaxConstraints().getMaxNumberOfRepayments(); - } + public Integer getMaxNumberOfRepayments() { + return this.loanProductMinMaxConstraints().getMaxNumberOfRepayments(); + } - public LoanProductRelatedDetail loanProductRelatedDetail() { - return this.loanProductRelatedDetail; - } + public LoanProductRelatedDetail loanProductRelatedDetail() { + return this.loanProductRelatedDetail; + } - public LoanProductMinMaxConstraints loanProductMinMaxConstraints() { - // If all min and max fields are null then loanProductMinMaxConstraints - // initialising to null - // Reset LoanProductMinMaxConstraints with null values. - this.loanProductMinMaxConstraints = (this.loanProductMinMaxConstraints == null) ? new LoanProductMinMaxConstraints( - null, null, null, null, null, null) - : this.loanProductMinMaxConstraints; - return loanProductMinMaxConstraints; - } + public LoanProductMinMaxConstraints loanProductMinMaxConstraints() { + // If all min and max fields are null then loanProductMinMaxConstraints + // initialising to null + // Reset LoanProductMinMaxConstraints with null values. + this.loanProductMinMaxConstraints = (this.loanProductMinMaxConstraints == null) ? new LoanProductMinMaxConstraints(null, null, + null, null, null, null) : this.loanProductMinMaxConstraints; + return loanProductMinMaxConstraints; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java index 9d41516e4..f27fd49ab 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java @@ -16,6 +16,7 @@ public class SavingsApiConstants { // general public static final String localeParamName = "locale"; public static final String dateFormatParamName = "dateFormat"; + public static final String monthDayFormatParamName = "monthDayFormat"; // savings product and account parameters public static final String idParamName = "id"; @@ -41,16 +42,19 @@ public class SavingsApiConstants { public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType"; public static final String withdrawalFeeAmountParamName = "withdrawalFeeAmount"; public static final String withdrawalFeeTypeParamName = "withdrawalFeeType"; + public static final String annualFeeAmountParamName = "annualFeeAmount"; + public static final String annualFeeOnMonthDayParamName = "annualFeeOnMonthDay"; // transaction parameters public static final String transactionDateParamName = "transactionDate"; public static final String transactionAmountParamName = "transactionAmount"; public static final Set SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, - nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, nominalAnnualInterestRateParamName, - interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, - interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName)); + monthDayFormatParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, + nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, + interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, + lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, + annualFeeAmountParamName, annualFeeOnMonthDayParamName)); /** * These parameters will match the class level parameters of @@ -61,16 +65,18 @@ public class SavingsApiConstants { nameParamName, descriptionParamName, "currency", digitsAfterDecimalParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, "currencyOptions", - "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", - "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions")); + lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, + annualFeeOnMonthDayParamName, "currencyOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", + "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", + "withdrawalFeeTypeOptions")); public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, - dateFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, - activeParamName, activationDateParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, - interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, - minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, - withdrawalFeeAmountParamName, withdrawalFeeTypeParamName)); + dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, + productIdParamName, activeParamName, activationDateParamName, nominalAnnualInterestRateParamName, + interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, + interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, + lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, + annualFeeOnMonthDayParamName)); /** * These parameters will match the class level parameters of @@ -82,9 +88,10 @@ public class SavingsApiConstants { groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, - withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, "summary", "transactions", "productOptions", - "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", - "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions")); + withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, "summary", + "transactions", "productOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", + "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", + "withdrawalFeeTypeOptions")); public static final Set SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java index 28b78057c..abdd18455 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; import java.util.Collection; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; @@ -39,6 +40,9 @@ public class SavingsAccountData { private final EnumOptionData lockinPeriodFrequencyType; private final BigDecimal withdrawalFeeAmount; private final EnumOptionData withdrawalFeeType; + private final BigDecimal annualFeeAmount; + private final MonthDay annualFeeOnMonthDay; + private final LocalDate annualFeeNextDueDate; // associations private final SavingsAccountSummaryData summary; @@ -68,7 +72,8 @@ public class SavingsAccountData { final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final SavingsAccountSummaryData summary) { + final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary) { final Collection productOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -83,9 +88,10 @@ public class SavingsAccountData { return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, summary, transactions, productOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, + annualFeeNextDueDate, summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, + interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -101,9 +107,10 @@ public class SavingsAccountData { account.savingsProductName, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, - account.withdrawalFeeAmount, account.withdrawalFeeType, account.summary, transactions, productOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + account.withdrawalFeeAmount, account.withdrawalFeeType, account.annualFeeAmount, account.annualFeeOnMonthDay, + account.annualFeeNextDueDate, account.summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, + interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -127,6 +134,9 @@ public class SavingsAccountData { final EnumOptionData lockinPeriodFrequencyType = null; final BigDecimal withdrawalFeeAmount = null; final EnumOptionData withdrawalFeeType = null; + final BigDecimal annualFeeAmount = null; + final MonthDay annualFeeOnMonthDay = null; + final LocalDate annualFeeNextDueDate = null; final SavingsAccountSummaryData summary = null; final Collection transactions = null; @@ -142,9 +152,10 @@ public class SavingsAccountData { return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, summary, transactions, productOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, + annualFeeNextDueDate, summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, + interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } private SavingsAccountData(final Long id, final String accountNo, final String externalId, final SavingsAccountStatusEnumData status, @@ -154,6 +165,7 @@ public class SavingsAccountData { final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType, + final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary, final Collection transactions, final Collection productOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, @@ -182,6 +194,9 @@ public class SavingsAccountData { this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; this.withdrawalFeeAmount = withdrawalFeeAmount; this.withdrawalFeeType = withdrawalFeeType; + this.annualFeeAmount = annualFeeAmount; + this.annualFeeOnMonthDay = annualFeeOnMonthDay; + this.annualFeeNextDueDate = annualFeeNextDueDate; this.summary = summary; this.transactions = transactions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java index f4038c65d..3bdc1459a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java @@ -3,6 +3,8 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNoParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.externalIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.groupIdParamName; @@ -26,6 +28,7 @@ import java.util.Map; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -173,6 +176,16 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); } + if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().positiveAmount(); + + if (annualFeeAmount != null) { + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull(); + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -316,15 +329,21 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); } + if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().positiveAmount(); + } + + if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { - if (!dataValidationErrors.isEmpty()) { - // - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors); - } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java index 8844f4c18..418bb375b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountSummaryData.java @@ -19,17 +19,19 @@ public class SavingsAccountSummaryData { private final BigDecimal totalDeposits; private final BigDecimal totalWithdrawals; private final BigDecimal totalWithdrawalFees; + private final BigDecimal totalAnnualFees; private final BigDecimal totalInterestEarned; private final BigDecimal totalInterestPosted; private final BigDecimal accountBalance; public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals, - final BigDecimal totalWithdrawalFees, final BigDecimal totalInterestEarned, final BigDecimal totalInterestPosted, - final BigDecimal accountBalance) { + final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned, + final BigDecimal totalInterestPosted, final BigDecimal accountBalance) { this.currency = currency; this.totalDeposits = totalDeposits; this.totalWithdrawals = totalWithdrawals; this.totalWithdrawalFees = totalWithdrawalFees; + this.totalAnnualFees = totalAnnualFees; this.totalInterestEarned = totalInterestEarned; this.totalInterestPosted = totalInterestPosted; this.accountBalance = accountBalance; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index ef7a50326..d0ccb1fb0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; import java.util.Collection; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; @@ -30,6 +31,8 @@ public class SavingsProductData { private final EnumOptionData lockinPeriodFrequencyType; private final BigDecimal withdrawalFeeAmount; private final EnumOptionData withdrawalFeeType; + private final BigDecimal annualFeeAmount; + private final MonthDay annualFeeOnMonthDay; // template @SuppressWarnings("unused") @@ -65,12 +68,15 @@ public class SavingsProductData { final EnumOptionData lockinPeriodFrequencyType = null; final BigDecimal withdrawalFeeAmount = null; final EnumOptionData withdrawalFeeType = null; + final BigDecimal annualFeeAmount = null; + final MonthDay annualFeeMonthDay = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, currencyOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions); } /** @@ -89,9 +95,9 @@ public class SavingsProductData { existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount, - existingProduct.withdrawalFeeType, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + existingProduct.withdrawalFeeType, existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, currencyOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, @@ -99,7 +105,7 @@ public class SavingsProductData { final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType) { + final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) { final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -111,9 +117,10 @@ public class SavingsProductData { return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, currencyOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions); } public static SavingsProductData lookup(final Long id, final String name) { @@ -130,6 +137,8 @@ public class SavingsProductData { final EnumOptionData lockinPeriodFrequencyType = null; final BigDecimal withdrawalFeeAmount = null; final EnumOptionData withdrawalFeeType = null; + final BigDecimal annualFeeAmount = null; + final MonthDay annualFeeMonthDay = null; final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -141,19 +150,19 @@ public class SavingsProductData { return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, currencyOptions, - interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType, - final Collection currencyOptions, - final Collection interestCompoundingPeriodTypeOptions, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, + final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, @@ -172,6 +181,8 @@ public class SavingsProductData { this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; this.withdrawalFeeAmount = withdrawalFeeAmount; this.withdrawalFeeType = withdrawalFeeType; + this.annualFeeAmount = annualFeeAmount; + this.annualFeeOnMonthDay = annualFeeOnMonthDay; this.currencyOptions = currencyOptions; this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index 95724be3e..aacdc063c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -2,6 +2,8 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; @@ -24,6 +26,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -136,6 +139,16 @@ public class SavingsProductDataValidator { } } + if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().positiveAmount(); + + if (annualFeeAmount != null) { + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).notNull(); + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -222,6 +235,16 @@ public class SavingsProductDataValidator { .isOneOfTheseValues(1, 2); } + if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().positiveAmount(); + } + + if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 7c2d65e11..5303d1370 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -5,6 +5,12 @@ */ package org.mifosplatform.portfolio.savings.domain; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.localeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; + import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; @@ -34,6 +40,7 @@ import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.DateTime; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -142,6 +149,19 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "withdrawal_fee_type_enum", nullable = true) private Integer withdrawalFeeType; + @Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true) + private BigDecimal annualFeeAmount; + + @Column(name = "annual_fee_on_month", nullable = true) + private Integer annualFeeOnMonth; + + @Column(name = "annual_fee_on_day", nullable = true) + private Integer annualFeeOnDay; + + @Temporal(TemporalType.DATE) + @Column(name = "annual_fee_next_due_date", nullable = true) + private Date annualFeeNextDueDate; + @Embedded private SavingsAccountSummary summary; @@ -165,13 +185,15 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType) { + final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, + final MonthDay annualFeeOnMonthDay) { final SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED; final LocalDate activationDate = null; return new SavingsAccount(client, group, product, accountNo, externalId, status, activationDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType); + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, + annualFeeAmount, annualFeeOnMonthDay); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final String accountNo, @@ -180,7 +202,8 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType) { + final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, + final MonthDay annualFeeOnMonthDay) { this.client = client; this.group = group; this.product = product; @@ -210,9 +233,27 @@ public class SavingsAccount extends AbstractPersistable { if (withdrawalFeeType != null) { this.withdrawalFeeType = withdrawalFeeType.getValue(); } + + this.annualFeeAmount = annualFeeAmount; + if (annualFeeOnMonthDay != null) { + this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear(); + this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); + + updateAnnualFeeNextDueDate(); + } + this.summary = new SavingsAccountSummary(); } + private void updateAnnualFeeNextDueDate() { + LocalDate nextDueLocalDate = new LocalDate().withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay); + if (nextDueLocalDate.isBefore(DateUtils.getLocalDateOfTenant())) { + nextDueLocalDate = nextDueLocalDate.plusYears(1); + } + + this.annualFeeNextDueDate = nextDueLocalDate.toDate(); + } + /** * Used after fetching/hydrating a {@link SavingsAccount} object to inject * helper services/components used for update summary details after @@ -834,6 +875,10 @@ public class SavingsAccount extends AbstractPersistable { } } + final Money annualFee = Money.of(this.currency, this.annualFeeAmount); + SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee); + this.transactions.add(annualFeeTransaction); + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); final LocalDate today = DateUtils.getLocalDateOfTenant(); @@ -842,6 +887,71 @@ public class SavingsAccount extends AbstractPersistable { return transaction; } + public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate) { + + if (isNotActive()) { + + final String defaultUserMessage = "Transaction is not allowed. Account is not active."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.is.not.active", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isDateInTheFuture(transactionDate)) { + final String defaultUserMessage = "Transaction date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (transactionDate.isBefore(getActivationLocalDate())) { + final Object[] defaultUserArgs = Arrays.asList(transactionDate.toString(formatter), + getActivationLocalDate().toString(formatter)).toArray(); + final String defaultUserMessage = "Transaction date cannot be before accounts activation date."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.before.activation.date", + defaultUserMessage, "transactionDate", defaultUserArgs); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isAccountLocked(transactionDate)) { + final String defaultUserMessage = "Withdrawal is not allowed. No withdrawals are allowed until after " + + getLockedInUntilLocalDate().toString(formatter); + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.savingsaccount.transaction.withdrawals.blocked.during.lockin.period", defaultUserMessage, "transactionDate", + transactionDate.toString(formatter), getLockedInUntilLocalDate().toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + final Money annualFee = Money.of(this.currency, this.annualFeeAmount); + SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee); + this.transactions.add(annualFeeTransaction); + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + + final LocalDate today = DateUtils.getLocalDateOfTenant(); + calculateInterest(today); + + updateAnnualFeeNextDueDate(); + + return annualFeeTransaction; + } + private boolean isAutomaticWithdrawalFee() { return this.withdrawalFeeType != null; } @@ -942,6 +1052,48 @@ public class SavingsAccount extends AbstractPersistable { this.lockinPeriodFrequencyType = null; } } + + if (command.isChangeInBigDecimalParameterNamed(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName); + actualChanges.put(withdrawalFeeAmountParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + this.withdrawalFeeAmount = newValue; + } + + if (command.isChangeInIntegerParameterNamed(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { + final Integer newValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); + actualChanges.put(withdrawalFeeTypeParamName, newValue); + this.withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(newValue).getValue(); + } + + if (command.isChangeInBigDecimalParameterNamed(annualFeeAmountParamName, this.annualFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + actualChanges.put(annualFeeAmountParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeAmount = newValue; + } + + if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { + final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + final Integer newValue = monthDay.getDayOfMonth(); + actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeOnDay = newValue; + + updateAnnualFeeNextDueDate(); + } + + if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { + final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + final Integer newValue = monthDay.getMonthOfYear(); + actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeOnMonth = newValue; + + updateAnnualFeeNextDueDate(); + } } public void update(final Client client) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index d3550da88..67ea6e851 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; @@ -22,6 +24,7 @@ import java.math.BigDecimal; import java.util.Locale; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; @@ -161,7 +164,7 @@ public class SavingsAccountAssembler { } else { withdrawalFeeAmount = product.withdrawalFeeAmount(); } - + SavingsWithdrawalFeesType withdrawalFeeType = null; if (command.parameterExists(withdrawalFeeAmountParamName)) { final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); @@ -172,10 +175,24 @@ public class SavingsAccountAssembler { withdrawalFeeType = product.withdrawalFeeType(); } + BigDecimal annualFeeAmount = null; + if (command.parameterExists(annualFeeAmountParamName)) { + annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + } else { + annualFeeAmount = product.annualFeeAmount(); + } + + MonthDay monthDayOfAnnualFee = null; + if (command.parameterExists(annualFeeOnMonthDayParamName)) { + monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + } else { + monthDayOfAnnualFee = product.monthDayOfAnnualFee(); + } final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType); + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, + annualFeeAmount, monthDayOfAnnualFee); account.setHelpers(this.savingsAccountTransactionSummaryWrapper); if (active) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index 4bd0d8243..825412b7e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -32,6 +32,9 @@ public final class SavingsAccountSummary { @Column(name = "total_withdrawal_fees_derived", scale = 6, precision = 19) private BigDecimal totalWithdrawalFees; + @Column(name = "total_annual_fees_derived", scale = 6, precision = 19) + private BigDecimal totalAnnualFees; + @Column(name = "account_balance_derived", scale = 6, precision = 19) private BigDecimal accountBalance = BigDecimal.ZERO; @@ -46,9 +49,10 @@ public final class SavingsAccountSummary { this.totalWithdrawals = wrapper.calculateTotalWithdrawals(currency, transactions); this.totalInterestPosted = wrapper.calculateTotalInterestPosted(currency, transactions); this.totalWithdrawalFees = wrapper.calculateTotalWithdrawalFees(currency, transactions); + this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) - .minus(this.totalWithdrawalFees).getAmount(); + .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).getAmount(); } public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index ac4915646..48bc6ccda 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -88,6 +88,12 @@ public final class SavingsAccountTransaction extends AbstractPersistable { isReversed); } + public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final LocalDate date, final Money amount) { + final boolean isReversed = false; + return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, + isReversed); + } + private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed) { this.savingsAccount = savingsAccount; @@ -125,6 +131,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return SavingsAccountTransactionType.fromInt(this.typeOf).isWithdrawalFee() && isNotReversed(); } + public boolean isAnnualFee() { + return SavingsAccountTransactionType.fromInt(this.typeOf).isAnnualFee() && isNotReversed(); + } + public boolean isNotReversed() { return !isReversed(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index a2297fa4e..a96ed3a4f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -53,4 +53,14 @@ public final class SavingsAccountTransactionSummaryWrapper { } return total.getAmountDefaultedToNullIfZero(); } + + public BigDecimal calculateTotalAnnualFees(final MonetaryCurrency currency, final List transactions) { + Money total = Money.zero(currency); + for (SavingsAccountTransaction transaction : transactions) { + if (transaction.isAnnualFee() && transaction.isNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java index 1e89b6664..56ac47929 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java @@ -15,7 +15,8 @@ public enum SavingsAccountTransactionType { DEPOSIT(1, "savingsAccountTransactionType.deposit"), // WITHDRAWAL(2, "savingsAccountTransactionType.withdrawal"), // INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), // - WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"); + WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), // + ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"); private final Integer value; private final String code; @@ -51,6 +52,9 @@ public enum SavingsAccountTransactionType { case 4: loanTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE; break; + case 5: + loanTransactionType = SavingsAccountTransactionType.ANNUAL_FEE; + break; } return loanTransactionType; } @@ -70,4 +74,8 @@ public enum SavingsAccountTransactionType { public boolean isWithdrawalFee() { return this.value.equals(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()); } + + public boolean isAnnualFee() { + return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index 60d3facfa..9ddd678a1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.domain; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; @@ -30,6 +32,7 @@ import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Table; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -55,7 +58,7 @@ public class SavingsProduct extends AbstractPersistable { /** * The interest period is the span of time at the end of which savings in a * client's account earn interest. - * + * * A value from the {@link SavingsCompoundingInterestPeriodType} * enumeration. */ @@ -96,16 +99,26 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "withdrawal_fee_type_enum", nullable = true) private Integer withdrawalFeeType; + @Column(name = "annual_fee_amount", scale = 6, precision = 19, nullable = true) + private BigDecimal annualFeeAmount; + + @Column(name = "annual_fee_on_month", nullable = true) + private Integer annualFeeOnMonth; + + @Column(name = "annual_fee_on_day", nullable = true) + private Integer annualFeeOnDay; + public static SavingsProduct createNew(final String name, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType) { + final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, + final MonthDay annualFeeOnMonthDay) { return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay); } protected SavingsProduct() { @@ -118,7 +131,8 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType) { + final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, + final MonthDay annualFeeOnMonthDay) { this.name = name; this.description = description; @@ -143,9 +157,15 @@ public class SavingsProduct extends AbstractPersistable { if (withdrawalFeeType != null) { this.withdrawalFeeType = withdrawalFeeType.getValue(); } + this.annualFeeAmount = annualFeeAmount; + if (annualFeeOnMonthDay != null) { + this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear(); + this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); + } validateLockinDetails(); validateWithdrawalFeeDetails(); + validateAnnualFeeDetails(); } public MonetaryCurrency currency() { @@ -200,6 +220,18 @@ public class SavingsProduct extends AbstractPersistable { return type; } + public BigDecimal annualFeeAmount() { + return this.annualFeeAmount; + } + + public MonthDay monthDayOfAnnualFee() { + MonthDay monthDay = null; + if (this.annualFeeOnMonth != null && this.annualFeeOnDay != null) { + monthDay = new MonthDay(this.annualFeeOnMonth, this.annualFeeOnDay); + } + return monthDay; + } + public Map update(final JsonCommand command) { final Map actualChanges = new LinkedHashMap(10); @@ -299,12 +331,51 @@ public class SavingsProduct extends AbstractPersistable { this.withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(newValue).getValue(); } + if (command.isChangeInBigDecimalParameterNamed(annualFeeAmountParamName, this.annualFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + actualChanges.put(annualFeeAmountParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeAmount = newValue; + } + + if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { + final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + final Integer newValue = monthDay.getDayOfMonth(); + actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeOnDay = newValue; + } + + if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { + final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); + final Integer newValue = monthDay.getMonthOfYear(); + actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); + actualChanges.put(localeParamName, localeAsInput); + this.annualFeeOnMonth = newValue; + } + validateLockinDetails(); validateWithdrawalFeeDetails(); + validateAnnualFeeDetails(); return actualChanges; } + private void validateAnnualFeeDetails() { + if (isInvalidConfigurationOfAnnualFeeSettings()) { + Object[] defaultUserMessageArgs = new Object[] { annualFeeAmountParamName }; + throw new GeneralPlatformDomainRuleException("error.msg.product.savings.invalid.annualfee.settings", + "Invalid configuration of annual fee settings.", defaultUserMessageArgs); + } + } + + private boolean isInvalidConfigurationOfAnnualFeeSettings() { + return (this.annualFeeAmount == null && (this.annualFeeOnDay != null || this.annualFeeOnMonth != null)) + || ((this.annualFeeOnDay == null || this.annualFeeOnMonth == null) && this.annualFeeAmount != null); + } + private void validateWithdrawalFeeDetails() { if (isInvalidConfigurationOfWithdrawalFeeSettings()) { Object[] defaultUserMessageArgs = new Object[] { withdrawalFeeAmountParamName }; @@ -330,4 +401,4 @@ public class SavingsProduct extends AbstractPersistable { return (this.lockinPeriodFrequency == null && this.lockinPeriodFrequencyType != null) || (this.lockinPeriodFrequencyType == null && this.lockinPeriodFrequency != null); } -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index aa57dcc4c..b2dc9c986 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -1,5 +1,7 @@ package org.mifosplatform.portfolio.savings.domain; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; @@ -17,6 +19,7 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdr import java.math.BigDecimal; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.springframework.stereotype.Component; @@ -76,8 +79,12 @@ public class SavingsProductAssembler { withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue); } + final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + monthDayOfAnnualFee); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 4803fc33f..04cb8a2b2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.Collection; import org.joda.time.LocalDate; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.DateUtils; @@ -114,9 +115,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); sqlBuilder.append("sa.withdrawal_fee_amount as withdrawalFeeAmount,"); sqlBuilder.append("sa.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); + sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,"); + sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth, "); + sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, "); + sqlBuilder.append("sa.annual_fee_next_due_date as annualFeeNextDueDate, "); sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); + sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); sqlBuilder.append("sa.account_balance_derived as accountBalance "); @@ -195,21 +201,34 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); } + final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); + + MonthDay annualFeeOnMonthDay = null; + final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); + final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); + if (annualFeeAmount != null && annualFeeOnDay != null) { + annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay); + } + + final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate"); + final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); + final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees"); final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestPosted"); final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, - totalWithdrawalFees, totalInterestEarned, totalInterestPosted, accountBalance); + totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance); return SavingsAccountData.instance(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, summary); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, + annualFeeNextDueDate, summary); } } @@ -425,7 +444,10 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); - sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum "); + sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); + sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); + sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); + sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); @@ -484,6 +506,15 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); } + final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); + + MonthDay annualFeeOnMonthDay = null; + final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); + final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); + if (annualFeeAmount != null && annualFeeOnDay != null) { + annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay); + } + Long clientId = null; String clientName = null; if (client != null) { @@ -500,11 +531,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final SavingsAccountStatusEnumData status = null; final LocalDate activationDate = null; + final LocalDate annualFeeNextDueDate = null; final SavingsAccountSummaryData summary = null; + return SavingsAccountData.instance(null, null, null, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, summary); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, + annualFeeNextDueDate, summary); } } } \ No newline at end of file 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 a63024a2e..57ee1e872 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 @@ -79,7 +79,10 @@ public class SavingsEnumerations { case WITHDRAWAL_FEE: optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue().longValue(), SavingsAccountTransactionType.WITHDRAWAL_FEE.getCode(), "Withdrawal fee"); - + break; + case ANNUAL_FEE: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.ANNUAL_FEE.getValue().longValue(), + SavingsAccountTransactionType.ANNUAL_FEE.getCode(), "Annual fee"); break; } return optionData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 3a1ef3c48..922630c31 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -10,6 +10,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; @@ -85,7 +86,10 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); sqlBuilder.append("sp.withdrawal_fee_amount as withdrawalFeeAmount,"); - sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum "); + sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); + sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); + sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); + sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); @@ -147,9 +151,19 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); } + final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); + + MonthDay annualFeeOnMonthDay = null; + final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs, "annualFeeOnMonth"); + final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); + if (annualFeeAmount != null && annualFeeOnDay != null) { + annualFeeOnMonthDay = new MonthDay(annualFeeOnMonth, annualFeeOnDay); + } + return SavingsProductData.instance(id, name, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay); } }