diff --git a/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql b/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql index aaf7236e5..54a24d01b 100644 --- a/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql +++ b/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql @@ -723,6 +723,9 @@ CREATE TABLE `m_savings_product` ( `currency_digits` smallint(5) NOT NULL, `nominal_interest_rate_per_period` decimal(19,6) NOT NULL, `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL, + `interest_period_enum` SMALLINT(5) NOT NULL, + `interest_calculation_type_enum` SMALLINT(5) NOT NULL, + `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL, `min_required_opening_balance` decimal(19,6) DEFAULT NULL, `lockin_period_frequency` decimal(19,6) DEFAULT NULL, `lockin_period_frequency_enum` smallint(5) DEFAULT NULL, @@ -730,7 +733,6 @@ CREATE TABLE `m_savings_product` ( UNIQUE KEY `sp_unq_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - CREATE TABLE `m_savings_account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `account_no` varchar(20) NOT NULL, @@ -745,12 +747,16 @@ CREATE TABLE `m_savings_account` ( `nominal_interest_rate_per_period` decimal(19,6) NOT NULL, `nominal_interest_rate_period_frequency_enum` smallint(5) NOT NULL, `annual_nominal_interest_rate` decimal(19,6) NOT NULL, + `interest_period_enum` SMALLINT(5) NOT NULL, + `interest_calculation_type_enum` SMALLINT(5) NOT NULL, + `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL, `min_required_opening_balance` decimal(19,6) DEFAULT NULL, `lockin_period_frequency` decimal(19,6) DEFAULT NULL, `lockin_period_frequency_enum` smallint(5) DEFAULT NULL, `lockedin_until_date_derived` DATE DEFAULT NULL, `total_deposits_derived` decimal(19,6) DEFAULT NULL, `total_withdrawals_derived` decimal(19,6) DEFAULT NULL, + `total_interest_earned_derived` decimal(19,6) DEFAULT NULL, `total_interest_posted_derived` decimal(19,6) DEFAULT NULL, `account_balance_derived` decimal(19,6) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), @@ -771,6 +777,10 @@ CREATE TABLE `m_savings_account_transaction` ( `transaction_date` date NOT NULL, `amount` decimal(19,6) NOT NULL, `is_reversed` tinyint(1) NOT NULL, + `running_balance_derived` DECIMAL(19,6) NULL, + `balance_number_of_days_derived` INT NULL, + `balance_end_date_derived` DATE NULL, + `cumulative_balance_derived` DECIMAL(19,6) NULL, PRIMARY KEY (`id`), KEY `FKSAT0000000001` (`savings_account_id`), CONSTRAINT `FKSAT0000000001` FOREIGN KEY (`savings_account_id`) REFERENCES `m_savings_account` (`id`) diff --git a/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql b/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql index 6836d79a6..971065a42 100644 --- a/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql +++ b/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql @@ -265,7 +265,9 @@ INSERT INTO `m_permission` ('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'), ('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL', '0'), ('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'), -('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'); +('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'), +('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'), +('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0'); -- == accounting related permissions INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES diff --git a/mifosng-db/patches/4-add-interest-calculation-fields.sql b/mifosng-db/patches/4-add-interest-calculation-fields.sql new file mode 100644 index 000000000..0a7c96b91 --- /dev/null +++ b/mifosng-db/patches/4-add-interest-calculation-fields.sql @@ -0,0 +1,31 @@ +ALTER TABLE `m_savings_product` +ADD COLUMN `interest_period_enum` SMALLINT(5) NOT NULL AFTER `nominal_interest_rate_period_frequency_enum`, +ADD COLUMN `interest_calculation_type_enum` SMALLINT(5) NOT NULL AFTER `interest_period_enum`; + + +ALTER TABLE `m_savings_account` +ADD COLUMN `interest_period_enum` SMALLINT(5) NOT NULL AFTER `annual_nominal_interest_rate`, +ADD COLUMN `interest_calculation_type_enum` SMALLINT(5) NOT NULL AFTER `interest_period_enum`; + + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES +('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '1'), +('transaction_savings', 'CALCULATEINTEREST_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CALCULATEINTEREST', '0'); + + +ALTER TABLE `m_savings_account_transaction` +ADD COLUMN `running_balance_derived` DECIMAL(19,6) NULL AFTER `is_reversed`, +ADD COLUMN `balance_number_of_days_derived` INT NULL AFTER `running_balance_derived`, +ADD COLUMN `balance_end_date_derived` DATE NULL AFTER `balance_number_of_days_derived`, +ADD COLUMN `cumulative_balance_derived` DECIMAL(19,6) NULL AFTER `balance_end_date_derived`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `total_interest_earned_derived` DECIMAL(19,6) DEFAULT NULL AFTER `total_withdrawals_derived`; + + +ALTER TABLE `m_savings_product` +ADD COLUMN `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL AFTER `interest_calculation_type_enum`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `interest_calculation_days_in_year_type_enum` SMALLINT(5) NOT NULL AFTER `interest_calculation_type_enum`; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index ab83e5f46..658ee7503 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -446,6 +446,10 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("WITHDRAWAL") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } + public boolean isSavingsAccountInterestCalculation() { + return this.actionName.equalsIgnoreCase("CALCULATEINTEREST") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + public boolean isCalendarResource() { return this.entityName.equalsIgnoreCase("CALENDAR"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 0dd569546..cf2b81ad2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -713,6 +713,15 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) { + this.actionName = "CALCULATEINTEREST"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=calculateInterest"; + return this; + } + public CommandWrapperBuilder createCalendar(final String supportedEntityType, final Long supportedEntityId) { this.actionName = "CREATE"; this.entityName = "CALENDAR"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index 4a18e74cc..d0b4236e6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -368,6 +368,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = applicationContext.getBean("withdrawSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isSavingsAccountActivation()) { handler = applicationContext.getBean("activateSavingsAccountCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isSavingsAccountInterestCalculation()) { + handler = applicationContext.getBean("calculateInterestSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 1b0355cff..3f50991bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -7,6 +7,7 @@ package org.mifosplatform.infrastructure.core.data; import java.math.BigDecimal; import java.text.ParseException; +import java.util.Arrays; import java.util.List; import net.fortuna.ical4j.model.ValidationException; @@ -191,6 +192,27 @@ public class DataValidatorBuilder { return this; } + public DataValidatorBuilder inOfTheseValues(final Object... values) { + if (value == null && ignoreNullValue) { return this; } + + if (value != null) { + List valuesList = Arrays.asList(values); + + if (!valuesList.contains(this.value)) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".is.not.one.of.expected.enumerations"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be one of [ ") + .append(values).append(" ] ").append("."); + + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, this.value, values); + + dataValidationErrors.add(error); + } + } + return this; + } + public DataValidatorBuilder positiveAmount() { if (value == null && ignoreNullValue) { return this; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java index c6b225eeb..4d8474d16 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java @@ -141,6 +141,9 @@ public class SavingsAccountsApiResource { Collection transactions = null; Collection productOptions = null; Collection interestRatePeriodFrequencyTypeOptions = null; + Collection interestPeriodTypeOptions = null; + Collection interestCalculationTypeOptions = null; + Collection interestCalculationDaysInYearTypeOptions = null; Collection lockinPeriodFrequencyTypeOptions = null; final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); @@ -164,10 +167,13 @@ public class SavingsAccountsApiResource { if (settings.isTemplate()) { productOptions = this.savingsProductReadPlatformService.retrieveAllForLookup(); interestRatePeriodFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRatePeriodFrequencyTypeOptions(); + interestPeriodTypeOptions = this.dropdownReadPlatformService.retrieveInterestPeriodTypeOptions(); + interestCalculationTypeOptions = this.dropdownReadPlatformService.retrieveInterestCalculationTypeOptions(); lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLockinPeriodFrequencyTypeOptions(); } return SavingsAccountData.withTemplateOptions(savingsAccount, productOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } @@ -198,6 +204,9 @@ public class SavingsAccountsApiResource { if (is(commandParam, "activate")) { final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculateInterest")) { + final CommandWrapper commandRequest = builder.savingsAccountInterestCalculation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { 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 92fa39d42..5eeae9afe 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 @@ -33,6 +33,9 @@ public class SavingsApiConstants { public static final String digitsAfterDecimalParamName = "digitsAfterDecimal"; public static final String interestRateParamName = "interestRate"; public static final String interestRatePeriodFrequencyTypeParamName = "interestRatePeriodFrequencyType"; + public static final String interestPeriodTypeParamName = "interestPeriodType"; + public static final String interestCalculationTypeParamName = "interestCalculationType"; + public static final String interestCalculationDaysInYearTypeParamName = "interestCalculationDaysInYearType"; public static final String minRequiredOpeningBalanceParamName = "minRequiredOpeningBalance"; public static final String lockinPeriodFrequencyParamName = "lockinPeriodFrequency"; public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType"; @@ -43,7 +46,8 @@ public class SavingsApiConstants { public static final Set SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, nameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, interestRateParamName, - interestRatePeriodFrequencyTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, + interestRatePeriodFrequencyTypeParamName, interestPeriodTypeParamName, interestCalculationTypeParamName, + interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName)); /** @@ -53,13 +57,15 @@ public class SavingsApiConstants { */ public static final Set SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, nameParamName, descriptionParamName, "currency", digitsAfterDecimalParamName, interestRateParamName, - interestRatePeriodFrequencyTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, + interestRatePeriodFrequencyTypeParamName, interestPeriodTypeParamName, interestCalculationTypeParamName, + interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "currencyOptions", "interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions")); public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, activeParamName, activationDateParamName, interestRateParamName, interestRatePeriodFrequencyTypeParamName, + interestPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName)); /** @@ -70,9 +76,10 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, accountNoParamName, externalIdParamName, statusParamName, activationDateParamName, clientIdParamName, "clientName", groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", interestRateParamName, - interestRatePeriodFrequencyTypeParamName, "annualInterestRate", minRequiredOpeningBalanceParamName, - lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "summary", "transactions", "productOptions", - "interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions")); + interestRatePeriodFrequencyTypeParamName, "annualInterestRate", interestPeriodTypeParamName, interestCalculationTypeParamName, + interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, + lockinPeriodFrequencyTypeParamName, "summary", "transactions", "productOptions", "interestRatePeriodFrequencyTypeOptions", + "lockinPeriodFrequencyTypeOptions")); 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/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java index 9d1e8ed62..7ea8b326c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java @@ -30,8 +30,11 @@ import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSeria import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.data.SavingsProductData; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.portfolio.savings.service.SavingsProductReadPlatformService; @@ -144,21 +147,41 @@ public class SavingsProductsApiResource { private SavingsProductData handleTemplateRelatedData(final SavingsProductData savingsProduct) { final EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations - .interestRatePeriodFrequencyType(PeriodFrequencyType.YEARS); + .interestRatePeriodFrequencyType(SavingsPeriodFrequencyType.YEARS); + + final EnumOptionData interestPeriodType = SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.MONTHLY); + + final EnumOptionData interestCalculationType = SavingsEnumerations + .interestCalculationType(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE); + + final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations + .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365); final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); final Collection interestRatePeriodFrequencyTypeOptions = this.dropdownReadPlatformService .retrieveInterestRatePeriodFrequencyTypeOptions(); + + final Collection interestPeriodTypeOptions = this.dropdownReadPlatformService.retrieveInterestPeriodTypeOptions(); + + final Collection interestCalculationTypeOptions = this.dropdownReadPlatformService + .retrieveInterestCalculationTypeOptions(); + + final Collection interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService + .retrieveInterestCalculationDaysInYearTypeOptions(); + final Collection lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService .retrieveLockinPeriodFrequencyTypeOptions(); SavingsProductData savingsProductToReturn = null; if (savingsProduct != null) { savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions); } else { - savingsProductToReturn = SavingsProductData.template(CurrencyData.blank(), interestRatePeriodFrequencyType, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + savingsProductToReturn = SavingsProductData.template(CurrencyData.blank(), interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, currencyOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions); } return savingsProductToReturn; 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 97474ec20..7e641408b 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 @@ -33,6 +33,9 @@ public class SavingsAccountData { private final BigDecimal interestRate; private final EnumOptionData interestRatePeriodFrequencyType; private final BigDecimal annualInterestRate; + private final EnumOptionData interestPeriodType; + private final EnumOptionData interestCalculationType; + private final EnumOptionData interestCalculationDaysInYearType; private final BigDecimal minRequiredOpeningBalance; private final Integer lockinPeriodFrequency; private final EnumOptionData lockinPeriodFrequencyType; @@ -44,35 +47,47 @@ public class SavingsAccountData { // template private final Collection productOptions; private final Collection interestRatePeriodFrequencyTypeOptions; + private final Collection interestPeriodTypeOptions; + private final Collection interestCalculationTypeOptions; + private final Collection interestCalculationDaysInYearTypeOptions; private final Collection lockinPeriodFrequencyTypeOptions; public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final SavingsAccountStatusEnumData status, final LocalDate activationDate, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final CurrencyData currency, final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary) { + final EnumOptionData interestPeriodType, final EnumOptionData interestCalculationType, + final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary) { final Collection productOptions = null; final Collection interestRatePeriodFrequencyTypeOptions = null; + final Collection interestPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection transactions = null; return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final Collection productOptions, final Collection interestRatePeriodFrequencyTypeOptions, + final Collection interestPeriodTypeOptions, final Collection interestCalculationTypeOptions, + final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection transactions) { return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.status, account.activationDate, account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.currency, account.interestRate, account.interestRatePeriodFrequencyType, - account.annualInterestRate, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, + account.annualInterestRate, account.interestPeriodType, account.interestCalculationType, + account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.summary, productOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } @@ -90,6 +105,9 @@ public class SavingsAccountData { final BigDecimal interestRate = null; final EnumOptionData interestRatePeriodFrequencyType = null; final BigDecimal annualInterestRate = null; + final EnumOptionData interestPeriodType = null; + final EnumOptionData interestCalculationType = null; + final EnumOptionData interestCalculationDaysInYearType = null; final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; @@ -97,22 +115,29 @@ public class SavingsAccountData { final Collection productOptions = null; final Collection currencyOptions = null; final Collection interestRatePeriodFrequencyTypeOptions = null; + final Collection interestPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection transactions = null; return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } private SavingsAccountData(final Long id, final String accountNo, final String externalId, final SavingsAccountStatusEnumData status, final LocalDate activationDate, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final CurrencyData currency, final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary, + final EnumOptionData interestPeriodType, final EnumOptionData interestCalculationType, + final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary, final Collection productOptions, final Collection interestRatePeriodFrequencyTypeOptions, + final Collection interestPeriodTypeOptions, final Collection interestCalculationTypeOptions, + final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection transactions) { this.id = id; this.accountNo = accountNo; @@ -129,6 +154,9 @@ public class SavingsAccountData { this.interestRate = interestRate; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; this.annualInterestRate = annualInterestRate; + this.interestPeriodType = interestPeriodType; + this.interestCalculationType = interestCalculationType; + this.interestCalculationDaysInYearType = interestCalculationDaysInYearType; this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; @@ -136,6 +164,9 @@ public class SavingsAccountData { this.productOptions = productOptions; this.interestRatePeriodFrequencyTypeOptions = interestRatePeriodFrequencyTypeOptions; + this.interestPeriodTypeOptions = interestPeriodTypeOptions; + this.interestCalculationTypeOptions = interestCalculationTypeOptions; + this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions; this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; 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 6312cd5ba..803b27dff 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 @@ -6,6 +6,9 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.active 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; +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.interestPeriodTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; @@ -103,6 +106,17 @@ public class SavingsAccountDataValidator { .inMinMaxRange(1, 3); } + final Integer interestPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPeriodTypeParamName, element); + baseDataValidator.reset().parameter(interestPeriodTypeParamName).value(interestPeriodType).notNull().inMinMaxRange(1, 7); + + final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull().inMinMaxRange(1, 2); + + final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed( + interestCalculationDaysInYearTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull() + .inOfTheseValues(360, 365); + if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); @@ -213,6 +227,25 @@ public class SavingsAccountDataValidator { .inMinMaxRange(1, 3); } + if (this.fromApiJsonHelper.parameterExists(interestPeriodTypeParamName, element)) { + final Integer interestPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPeriodTypeParamName, element); + baseDataValidator.reset().parameter(interestPeriodTypeParamName).value(interestPeriodType).notNull().inMinMaxRange(1, 7); + } + + if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) { + final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, + element); + baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull() + .inMinMaxRange(1, 2); + } + + if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) { + final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed( + interestCalculationDaysInYearTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType) + .notNull().inOfTheseValues(360, 365); + } + if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); 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 eb4bc93d5..39756f590 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 @@ -18,14 +18,16 @@ public class SavingsAccountSummaryData { private final CurrencyData currency; private final BigDecimal totalDeposits; private final BigDecimal totalWithdrawals; + 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 totalInterestPosted, final BigDecimal accountBalance) { + final BigDecimal totalInterestEarned, final BigDecimal totalInterestPosted, final BigDecimal accountBalance) { this.currency = currency; this.totalDeposits = totalDeposits; this.totalWithdrawals = totalWithdrawals; + 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 0a62f7765..a4e410d77 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 @@ -14,7 +14,6 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; /** * Immutable data object represent a savings product. */ -@SuppressWarnings("unused") public class SavingsProductData { private final Long id; @@ -23,31 +22,47 @@ public class SavingsProductData { private final CurrencyData currency; private final BigDecimal interestRate; private final EnumOptionData interestRatePeriodFrequencyType; + private final EnumOptionData interestPeriodType; + private final EnumOptionData interestCalculationType; + private final EnumOptionData interestCalculationDaysInYearType; private final BigDecimal minRequiredOpeningBalance; private final Integer lockinPeriodFrequency; private final EnumOptionData lockinPeriodFrequencyType; // template + @SuppressWarnings("unused") private final Collection currencyOptions; + @SuppressWarnings("unused") private final Collection interestRatePeriodFrequencyTypeOptions; + @SuppressWarnings("unused") + private final Collection interestPeriodTypeOptions; + @SuppressWarnings("unused") + private final Collection interestCalculationTypeOptions; + @SuppressWarnings("unused") + private final Collection interestCalculationDaysInYearTypeOptions; + @SuppressWarnings("unused") private final Collection lockinPeriodFrequencyTypeOptions; public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestRatePeriodFrequencyType, - final Collection currencyOptions, final Collection interestRatePeriodFrequencyTypeOptions, + final EnumOptionData interestPeriodType, final EnumOptionData interestCalculationType, + final EnumOptionData interestCalculationDaysInYearType, final Collection currencyOptions, + final Collection interestRatePeriodFrequencyTypeOptions, + final Collection interestPeriodTypeOptions, final Collection interestCalculationTypeOptions, + final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions) { final Long id = null; final String name = null; final String description = null; - final Integer digitsAfterDecimal = null; final BigDecimal interestRate = null; final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; - return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, currencyOptions, interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions); } /** @@ -56,25 +71,35 @@ public class SavingsProductData { */ public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Collection currencyOptions, final Collection interestRatePeriodFrequencyTypeOptions, + final Collection interestPeriodTypeOptions, final Collection interestCalculationTypeOptions, + final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions) { return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.description, existingProduct.currency, - existingProduct.interestRate, existingProduct.interestRatePeriodFrequencyType, existingProduct.minRequiredOpeningBalance, - existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + existingProduct.interestRate, existingProduct.interestRatePeriodFrequencyType, existingProduct.interestPeriodType, + existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, + existingProduct.minRequiredOpeningBalance, existingProduct.lockinPeriodFrequency, + existingProduct.lockinPeriodFrequencyType, currencyOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, + lockinPeriodFrequencyTypeOptions); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, - final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, + final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final EnumOptionData interestPeriodType, + final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType) { final Collection currencyOptions = null; final Collection interestRatePeriodFrequencyTypeOptions = null; + final Collection interestPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; - return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, currencyOptions, interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions); } public static SavingsProductData lookup(final Long id, final String name) { @@ -83,24 +108,34 @@ public class SavingsProductData { final String description = null; final BigDecimal interestRate = null; final EnumOptionData interestRatePeriodFrequencyType = null; + final EnumOptionData interestPeriodType = null; + final EnumOptionData interestCalculationType = null; + final EnumOptionData interestCalculationDaysInYearType = null; final BigDecimal minRequiredOpeningBalance = null; final Integer lockinPeriodFrequency = null; final EnumOptionData lockinPeriodFrequencyType = null; final Collection currencyOptions = null; final Collection interestRatePeriodFrequencyTypeOptions = null; + final Collection interestPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; - return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, currencyOptions, - interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions); + return new SavingsProductData(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, currencyOptions, interestRatePeriodFrequencyTypeOptions, interestPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, - final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, + final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final EnumOptionData interestPeriodType, + final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final Collection currencyOptions, final Collection interestRatePeriodFrequencyTypeOptions, + final Collection interestPeriodTypeOptions, final Collection interestCalculationTypeOptions, + final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions) { this.id = id; this.name = name; @@ -108,12 +143,18 @@ public class SavingsProductData { this.currency = currency; this.interestRate = interestRate; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; + this.interestPeriodType = interestPeriodType; + this.interestCalculationType = interestCalculationType; + this.interestCalculationDaysInYearType = interestCalculationDaysInYearType; this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; this.currencyOptions = currencyOptions; this.interestRatePeriodFrequencyTypeOptions = interestRatePeriodFrequencyTypeOptions; + this.interestPeriodTypeOptions = interestPeriodTypeOptions; + this.interestCalculationTypeOptions = interestCalculationTypeOptions; + this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions; this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; } } \ No newline at end of file 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 073a1c6c7..c9876d1a2 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 @@ -1,10 +1,13 @@ package org.mifosplatform.portfolio.savings.data; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; 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.currencyCodeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; +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.interestPeriodTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; @@ -48,7 +51,8 @@ public class SavingsProductDataValidator { fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS); final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_PRODUCT_RESOURCE_NAME); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); final JsonElement element = fromApiJsonHelper.parse(json); final String name = fromApiJsonHelper.extractStringNamed(nameParamName, element); @@ -71,6 +75,17 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(interestRatePeriodFrequencyTypeParamName).value(interestRatePeriodFrequencyType).notNull() .inMinMaxRange(1, 3); + final Integer interestPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPeriodTypeParamName, element); + baseDataValidator.reset().parameter(interestPeriodTypeParamName).value(interestPeriodType).notNull().inMinMaxRange(1, 7); + + final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull().inMinMaxRange(1, 2); + + final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed( + interestCalculationDaysInYearTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull() + .inOfTheseValues(360, 365); + if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); @@ -114,7 +129,8 @@ public class SavingsProductDataValidator { fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS); final List dataValidationErrors = new ArrayList(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_PRODUCT_RESOURCE_NAME); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); final JsonElement element = fromApiJsonHelper.parse(json); if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) { @@ -149,6 +165,25 @@ public class SavingsProductDataValidator { .inMinMaxRange(1, 3); } + if (this.fromApiJsonHelper.parameterExists(interestPeriodTypeParamName, element)) { + final Integer interestPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPeriodTypeParamName, element); + baseDataValidator.reset().parameter(interestPeriodTypeParamName).value(interestPeriodType).notNull().inMinMaxRange(1, 7); + } + + if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) { + final Integer interestCalculationType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, + element); + baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull() + .inMinMaxRange(1, 2); + } + + if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) { + final Integer interestCalculationDaysInYearType = fromApiJsonHelper.extractIntegerSansLocaleNamed( + interestCalculationDaysInYearTypeParamName, element); + baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType) + .notNull().inOfTheseValues(360, 365); + } + if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodInterval.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodInterval.java new file mode 100644 index 000000000..e0a9d4012 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodInterval.java @@ -0,0 +1,43 @@ +package org.mifosplatform.portfolio.savings.domain; + +import org.joda.time.Days; +import org.joda.time.LocalDate; + +public class InterestPeriodInterval { + + private final LocalDate startDate; + private final LocalDate endDate; + + public static InterestPeriodInterval create(final LocalDate startDate, final LocalDate endDate) { + return new InterestPeriodInterval(startDate, endDate); + } + + public InterestPeriodInterval(final LocalDate startDate, final LocalDate endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public LocalDate startDate() { + return this.startDate; + } + + public LocalDate endDate() { + return this.endDate; + } + + public Integer daysInPeriodInclusiveOfEndDate() { + return daysBetween() + 1; + } + + private Integer daysBetween() { + return Days.daysBetween(this.startDate, this.endDate).getDays(); + } + + public boolean contains(final LocalDate target) { + return isBetweenInclusive(this.startDate, this.endDate, target); + } + + private boolean isBetweenInclusive(final LocalDate start, final LocalDate end, final LocalDate target) { + return !target.isBefore(start) && !target.isAfter(end); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodSummary.java new file mode 100644 index 000000000..a53f5b8f6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestPeriodSummary.java @@ -0,0 +1,61 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; + +public class InterestPeriodSummary { + + @SuppressWarnings("unused") + private final Date startDate; + @SuppressWarnings("unused") + private final Date endDate; + @SuppressWarnings("unused") + private final Integer numberOfDays; + @SuppressWarnings("unused") + private final BigDecimal startingBalance; + private final BigDecimal closingBalance; + private final BigDecimal interestEarnedUnrounded; + private final BigDecimal interestEarned; + @SuppressWarnings("unused") + private final List dailyBalances; + + public static InterestPeriodSummary from(final InterestPeriodInterval interestPeriodInterval, final Money startingBalance, + final List dailyBalances, final BigDecimal totalInterestEarnedUnrounded) { + + return new InterestPeriodSummary(interestPeriodInterval, startingBalance, dailyBalances, totalInterestEarnedUnrounded); + } + + private InterestPeriodSummary(final InterestPeriodInterval interestPeriodInterval, final Money startingBalance, + final List dailyBalances, final BigDecimal totalInterestEarnedUnrounded) { + this.startDate = interestPeriodInterval.startDate().toDate(); + this.endDate = interestPeriodInterval.endDate().toDate(); + this.numberOfDays = interestPeriodInterval.daysInPeriodInclusiveOfEndDate(); + this.startingBalance = startingBalance.getAmount(); + + final MonetaryCurrency currency = startingBalance.getCurrency(); + + this.interestEarnedUnrounded = totalInterestEarnedUnrounded; + this.interestEarned = Money.of(currency, this.interestEarnedUnrounded).getAmount(); + + Money closingBalanceForPeriod = startingBalance.copy(); + + for (SavingsAccountDailyBalance balance : dailyBalances) { + closingBalanceForPeriod = balance.dailyRunningBalance(currency); + } + + this.closingBalance = closingBalanceForPeriod.getAmount(); + this.dailyBalances = dailyBalances; + } + + public Money closingBalance(final MonetaryCurrency currency) { + return Money.of(currency, this.closingBalance); + } + + public BigDecimal interest() { + return this.interestEarned; + } +} \ No newline at end of file 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 e3c721811..2929ee3d7 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 @@ -6,6 +6,8 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -29,6 +31,7 @@ import javax.persistence.UniqueConstraint; import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; +import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; @@ -84,7 +87,28 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "nominal_interest_rate_period_frequency_enum", nullable = false) private Integer interestRatePeriodFrequencyType; - @SuppressWarnings("unused") + /** + * 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 SavingsInterestPeriodType} enumeration. + */ + @Column(name = "interest_period_enum", nullable = false) + private Integer interestPeriodType; + + /** + * A value from the {@link SavingsInterestCalculationType} enumeration. + */ + @Column(name = "interest_calculation_type_enum", nullable = false) + private Integer interestCalculationType; + + /** + * A value from the {@link SavingsInterestCalculationDaysInYearType} + * enumeration. + */ + @Column(name = "interest_calculation_days_in_year_type_enum", nullable = false) + private Integer interestCalculationDaysInYearType; + @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = false) private BigDecimal annualInterestRate; @@ -124,26 +148,25 @@ public class SavingsAccount extends AbstractPersistable { } public static SavingsAccount createNewAccount(final Client client, final Group group, final SavingsProduct product, - final String accountNo, final String externalId, final boolean active, final LocalDate activationDate, - final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final PeriodFrequencyType lockinPeriodFrequencyType) { - - SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED; - if (active) { - status = SavingsAccountStatusType.ACTIVE; - } + final String accountNo, final String externalId, final BigDecimal interestRate, + final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, + final SavingsInterestPeriodType interestPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { + final SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED; + final LocalDate activationDate = null; return new SavingsAccount(client, group, product, accountNo, externalId, status, activationDate, interestRate, - interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType); + interestRatePeriodFrequencyType, annualInterestRate, interestPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final String accountNo, final String externalId, final SavingsAccountStatusType status, final LocalDate activationDate, final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final PeriodFrequencyType lockinPeriodFrequencyType) { + final SavingsInterestPeriodType interestPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { this.client = client; this.group = group; this.product = product; @@ -162,6 +185,9 @@ public class SavingsAccount extends AbstractPersistable { this.interestRate = interestRate; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType.getValue(); this.annualInterestRate = annualInterestRate; + this.interestPeriodType = interestPeriodType.getValue(); + this.interestCalculationType = interestCalculationType.getValue(); + this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue(); this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; if (lockinPeriodFrequencyType != null) { @@ -249,6 +275,224 @@ public class SavingsAccount extends AbstractPersistable { return lockedInUntilLocalDate; } + /** + * All interest calculation based on END-OF-DAY-BALANCE. + * + * Interest calculation is performed on-the-fly over all account + * transactions. + */ + /* + * The daily balance method is the application of a daily interest rate to + * the full amount of principal in the account each day. For the days the + * account is overdrawn, a zero balance is used to calculate the interest + * for those days. + */ + public void calculateInterest() { + + // no openingBalance concept supported yet but probably will to all for + // migrations. + final Money openingAccountBalance = Money.zero(this.currency); + + // 1. update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(openingAccountBalance); + + // 2. determine interest periods + final SavingsInterestPeriodType interestPeriodType = SavingsInterestPeriodType.fromInt(this.interestPeriodType); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + final List interestPeriods = determineInterestPeriods(getActivationLocalDate(), interestPeriodType); + + final List interestPeriodSummaries = deriveInterestPeriodSummaries(openingAccountBalance, interestPeriods, + this.annualInterestRate, daysInYearType); + + this.summary.updateFromInterestPeriodSummaries(currency, interestPeriodSummaries); + } + + private List determineInterestPeriods(final LocalDate activationLocalDate, + final SavingsInterestPeriodType interestPeriodType) { + + LocalDate today = DateUtils.getLocalDateOfTenant(); + LocalDate periodStartDate = activationLocalDate; + LocalDate periodEndDate = periodStartDate; + + final List interestPeriods = new ArrayList(); + + while (!periodStartDate.isAfter(today) && !periodEndDate.isAfter(today)) { + + periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType); + interestPeriods.add(InterestPeriodInterval.create(periodStartDate, periodEndDate)); + + // move periodStartDate forward to day after this period + periodStartDate = periodEndDate.plusDays(1); + } + + return interestPeriods; + } + + private LocalDate determineInterestPeriodEndDateFrom(final LocalDate periodStartDate, final SavingsInterestPeriodType interestPeriodType) { + + LocalDate periodEndDate = DateUtils.getLocalDateOfTenant(); + + switch (interestPeriodType) { + case INVALID: + break; + case DAILY: + periodEndDate = periodStartDate; + break; + case WEEKLY: + periodEndDate = periodStartDate.dayOfWeek().withMaximumValue(); + break; + case BIWEEKLY: + final LocalDate closestEndOfWeek = periodStartDate.dayOfWeek().withMaximumValue(); + periodEndDate = closestEndOfWeek.plusWeeks(1); + break; + case MONTHLY: + // produce period end date on last day of current month + periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + break; + case QUATERLY: + // jan 1st to mar 31st, 1st apr to jun 30, jul 1st to sept 30, + // oct 1st to dec 31 + int year = periodStartDate.getYearOfEra(); + int monthofYear = periodStartDate.getMonthOfYear(); + if (monthofYear <= 3) { + periodEndDate = new DateTime().withDate(year, 3, 31).toLocalDate(); + } else if (monthofYear <= 6) { + periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); + } else if (monthofYear <= 9) { + periodEndDate = new DateTime().withDate(year, 9, 30).toLocalDate(); + } else if (monthofYear <= 12) { + periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); + } + break; + case SEMIANNUAL: + // jan 1st to 30, jul 1st to dec 31 + year = periodStartDate.getYearOfEra(); + monthofYear = periodStartDate.getMonthOfYear(); + if (monthofYear <= 6) { + periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); + } else if (monthofYear <= 12) { + periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); + } + break; + case ANNUAL: + periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + break; + } + + return periodEndDate; + } + + private BigDecimal determinePeriodsInOneYear(final SavingsInterestCalculationDaysInYearType daysInYearType) { + + BigDecimal periodsInOneYear = BigDecimal.ZERO; + + switch (daysInYearType) { + case DAYS_360: + periodsInOneYear = BigDecimal.valueOf(360l); + break; + case DAYS_365: + periodsInOneYear = BigDecimal.valueOf(365l); + break; + case INVALID: + break; + } + + return periodsInOneYear; + } + + private List deriveInterestPeriodSummaries(final Money openingAccountBalance, + final List interestPeriods, final BigDecimal annualInterestRate, + final SavingsInterestCalculationDaysInYearType daysInYearType) { + + final List summaries = new ArrayList(); + + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final BigDecimal percentageDivisor = BigDecimal.valueOf(100l); + final BigDecimal periodsInOneYear = determinePeriodsInOneYear(daysInYearType); + final BigDecimal periodsInOneYearAsFraction = BigDecimal.ONE.divide(periodsInOneYear, mc); + final BigDecimal annualInterestRateAsFraction = annualInterestRate.divide(percentageDivisor, mc); + + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + + final SavingsInterestCalculatorFactory interestCalculatorFactory = new SavingsInterestCalculatorFactory(); + final SavingsInterestCalculator interestCalculator = interestCalculatorFactory.createFrom(interestCalculationType, + periodsInOneYearAsFraction, annualInterestRateAsFraction); + + Money periodOpeningBalance = openingAccountBalance.copy(); + for (InterestPeriodInterval interestPeriodInterval : interestPeriods) { + + InterestPeriodSummary interestPeriodSummary = calculateInterestPeriodSummaryFor(interestPeriodInterval, periodOpeningBalance, + interestCalculator); + + periodOpeningBalance = interestPeriodSummary.closingBalance(this.currency); + + summaries.add(interestPeriodSummary); + } + + return summaries; + } + + private InterestPeriodSummary calculateInterestPeriodSummaryFor(final InterestPeriodInterval interestPeriodInterval, + final Money startingBalanceForPeriod, final SavingsInterestCalculator interestCalculator) { + + List dailyBalances = new ArrayList(); + for (SavingsAccountTransaction transaction : this.transactions) { + + if (transaction.isAcceptableForDailyBalance(interestPeriodInterval)) { + SavingsAccountDailyBalance dailyBalance = transaction.toDailyBalance(interestPeriodInterval.endDate()); + dailyBalances.add(dailyBalance); + } + } + + if (dailyBalances.isEmpty()) { + SavingsAccountDailyBalance dailyBalance = SavingsAccountDailyBalance.createFrom(interestPeriodInterval, + startingBalanceForPeriod.getAmount()); + dailyBalances.add(dailyBalance); + } + + final BigDecimal totalInterestEarnedUnrounded = interestCalculator.calculate(dailyBalances, + interestPeriodInterval.daysInPeriodInclusiveOfEndDate()); + + return InterestPeriodSummary.from(interestPeriodInterval, startingBalanceForPeriod, dailyBalances, totalInterestEarnedUnrounded); + } + + private void recalculateDailyBalances(final Money openingAccountBalance) { + + Money runningBalance = openingAccountBalance.copy(); + for (SavingsAccountTransaction transaction : this.transactions) { + + if (transaction.isReversed()) { + transaction.zeroBalanceFields(); + } else { + Money transactionAmount = Money.zero(this.currency); + if (transaction.isDeposit()) { + transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); + } else if (transaction.isWithdrawal()) { + transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency)); + } else if (transaction.isInterestPosting()) { + transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); + } + + runningBalance = runningBalance.plus(transactionAmount); + transaction.updateRunningBalance(runningBalance); + } + } + + // loop over transactions in reverse + LocalDate endOfBalanceDate = DateUtils.getLocalDateOfTenant(); + for (int i = this.transactions.size() - 1; i >= 0; i--) { + SavingsAccountTransaction transaction = this.transactions.get(i); + transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); + + // this transactions transaction date is end of balance date for + // previous transaction. + endOfBalanceDate = transaction.localDate().minusDays(1); + } + } + public SavingsAccountTransaction deposit(final DateTimeFormatter formatter, final LocalDate transactionDate, final BigDecimal transactionAmount) { @@ -293,6 +537,8 @@ public class SavingsAccount extends AbstractPersistable { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + calculateInterest(); + return transaction; } @@ -368,6 +614,8 @@ public class SavingsAccount extends AbstractPersistable { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + calculateInterest(); + return transaction; } 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 784a3cd17..59afef0af 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,9 @@ 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.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPeriodTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; @@ -112,6 +115,25 @@ public class SavingsAccountAssembler { final BigDecimal annualInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRate); + SavingsInterestPeriodType interestPeriodType = null; + final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestPeriodTypeParamName); + if (interestPeriodTypeValue != null) { + interestPeriodType = SavingsInterestPeriodType.fromInt(interestPeriodTypeValue); + } + + SavingsInterestCalculationType interestCalculationType = null; + final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName); + if (interestCalculationTypeValue != null) { + interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue); + } + + SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null; + final Integer interestCalculationDaysInYearTypeValue = command + .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName); + if (interestCalculationDaysInYearTypeValue != null) { + interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue); + } + BigDecimal minRequiredOpeningBalance = null; if (command.parameterExists(minRequiredOpeningBalanceParamName)) { minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); @@ -137,12 +159,12 @@ public class SavingsAccountAssembler { lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); } - final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, active, - activationDate, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType); + final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, interestRate, + interestRatePeriodFrequencyType, annualInterestRate, interestPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); account.setHelpers(this.savingsAccountTransactionSummaryWrapper); - if (account.isActive()) { + if (active) { final Locale locale = command.extractLocale(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); account.activate(formatter, activationDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java new file mode 100644 index 000000000..6f0e676f7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java @@ -0,0 +1,49 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; + +public class SavingsAccountDailyBalance { + + private final InterestPeriodInterval interestPeriodInterval; + private final BigDecimal runningBalance; + private final BigDecimal cumulativeBalance; + + public static SavingsAccountDailyBalance createFrom(final InterestPeriodInterval interestPeriodInterval, final BigDecimal runningBalance) { + return new SavingsAccountDailyBalance(interestPeriodInterval, runningBalance); + } + + private SavingsAccountDailyBalance(final InterestPeriodInterval interestPeriodInterval, final BigDecimal runningBalance) { + this.interestPeriodInterval = interestPeriodInterval; + this.runningBalance = runningBalance; + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final BigDecimal numberOfDaysBigDecimal = BigDecimal.valueOf(interestPeriodInterval.daysInPeriodInclusiveOfEndDate()); + this.cumulativeBalance = runningBalance.multiply(numberOfDaysBigDecimal, mc); + } + + public Money dailyRunningBalance(final MonetaryCurrency currency) { + return Money.of(currency, runningBalance); + } + + public BigDecimal cumulativeBalance() { + return this.cumulativeBalance; + } + + public Integer numberOfDays() { + return interestPeriodInterval.daysInPeriodInclusiveOfEndDate(); + } + + public boolean isGreaterThanZero() { + return this.runningBalance.compareTo(BigDecimal.ZERO) > 0; + } + + public BigDecimal interestUsingDailyBalanceMethod(final BigDecimal dailyInterestRate, final Integer numberOfDays) { + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final BigDecimal numberOfDaysBigDecimal = BigDecimal.valueOf(numberOfDays.longValue()); + return this.runningBalance.multiply(dailyInterestRate, mc).multiply(numberOfDaysBigDecimal, mc); + } +} \ No newline at end of file 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 9ba842b05..c745e6659 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 @@ -22,6 +22,10 @@ public final class SavingsAccountSummary { @Column(name = "total_withdrawals_derived", scale = 6, precision = 19) private BigDecimal totalWithdrawals; + @SuppressWarnings("unused") + @Column(name = "total_interest_earned_derived", scale = 6, precision = 19) + private BigDecimal totalInterestEarned; + @Column(name = "total_interest_posted_derived", scale = 6, precision = 19) private BigDecimal totalInterestPosted; @@ -43,6 +47,15 @@ public final class SavingsAccountSummary { .getAmount(); } + public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List interestPeriodSummaries) { + Money totalInterestEarned = Money.zero(currency); + for (InterestPeriodSummary interestPeriodSummary : interestPeriodSummaries) { + totalInterestEarned = totalInterestEarned.plus(interestPeriodSummary.interest()); + } + + this.totalInterestEarned = totalInterestEarned.getAmountDefaultedToNullIfZero(); + } + public boolean isLessThanOrEqualToAccountBalance(final Money amount) { final Money accountBalance = getAccountBalance(amount.getCurrency()); return accountBalance.isGreaterThanOrEqualTo(amount); 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 48362f53c..dae320886 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 @@ -37,7 +37,6 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Column(name = "transaction_type_enum", nullable = false) private final Integer typeOf; - @SuppressWarnings("unused") @Temporal(TemporalType.DATE) @Column(name = "transaction_date", nullable = false) private final Date dateOf; @@ -48,6 +47,20 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Column(name = "is_reversed", nullable = false) private boolean reversed; + @Column(name = "running_balance_derived", scale = 6, precision = 19, nullable = false) + private BigDecimal runningBalance; + + @SuppressWarnings("unused") + @Column(name = "cumulative_balance_derived", scale = 6, precision = 19, nullable = false) + private BigDecimal cumulativeBalance; + + @Temporal(TemporalType.DATE) + @Column(name = "balance_end_date_derived", nullable = false) + private Date balanceEndDate; + + @Column(name = "balance_number_of_days_derived", nullable = false) + private Integer balanceNumberOfDays; + protected SavingsAccountTransaction() { this.dateOf = null; this.typeOf = null; @@ -63,15 +76,19 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.WITHDRAWAL.getValue(), date, amount, isReversed); } - private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Integer typeOf, final LocalDate dateOf, + private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed) { this.savingsAccount = savingsAccount; this.typeOf = typeOf; - this.dateOf = dateOf.toDate(); + this.dateOf = transactionLocalDate.toDate(); this.amount = amount.getAmount(); this.reversed = isReversed; } + public LocalDate localDate() { + return new LocalDate(this.dateOf); + } + public void reverse() { this.reversed = true; } @@ -99,4 +116,53 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public boolean isReversed() { return this.reversed; } + + public void zeroBalanceFields() { + this.runningBalance = null; + this.cumulativeBalance = null; + this.balanceEndDate = null; + this.balanceNumberOfDays = null; + } + + public void updateRunningBalance(final Money balance) { + this.runningBalance = balance.getAmount(); + } + + public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) { + this.balanceEndDate = endOfBalanceDate.toDate(); + this.balanceNumberOfDays = InterestPeriodInterval.create(getTransactionLocalDate(), endOfBalanceDate) + .daysInPeriodInclusiveOfEndDate(); + this.cumulativeBalance = Money.of(currency, this.runningBalance).multipliedBy(this.balanceNumberOfDays).getAmount(); + } + + private LocalDate getTransactionLocalDate() { + return new LocalDate(this.dateOf); + } + + private LocalDate getEndOfBalanceLocalDate() { + LocalDate endDate = null; + if (this.balanceEndDate != null) { + endDate = new LocalDate(this.balanceEndDate); + } + return endDate; + } + + public boolean isAcceptableForDailyBalance(final InterestPeriodInterval interestPeriodInterval) { + return isNotReversed() && interestPeriodInterval.contains(getTransactionLocalDate()) && isABalanceForAtLeastOneDay(); + } + + private boolean isABalanceForAtLeastOneDay() { + return this.balanceNumberOfDays != null && this.balanceNumberOfDays > 0; + } + + public SavingsAccountDailyBalance toDailyBalance(final LocalDate periodEnd) { + + LocalDate balanceValidTo = getEndOfBalanceLocalDate(); + if (periodEnd.isBefore(getEndOfBalanceLocalDate())) { + balanceValidTo = periodEnd; + } + + final InterestPeriodInterval interestPeriodInterval = InterestPeriodInterval.create(getTransactionLocalDate(), balanceValidTo); + return SavingsAccountDailyBalance.createFrom(interestPeriodInterval, this.runningBalance); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java new file mode 100644 index 000000000..9ad691fa1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java @@ -0,0 +1,52 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +/** + *
    + * People typically use either of the following settings when calculating there + * interest using the daily method: + *
  • 360 and
  • + *
  • 365
  • + *
+ */ +public enum SavingsInterestCalculationDaysInYearType { + + INVALID(0, "savingsInterestCalculationDaysInYearType.invalid"), // + DAYS_360(360, "savingsInterestCalculationDaysInYearType.days360"), // + DAYS_365(365, "savingsInterestCalculationDaysInYearType.days365"); + + private final Integer value; + private final String code; + + private SavingsInterestCalculationDaysInYearType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public static SavingsInterestCalculationDaysInYearType fromInt(final Integer type) { + SavingsInterestCalculationDaysInYearType repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.INVALID; + if (type != null) { + switch (type) { + case 360: + repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_360; + break; + case 365: + repaymentFrequencyType = SavingsInterestCalculationDaysInYearType.DAYS_365; + break; + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java new file mode 100644 index 000000000..c060a9818 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java @@ -0,0 +1,66 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +/** + *
    + * There are two methods to calculate the interest on a savings account: + *
  • The daily balance method; and
  • + *
  • The average daily balance method.
  • + *
+ * + *

+ * The interest calculation must be based on a point in time for determining the + * balance in the account, such as: + *

+ *
    + *
  • beginning-of-day balance
  • + *
  • end-of-day balance
  • + *
  • close-of-business-day balance
  • + *
+ * + *

+ * Any one of the three may be used, but must be applied consistently. + * End-of-day balance is used by default at present. + *

+ */ +public enum SavingsInterestCalculationType { + + INVALID(0, "savingsInterestCalculationType.invalid"), // + DAILY_BALANCE(1, "savingsInterestCalculationType.dailybalance"), // + AVERAGE_DAILY_BALANCE(2, "savingsInterestCalculationType.averagedailybalance"); + + private final Integer value; + private final String code; + + private SavingsInterestCalculationType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public static SavingsInterestCalculationType fromInt(final Integer type) { + SavingsInterestCalculationType repaymentFrequencyType = SavingsInterestCalculationType.INVALID; + if (type != null) { + switch (type) { + case 1: + repaymentFrequencyType = SavingsInterestCalculationType.DAILY_BALANCE; + break; + case 2: + repaymentFrequencyType = SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE; + break; + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculator.java new file mode 100644 index 000000000..5d624315f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculator.java @@ -0,0 +1,10 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.util.List; + +public interface SavingsInterestCalculator { + + BigDecimal calculate(List dailyBalances, Integer numberOfDays); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java new file mode 100644 index 000000000..20c7e600b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java @@ -0,0 +1,27 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; + +public class SavingsInterestCalculatorFactory { + + public SavingsInterestCalculator createFrom(final SavingsInterestCalculationType interestCalculationType, + final BigDecimal periodsInOneYearAsFraction, final BigDecimal annualInterestRateAsFraction) { + + SavingsInterestCalculator savingsInterestCalculator = new SavingsInterestCalculatorForDailyBalance(periodsInOneYearAsFraction, + annualInterestRateAsFraction); + switch (interestCalculationType) { + case AVERAGE_DAILY_BALANCE: + savingsInterestCalculator = new SavingsInterestCalculatorForAverageBalance(periodsInOneYearAsFraction, + annualInterestRateAsFraction); + break; + case DAILY_BALANCE: + savingsInterestCalculator = new SavingsInterestCalculatorForDailyBalance(periodsInOneYearAsFraction, + annualInterestRateAsFraction); + break; + case INVALID: + break; + } + + return savingsInterestCalculator; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForAverageBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForAverageBalance.java new file mode 100644 index 000000000..9d4fc6a88 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForAverageBalance.java @@ -0,0 +1,44 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.List; + +/** + * Calculates interest on a savings account using the Average Balance + * method. + */ +public class SavingsInterestCalculatorForAverageBalance implements SavingsInterestCalculator { + + private final BigDecimal periodsInOneYearAsFraction; + private final BigDecimal annualInterestRateAsFraction; + + public SavingsInterestCalculatorForAverageBalance(final BigDecimal periodsInOneYearAsFraction, + final BigDecimal annualInterestRateAsFraction) { + this.periodsInOneYearAsFraction = periodsInOneYearAsFraction; + this.annualInterestRateAsFraction = annualInterestRateAsFraction; + } + + @Override + public BigDecimal calculate(final List dailyBalances, final Integer numberOfDays) { + + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final BigDecimal dailyInterestRate = annualInterestRateAsFraction.multiply(periodsInOneYearAsFraction, mc); + final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays.longValue())); + + final BigDecimal numberOfDaysBigDecimal = BigDecimal.valueOf(numberOfDays.longValue()); + + BigDecimal totalCumulativeBalanceForPeriod = BigDecimal.ZERO; + for (SavingsAccountDailyBalance balance : dailyBalances) { + if (balance.isGreaterThanZero()) { + final BigDecimal cumulativeBalance = balance.cumulativeBalance(); + totalCumulativeBalanceForPeriod = totalCumulativeBalanceForPeriod.add(cumulativeBalance); + } + } + + final BigDecimal averageDailyBalance = totalCumulativeBalanceForPeriod.divide(numberOfDaysBigDecimal, mc); + + return averageDailyBalance.multiply(periodicInterestRate, mc); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForDailyBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForDailyBalance.java new file mode 100644 index 000000000..287025d0d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorForDailyBalance.java @@ -0,0 +1,39 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.List; + +/** + * Calculates interest on a savings account using the Daily Balance + * method. + */ +public class SavingsInterestCalculatorForDailyBalance implements SavingsInterestCalculator { + + private final BigDecimal periodsInOneYearAsFraction; + private final BigDecimal annualInterestRateAsFraction; + + public SavingsInterestCalculatorForDailyBalance(final BigDecimal periodsInOneYearAsFraction, + final BigDecimal annualInterestRateAsFraction) { + this.periodsInOneYearAsFraction = periodsInOneYearAsFraction; + this.annualInterestRateAsFraction = annualInterestRateAsFraction; + } + + @Override + public BigDecimal calculate(final List dailyBalances, final Integer numberOfDays) { + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final BigDecimal dailyInterestRate = annualInterestRateAsFraction.multiply(periodsInOneYearAsFraction, mc); + + BigDecimal interestCalculated = BigDecimal.ZERO; + + for (SavingsAccountDailyBalance balance : dailyBalances) { + if (balance.isGreaterThanZero()) { + final BigDecimal interestForBalance = balance.interestUsingDailyBalanceMethod(dailyInterestRate, numberOfDays); + interestCalculated = interestCalculated.add(interestForBalance, mc); + } + } + + return interestCalculated; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPeriodType.java new file mode 100644 index 000000000..9ab136fef --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPeriodType.java @@ -0,0 +1,75 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +/** + *

+ * The interest period is the span of time at the end of which savings in a + * client’s account earn interest. + *

+ * + *

+ * Interest periods may differ for different types of savings products. In all + * cases, savings accounts must have established and published interest periods. + *

+ */ +public enum SavingsInterestPeriodType { + + INVALID(0, "savingsCalculationPeriodType.invalid"), // + DAILY(1, "savingsCalculationPeriodType.daily"), // + WEEKLY(2, "savingsCalculationPeriodType.weekly"), // + BIWEEKLY(3, "savingsCalculationPeriodType.biweekly"), // + MONTHLY(4, "savingsCalculationPeriodType.monthly"), // + QUATERLY(5, "savingsCalculationPeriodType.quarterly"), // + SEMIANNUAL(6, "savingsCalculationPeriodType.semiannual"), // + ANNUAL(7, "savingsCalculationPeriodType.annual"); + + private final Integer value; + private final String code; + + private SavingsInterestPeriodType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public static SavingsInterestPeriodType fromInt(final Integer type) { + SavingsInterestPeriodType repaymentFrequencyType = SavingsInterestPeriodType.INVALID; + if (type != null) { + switch (type) { + case 1: + repaymentFrequencyType = SavingsInterestPeriodType.DAILY; + break; + case 2: + repaymentFrequencyType = SavingsInterestPeriodType.WEEKLY; + break; + case 3: + repaymentFrequencyType = SavingsInterestPeriodType.BIWEEKLY; + break; + case 4: + repaymentFrequencyType = SavingsInterestPeriodType.MONTHLY; + break; + case 5: + repaymentFrequencyType = SavingsInterestPeriodType.QUATERLY; + break; + case 6: + repaymentFrequencyType = SavingsInterestPeriodType.SEMIANNUAL; + break; + case 7: + repaymentFrequencyType = SavingsInterestPeriodType.ANNUAL; + break; + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java new file mode 100644 index 000000000..5397729f4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java @@ -0,0 +1,54 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +/** + * An enumeration of supported calendar periods used in savings. + */ +public enum SavingsPeriodFrequencyType { + DAYS(0, "savingsPeriodFrequencyType.days"), // + WEEKS(1, "savingsPeriodFrequencyType.weeks"), // + MONTHS(2, "savingsPeriodFrequencyType.months"), // + YEARS(3, "savingsPeriodFrequencyType.years"), // + INVALID(4, "savingsPeriodFrequencyType.invalid"); + + private final Integer value; + private final String code; + + private SavingsPeriodFrequencyType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public static SavingsPeriodFrequencyType fromInt(final Integer type) { + SavingsPeriodFrequencyType repaymentFrequencyType = SavingsPeriodFrequencyType.INVALID; + if (type != null) { + switch (type) { + case 0: + repaymentFrequencyType = SavingsPeriodFrequencyType.DAYS; + break; + case 1: + repaymentFrequencyType = SavingsPeriodFrequencyType.WEEKS; + break; + case 2: + repaymentFrequencyType = SavingsPeriodFrequencyType.MONTHS; + break; + case 3: + repaymentFrequencyType = SavingsPeriodFrequencyType.YEARS; + break; + } + } + return repaymentFrequencyType; + } +} 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 af4b2af19..3c18282ae 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 @@ -8,6 +8,9 @@ package org.mifosplatform.portfolio.savings.domain; 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; +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.interestPeriodTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.localeParamName; @@ -51,6 +54,28 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "nominal_interest_rate_period_frequency_enum", nullable = false) private Integer interestRatePeriodFrequencyType; + /** + * 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 SavingsInterestPeriodType} enumeration. + */ + @Column(name = "interest_period_enum", nullable = false) + private Integer interestPeriodType; + + /** + * A value from the {@link SavingsInterestCalculationType} enumeration. + */ + @Column(name = "interest_calculation_type_enum", nullable = false) + private Integer interestCalculationType; + + /** + * A value from the {@link SavingsInterestCalculationDaysInYearType} + * enumeration. + */ + @Column(name = "interest_calculation_days_in_year_type_enum", nullable = false) + private Integer interestCalculationDaysInYearType; + @Column(name = "min_required_opening_balance", scale = 6, precision = 19, nullable = false) private BigDecimal minRequiredOpeningBalance; @@ -61,12 +86,15 @@ public class SavingsProduct extends AbstractPersistable { private Integer lockinPeriodFrequencyType; public static SavingsProduct createNew(final String name, final String description, final MonetaryCurrency currency, - final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, + final BigDecimal interestRate, final SavingsPeriodFrequencyType interestRatePeriodFrequencyType, + final SavingsInterestPeriodType interestPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { - return new SavingsProduct(name, description, currency, interestRate, interestRatePeriodFrequencyType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType); + return new SavingsProduct(name, description, currency, interestRate, interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType); } protected SavingsProduct() { @@ -75,7 +103,9 @@ public class SavingsProduct extends AbstractPersistable { } private SavingsProduct(final String name, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, - final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal minRequiredOpeningBalance, + final SavingsPeriodFrequencyType interestRatePeriodFrequencyType, final SavingsInterestPeriodType interestPeriodType, + final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { this.name = name; @@ -84,6 +114,9 @@ public class SavingsProduct extends AbstractPersistable { this.currency = currency; this.interestRate = interestRate; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType.getValue(); + this.interestPeriodType = interestPeriodType.getValue(); + this.interestCalculationType = interestCalculationType.getValue(); + this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue(); if (minRequiredOpeningBalance != null) { this.minRequiredOpeningBalance = Money.of(currency, minRequiredOpeningBalance).getAmount(); @@ -174,7 +207,25 @@ public class SavingsProduct extends AbstractPersistable { if (command.isChangeInIntegerParameterNamed(interestRatePeriodFrequencyTypeParamName, this.interestRatePeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(interestRatePeriodFrequencyTypeParamName); actualChanges.put(interestRatePeriodFrequencyTypeParamName, newValue); - this.interestRatePeriodFrequencyType = PeriodFrequencyType.fromInt(newValue).getValue(); + this.interestRatePeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(newValue).getValue(); + } + + if (command.isChangeInIntegerParameterNamed(interestPeriodTypeParamName, this.interestPeriodType)) { + final Integer newValue = command.integerValueOfParameterNamed(interestPeriodTypeParamName); + actualChanges.put(interestPeriodTypeParamName, newValue); + this.interestPeriodType = SavingsInterestPeriodType.fromInt(newValue).getValue(); + } + + if (command.isChangeInIntegerParameterNamed(interestCalculationTypeParamName, this.interestCalculationType)) { + final Integer newValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName); + actualChanges.put(interestCalculationTypeParamName, newValue); + this.interestCalculationType = SavingsInterestCalculationType.fromInt(newValue).getValue(); + } + + if (command.isChangeInIntegerParameterNamed(interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType)) { + final Integer newValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName); + actualChanges.put(interestCalculationDaysInYearTypeParamName, newValue); + this.interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(newValue).getValue(); } if (command.isChangeInBigDecimalParameterNamed(minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) { @@ -194,7 +245,7 @@ public class SavingsProduct extends AbstractPersistable { if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName); actualChanges.put(lockinPeriodFrequencyTypeParamName, newValue); - this.lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue).getValue(); + this.lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(newValue).getValue(); } validateLockinDetails(); @@ -203,9 +254,11 @@ public class SavingsProduct extends AbstractPersistable { } private void validateLockinDetails() { - if (isInvalidConfigurationOfLockinSettings()) { throw new InvalidSavingsProductSettingsException( - "error.msg.product.savings.invalid.lockin.settings", "Invalid configuration of lock in settings.", - lockinPeriodFrequencyParamName); } + if (isInvalidConfigurationOfLockinSettings()) { + // + throw new InvalidSavingsProductSettingsException("error.msg.product.savings.invalid.lockin.settings", + "Invalid configuration of lock in settings.", lockinPeriodFrequencyParamName); + } } private boolean isInvalidConfigurationOfLockinSettings() { 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 d75bee3b5..378f67aab 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 @@ -3,6 +3,9 @@ package org.mifosplatform.portfolio.savings.domain; 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; +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.interestPeriodTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; @@ -30,10 +33,29 @@ public class SavingsProductAssembler { MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal); final BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(interestRateParamName); - PeriodFrequencyType interestRatePeriodFrequencyType = null; + SavingsPeriodFrequencyType interestRatePeriodFrequencyType = null; final Integer interestRatePeriodFrequencyTypeValue = command.integerValueOfParameterNamed(interestRatePeriodFrequencyTypeParamName); if (interestRatePeriodFrequencyTypeValue != null) { - interestRatePeriodFrequencyType = PeriodFrequencyType.fromInt(interestRatePeriodFrequencyTypeValue); + interestRatePeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(interestRatePeriodFrequencyTypeValue); + } + + SavingsInterestPeriodType interestPeriodType = null; + final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestPeriodTypeParamName); + if (interestPeriodTypeValue != null) { + interestPeriodType = SavingsInterestPeriodType.fromInt(interestPeriodTypeValue); + } + + SavingsInterestCalculationType interestCalculationType = null; + final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName); + if (interestCalculationTypeValue != null) { + interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue); + } + + SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null; + final Integer interestCalculationDaysInYearTypeValue = command + .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName); + if (interestCalculationDaysInYearTypeValue != null) { + interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue); } final BigDecimal minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); @@ -45,7 +67,8 @@ public class SavingsProductAssembler { lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); } - return SavingsProduct.createNew(name, description, currency, interestRate, interestRatePeriodFrequencyType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); + return SavingsProduct.createNew(name, description, currency, interestRate, interestRatePeriodFrequencyType, interestPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java new file mode 100644 index 000000000..c6eb7359a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java @@ -0,0 +1,26 @@ +package org.mifosplatform.portfolio.savings.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CalculateInterestSavingsAccountCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + + @Autowired + public CalculateInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + this.savingAccountWritePlatformService = savingAccountWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.savingAccountWritePlatformService.calculateInterest(command.getSavingsId(), command); + } +} \ 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 5e9cfa81e..2e1588505 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 @@ -21,13 +21,16 @@ import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountSummaryData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.mifosplatform.portfolio.savings.data.SavingsProductData; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.exception.SavingsAccountNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -103,11 +106,15 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.nominal_interest_rate_per_period as interestRate, "); sqlBuilder.append("sa.nominal_interest_rate_period_frequency_enum as interestRatePeriodFrequencyType, "); sqlBuilder.append("sa.annual_nominal_interest_rate as annualInterestRate, "); + sqlBuilder.append("sa.interest_period_enum as interestPeriodType, "); + sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); + sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); + 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 "); sqlBuilder.append("from m_savings_account sa "); @@ -152,31 +159,45 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead currencyNameCode); final BigDecimal interestRate = rs.getBigDecimal("interestRate"); - final EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType - .fromInt(JdbcSupport.getInteger(rs, "interestRatePeriodFrequencyType"))); + final SavingsPeriodFrequencyType interestRatePeriodType = SavingsPeriodFrequencyType.fromInt(JdbcSupport.getInteger(rs, + "interestRatePeriodFrequencyType")); + final EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations + .interestRatePeriodFrequencyType(interestRatePeriodType); final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); + EnumOptionData interestPeriodType = SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.fromInt(JdbcSupport + .getInteger(rs, "interestPeriodType"))); + + EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType + .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); + + EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations + .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, + "interestCalculationDaysInYearType"))); + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance"); final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); EnumOptionData lockinPeriodFrequencyType = null; final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); if (lockinPeriodFrequencyTypeValue != null) { - lockinPeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType - .fromInt(lockinPeriodFrequencyTypeValue)); + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); } final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); + 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, - totalInterestPosted, accountBalance); + totalInterestEarned, totalInterestPosted, accountBalance); return SavingsAccountData.instance(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary); + interestPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, summary); } } @@ -206,11 +227,22 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection interestRatePeriodFrequencyTypeOptions = this.dropdownReadPlatformService .retrieveInterestRatePeriodFrequencyTypeOptions(); + + final Collection interestPeriodTypeOptions = this.dropdownReadPlatformService + .retrieveInterestPeriodTypeOptions(); + + final Collection interestCalculationTypeOptions = this.dropdownReadPlatformService + .retrieveInterestCalculationTypeOptions(); + + final Collection interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService + .retrieveInterestCalculationDaysInYearTypeOptions(); + final Collection lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService .retrieveLockinPeriodFrequencyTypeOptions(); final Collection transactions = null; template = SavingsAccountData.withTemplateOptions(template, productOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } else { @@ -227,10 +259,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead template = SavingsAccountData.withClientTemplate(clientId, clientName, groupId, groupName); final Collection interestRatePeriodFrequencyTypeOptions = null; + final Collection interestPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection transactions = null; template = SavingsAccountData.withTemplateOptions(template, productOptions, interestRatePeriodFrequencyTypeOptions, + interestPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } @@ -364,6 +400,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); sqlBuilder.append("sp.nominal_interest_rate_per_period as interestRate, "); sqlBuilder.append("sp.nominal_interest_rate_period_frequency_enum as interestRatePeriodFrequencyType, "); + sqlBuilder.append("sp.interest_period_enum as interestPeriodType, "); + sqlBuilder.append("sp.interest_calculation_type_enum as interestCalculationType, "); + sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, "); sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency, "); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType "); @@ -392,18 +431,30 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead currencyNameCode); final BigDecimal interestRate = rs.getBigDecimal("interestRate"); - final EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType - .fromInt(JdbcSupport.getInteger(rs, "interestRatePeriodFrequencyType"))); + final SavingsPeriodFrequencyType interestRatePeriodType = SavingsPeriodFrequencyType.fromInt(JdbcSupport.getInteger(rs, + "interestRatePeriodFrequencyType")); + final EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations + .interestRatePeriodFrequencyType(interestRatePeriodType); final BigDecimal annualInterestRate = null; + EnumOptionData interestPeriodType = SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.fromInt(JdbcSupport + .getInteger(rs, "interestPeriodType"))); + + EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType + .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); + + EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations + .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, + "interestCalculationDaysInYearType"))); + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance"); final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); EnumOptionData lockinPeriodFrequencyType = null; final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); if (lockinPeriodFrequencyTypeValue != null) { - lockinPeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType - .fromInt(lockinPeriodFrequencyTypeValue)); + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); } Long clientId = null; @@ -425,7 +476,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final SavingsAccountSummaryData summary = null; return SavingsAccountData.instance(null, null, null, status, activationDate, groupId, groupName, clientId, clientName, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary); + interestPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, summary); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 2f8240d96..912f47e41 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -21,4 +21,6 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult deposit(Long savingsId, JsonCommand command); CommandProcessingResult withdrawal(Long savingsId, JsonCommand command); + + CommandProcessingResult calculateInterest(Long savingsId, JsonCommand command); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 678116cda..09402160f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -314,4 +314,22 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withSavingsId(savingsId) // .build(); } + + @Override + public CommandProcessingResult calculateInterest(final Long savingsId, final JsonCommand command) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + + account.calculateInterest(); + this.savingAccountRepository.save(account); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformService.java index a55d20bde..08afbf43b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformService.java @@ -9,4 +9,10 @@ public interface SavingsDropdownReadPlatformService { Collection retrieveInterestRatePeriodFrequencyTypeOptions(); Collection retrieveLockinPeriodFrequencyTypeOptions(); + + Collection retrieveInterestPeriodTypeOptions(); + + Collection retrieveInterestCalculationTypeOptions(); + + Collection retrieveInterestCalculationDaysInYearTypeOptions(); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java index 8b372cad6..abc9e19c3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java @@ -1,10 +1,14 @@ package org.mifosplatform.portfolio.savings.service; import java.util.Arrays; +import java.util.Collection; import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; import org.springframework.stereotype.Service; @Service @@ -13,8 +17,8 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe @Override public List retrieveInterestRatePeriodFrequencyTypeOptions() { List allowedInterestRatePeriodFrequencyTypeOptions = Arrays.asList( // - SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType.MONTHS), // - SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType.YEARS) // + SavingsEnumerations.interestRatePeriodFrequencyType(SavingsPeriodFrequencyType.MONTHS), // + SavingsEnumerations.interestRatePeriodFrequencyType(SavingsPeriodFrequencyType.YEARS) // ); return allowedInterestRatePeriodFrequencyTypeOptions; @@ -23,12 +27,47 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe @Override public List retrieveLockinPeriodFrequencyTypeOptions() { List allowedLockinPeriodFrequencyTypeOptions = Arrays.asList( // - SavingsEnumerations.lockinPeriodFrequencyType(PeriodFrequencyType.DAYS), // - SavingsEnumerations.lockinPeriodFrequencyType(PeriodFrequencyType.WEEKS), // - SavingsEnumerations.lockinPeriodFrequencyType(PeriodFrequencyType.MONTHS), // - SavingsEnumerations.lockinPeriodFrequencyType(PeriodFrequencyType.YEARS) // + SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.DAYS), // + SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.WEEKS), // + SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.MONTHS), // + SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType.YEARS) // ); return allowedLockinPeriodFrequencyTypeOptions; } + + @Override + public Collection retrieveInterestPeriodTypeOptions() { + List allowedOptions = Arrays.asList( // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.DAILY), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.WEEKLY), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.BIWEEKLY), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.MONTHLY), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.QUATERLY), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.SEMIANNUAL), // + SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.ANNUAL) // + ); + + return allowedOptions; + } + + @Override + public Collection retrieveInterestCalculationTypeOptions() { + List allowedOptions = Arrays.asList( // + SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE), // + SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE) // + ); + + return allowedOptions; + } + + @Override + public Collection retrieveInterestCalculationDaysInYearTypeOptions() { + List allowedOptions = Arrays.asList( // + SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_360), // + SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365) // + ); + + return allowedOptions; + } } \ 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 0d4fb415e..545107e23 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 @@ -6,71 +6,74 @@ package org.mifosplatform.portfolio.savings.service; import org.mifosplatform.infrastructure.core.data.EnumOptionData; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; public class SavingsEnumerations { public static EnumOptionData interestRatePeriodFrequencyType(final int id) { - return interestRatePeriodFrequencyType(PeriodFrequencyType.fromInt(id)); + return interestRatePeriodFrequencyType(SavingsPeriodFrequencyType.fromInt(id)); } - public static EnumOptionData interestRatePeriodFrequencyType(final PeriodFrequencyType type) { + public static EnumOptionData interestRatePeriodFrequencyType(final SavingsPeriodFrequencyType type) { final String codePrefix = "savings.interest.rate."; - EnumOptionData optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), - PeriodFrequencyType.INVALID.getCode(), "Invalid"); + EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(), + SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid"); switch (type) { case INVALID: break; case DAYS: - optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix - + PeriodFrequencyType.DAYS.getCode(), "Per day"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.DAYS.getCode(), "Per day"); break; case WEEKS: - optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix - + PeriodFrequencyType.WEEKS.getCode(), "Per week"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.WEEKS.getCode(), "Per week"); break; case MONTHS: - optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix - + PeriodFrequencyType.MONTHS.getCode(), "Per month"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.MONTHS.getCode(), "Per month"); break; case YEARS: - optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix - + PeriodFrequencyType.YEARS.getCode(), "Per year"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.YEARS.getCode(), "Per year"); break; } return optionData; } public static EnumOptionData lockinPeriodFrequencyType(final int id) { - return lockinPeriodFrequencyType(PeriodFrequencyType.fromInt(id)); + return lockinPeriodFrequencyType(SavingsPeriodFrequencyType.fromInt(id)); } - public static EnumOptionData lockinPeriodFrequencyType(final PeriodFrequencyType type) { + public static EnumOptionData lockinPeriodFrequencyType(final SavingsPeriodFrequencyType type) { final String codePrefix = "savings.lockin."; - EnumOptionData optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), - PeriodFrequencyType.INVALID.getCode(), "Invalid"); + EnumOptionData optionData = new EnumOptionData(SavingsPeriodFrequencyType.INVALID.getValue().longValue(), + SavingsPeriodFrequencyType.INVALID.getCode(), "Invalid"); switch (type) { case INVALID: break; case DAYS: - optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix - + PeriodFrequencyType.DAYS.getCode(), "Days"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.DAYS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.DAYS.getCode(), "Days"); break; case WEEKS: - optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix - + PeriodFrequencyType.WEEKS.getCode(), "Weeks"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.WEEKS.getCode(), "Weeks"); break; case MONTHS: - optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix - + PeriodFrequencyType.MONTHS.getCode(), "Months"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.MONTHS.getCode(), "Months"); break; case YEARS: - optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(), codePrefix - + PeriodFrequencyType.YEARS.getCode(), "Years"); + optionData = new EnumOptionData(SavingsPeriodFrequencyType.YEARS.getValue().longValue(), codePrefix + + SavingsPeriodFrequencyType.YEARS.getCode(), "Years"); break; } return optionData; @@ -136,4 +139,99 @@ public class SavingsEnumerations { } return optionData; } + + public static EnumOptionData interestPeriodType(final Integer type) { + return interestPeriodType(SavingsInterestPeriodType.fromInt(type)); + } + + public static EnumOptionData interestPeriodType(final SavingsInterestPeriodType type) { + + final String codePrefix = "savings.interest.period."; + EnumOptionData optionData = new EnumOptionData(SavingsInterestPeriodType.INVALID.getValue().longValue(), + SavingsInterestPeriodType.INVALID.getCode(), "Invalid"); + + switch (type) { + case INVALID: + break; + case DAILY: + optionData = new EnumOptionData(SavingsInterestPeriodType.DAILY.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.DAILY.getCode(), "Daily"); + break; + case WEEKLY: + optionData = new EnumOptionData(SavingsInterestPeriodType.WEEKLY.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.WEEKLY.getCode(), "Weekly"); + break; + case BIWEEKLY: + optionData = new EnumOptionData(SavingsInterestPeriodType.BIWEEKLY.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.BIWEEKLY.getCode(), "Bi-Weekly"); + break; + case MONTHLY: + optionData = new EnumOptionData(SavingsInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.MONTHLY.getCode(), "Monthly"); + break; + case QUATERLY: + optionData = new EnumOptionData(SavingsInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.QUATERLY.getCode(), "Quarterly"); + break; + case SEMIANNUAL: + optionData = new EnumOptionData(SavingsInterestPeriodType.SEMIANNUAL.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.SEMIANNUAL.getCode(), "Semi-Annual"); + break; + case ANNUAL: + optionData = new EnumOptionData(SavingsInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix + + SavingsInterestPeriodType.ANNUAL.getCode(), "Annually"); + break; + } + + return optionData; + } + + public static EnumOptionData interestCalculationType(final Integer type) { + return interestCalculationType(SavingsInterestCalculationType.fromInt(type)); + } + + public static EnumOptionData interestCalculationType(final SavingsInterestCalculationType type) { + + EnumOptionData optionData = new EnumOptionData(SavingsInterestCalculationType.INVALID.getValue().longValue(), + SavingsInterestCalculationType.INVALID.getCode(), "Invalid"); + + switch (type) { + case INVALID: + break; + case DAILY_BALANCE: + optionData = new EnumOptionData(SavingsInterestCalculationType.DAILY_BALANCE.getValue().longValue(), + SavingsInterestCalculationType.DAILY_BALANCE.getCode(), "Daily Balance"); + break; + case AVERAGE_DAILY_BALANCE: + optionData = new EnumOptionData(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE.getValue().longValue(), + SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE.getCode(), "Average Daily Balance"); + break; + } + + return optionData; + } + + public static EnumOptionData interestCalculationDaysInYearType(final Integer type) { + return interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.fromInt(type)); + } + + public static EnumOptionData interestCalculationDaysInYearType(final SavingsInterestCalculationDaysInYearType type) { + EnumOptionData optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.INVALID.getValue().longValue(), + SavingsInterestCalculationDaysInYearType.INVALID.getCode(), "Invalid"); + + switch (type) { + case INVALID: + break; + case DAYS_360: + optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.DAYS_360.getValue().longValue(), + SavingsInterestCalculationDaysInYearType.DAYS_360.getCode(), "360 Days"); + break; + case DAYS_365: + optionData = new EnumOptionData(SavingsInterestCalculationDaysInYearType.DAYS_365.getValue().longValue(), + SavingsInterestCalculationDaysInYearType.DAYS_365.getCode(), "365 Days"); + break; + } + + return optionData; + } } \ No newline at end of file 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 0a229a906..e9861bfb7 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 @@ -15,8 +15,11 @@ import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.data.SavingsProductData; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -69,15 +72,30 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead private static final class SavingProductMapper implements RowMapper { + private final String schemaSql; + + public SavingProductMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sp.id as id, sp.name as name, sp.description as description, "); + sqlBuilder.append("sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, "); + sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + sqlBuilder.append("sp.nominal_interest_rate_per_period as interestRate, "); + sqlBuilder.append("sp.nominal_interest_rate_period_frequency_enum as interestRatePeriodFrequencyType, "); + sqlBuilder.append("sp.interest_period_enum as interestPeriodType, "); + sqlBuilder.append("sp.interest_calculation_type_enum as interestCalculationType, "); + sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); + sqlBuilder.append("sp.min_required_opening_balance as minRequiredOpeningBalance, "); + sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); + sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType "); + sqlBuilder.append("from m_savings_product sp "); + sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); + + this.schemaSql = sqlBuilder.toString(); + } + public String schema() { - return "sp.id as id,sp.name as name, sp.description as description, " - + "sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, " - + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, " - + "sp.nominal_interest_rate_per_period as interestRate, sp.nominal_interest_rate_period_frequency_enum as interestRatePeriodFrequencyType, " - + "sp.min_required_opening_balance as minRequiredOpeningBalance, " - + "sp.lockin_period_frequency as lockinPeriodFrequency, sp.lockin_period_frequency_enum as lockinPeriodFrequencyType " // - + "from m_savings_product sp " // - + "join m_currency curr on curr.code = sp.currency_code"; + return this.schemaSql; } @Override @@ -96,21 +114,37 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, currencyNameCode); BigDecimal interestRate = rs.getBigDecimal("interestRate"); - EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType + + EnumOptionData interestRatePeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(SavingsPeriodFrequencyType .fromInt(JdbcSupport.getInteger(rs, "interestRatePeriodFrequencyType"))); + EnumOptionData interestPeriodType = SavingsEnumerations.interestPeriodType(SavingsInterestPeriodType.fromInt(JdbcSupport + .getInteger(rs, "interestPeriodType"))); + + EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType + .fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); + + EnumOptionData interestCalculationDaysInYearType = null; + final Integer interestCalculationDaysInYearTypeValue = JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType"); + if (interestCalculationDaysInYearTypeValue != null) { + interestCalculationDaysInYearType = SavingsEnumerations + .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType + .fromInt(interestCalculationDaysInYearTypeValue)); + } + final BigDecimal minRequiredOpeningBalance = rs.getBigDecimal("minRequiredOpeningBalance"); Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); EnumOptionData lockinPeriodFrequencyType = null; final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); if (lockinPeriodFrequencyTypeValue != null) { - lockinPeriodFrequencyType = SavingsEnumerations.interestRatePeriodFrequencyType(PeriodFrequencyType + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(SavingsPeriodFrequencyType .fromInt(lockinPeriodFrequencyTypeValue)); } return SavingsProductData.instance(id, name, description, currency, interestRate, interestRatePeriodFrequencyType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); + interestPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType); } }