MIFOSX-250: add support for daily balance and average daily balance methods. support for daily,weekly,monthly interest periods.

This commit is contained in:
Keith Woodlock
2013-03-27 23:45:43 +00:00
parent ca038a8ebf
commit 715bc2cf17
40 changed files with 1616 additions and 135 deletions
@@ -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`)
@@ -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
@@ -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`;
@@ -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");
}
@@ -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";
@@ -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());
}
@@ -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<Object> 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; }
@@ -141,6 +141,9 @@ public class SavingsAccountsApiResource {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsProductData> productOptions = null;
Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
Collection<EnumOptionData> interestPeriodTypeOptions = null;
Collection<EnumOptionData> interestCalculationTypeOptions = null;
Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Set<String> 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) {
@@ -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<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<String>(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<String> SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(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<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<String>(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<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(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<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName));
@@ -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<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = this.dropdownReadPlatformService
.retrieveInterestRatePeriodFrequencyTypeOptions();
final Collection<EnumOptionData> interestPeriodTypeOptions = this.dropdownReadPlatformService.retrieveInterestPeriodTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions();
final Collection<EnumOptionData> 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;
@@ -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<SavingsProductData> productOptions;
private final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions;
private final Collection<EnumOptionData> interestPeriodTypeOptions;
private final Collection<EnumOptionData> interestCalculationTypeOptions;
private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
private final Collection<EnumOptionData> 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<SavingsProductData> productOptions = null;
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> interestPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<SavingsAccountTransactionData> 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<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> interestPeriodTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<SavingsAccountTransactionData> 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<SavingsProductData> productOptions = null;
final Collection<CurrencyData> currencyOptions = null;
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> interestPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<SavingsAccountTransactionData> 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<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> interestPeriodTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<SavingsAccountTransactionData> 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;
}
@@ -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);
@@ -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;
}
@@ -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<CurrencyData> currencyOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestPeriodTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestCalculationTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestRatePeriodFrequencyType,
final Collection<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final EnumOptionData interestPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final Collection<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> interestPeriodTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> 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<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> interestPeriodTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> 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<CurrencyData> currencyOptions = null;
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> interestPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> 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<CurrencyData> currencyOptions = null;
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> interestPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> 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<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> interestPeriodTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> 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;
}
}
@@ -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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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);
@@ -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);
}
}
@@ -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<SavingsAccountDailyBalance> dailyBalances;
public static InterestPeriodSummary from(final InterestPeriodInterval interestPeriodInterval, final Money startingBalance,
final List<SavingsAccountDailyBalance> dailyBalances, final BigDecimal totalInterestEarnedUnrounded) {
return new InterestPeriodSummary(interestPeriodInterval, startingBalance, dailyBalances, totalInterestEarnedUnrounded);
}
private InterestPeriodSummary(final InterestPeriodInterval interestPeriodInterval, final Money startingBalance,
final List<SavingsAccountDailyBalance> 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;
}
}
@@ -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<Long> {
@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<Long> {
}
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<Long> {
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<Long> {
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<InterestPeriodInterval> interestPeriods = determineInterestPeriods(getActivationLocalDate(), interestPeriodType);
final List<InterestPeriodSummary> interestPeriodSummaries = deriveInterestPeriodSummaries(openingAccountBalance, interestPeriods,
this.annualInterestRate, daysInYearType);
this.summary.updateFromInterestPeriodSummaries(currency, interestPeriodSummaries);
}
private List<InterestPeriodInterval> determineInterestPeriods(final LocalDate activationLocalDate,
final SavingsInterestPeriodType interestPeriodType) {
LocalDate today = DateUtils.getLocalDateOfTenant();
LocalDate periodStartDate = activationLocalDate;
LocalDate periodEndDate = periodStartDate;
final List<InterestPeriodInterval> interestPeriods = new ArrayList<InterestPeriodInterval>();
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<InterestPeriodSummary> deriveInterestPeriodSummaries(final Money openingAccountBalance,
final List<InterestPeriodInterval> interestPeriods, final BigDecimal annualInterestRate,
final SavingsInterestCalculationDaysInYearType daysInYearType) {
final List<InterestPeriodSummary> summaries = new ArrayList<InterestPeriodSummary>();
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<SavingsAccountDailyBalance> dailyBalances = new ArrayList<SavingsAccountDailyBalance>();
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<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
calculateInterest();
return transaction;
}
@@ -368,6 +614,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
calculateInterest();
return transaction;
}
@@ -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);
@@ -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);
}
}
@@ -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<InterestPeriodSummary> 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);
@@ -37,7 +37,6 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
@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<Long> {
@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<Long> {
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<Long> {
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);
}
}
@@ -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;
/**
* <ul>
* People typically use either of the following settings when calculating there
* interest using the daily method:
* <li>360 and</li>
* <li>365</li>
* </ul>
*/
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;
}
}
@@ -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;
/**
* <ul>
* There are two methods to calculate the interest on a savings account:
* <li>The daily balance method; and</li>
* <li>The average daily balance method.</li>
* </ul>
*
* <p>
* The interest calculation must be based on a point in time for determining the
* balance in the account, such as:
* </p>
* <ul>
* <li>beginning-of-day balance</li>
* <li>end-of-day balance</li>
* <li>close-of-business-day balance</li>
* </ul>
*
* <p>
* Any one of the three may be used, but must be applied consistently.
* End-of-day balance is used by default at present.
* </p>
*/
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;
}
}
@@ -0,0 +1,10 @@
package org.mifosplatform.portfolio.savings.domain;
import java.math.BigDecimal;
import java.util.List;
public interface SavingsInterestCalculator {
BigDecimal calculate(List<SavingsAccountDailyBalance> dailyBalances, Integer numberOfDays);
}
@@ -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;
}
}
@@ -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 <b>Average Balance</b>
* 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<SavingsAccountDailyBalance> 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);
}
}
@@ -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 <b>Daily Balance</b>
* 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<SavingsAccountDailyBalance> 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;
}
}
@@ -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;
/**
* <p>
* The interest period is the span of time at the end of which savings in a
* client’s account earn interest.
* </p>
*
* <p>
* Interest periods may differ for different types of savings products. In all
* cases, savings accounts must have established and published interest periods.
* </p>
*/
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;
}
}
@@ -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;
}
}
@@ -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<Long> {
@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<Long> {
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<Long> {
}
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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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() {
@@ -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);
}
}
@@ -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);
}
}
@@ -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<EnumOptionData> interestRatePeriodFrequencyTypeOptions = this.dropdownReadPlatformService
.retrieveInterestRatePeriodFrequencyTypeOptions();
final Collection<EnumOptionData> interestPeriodTypeOptions = this.dropdownReadPlatformService
.retrieveInterestPeriodTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions();
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService
.retrieveLockinPeriodFrequencyTypeOptions();
final Collection<SavingsAccountTransactionData> 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<EnumOptionData> interestRatePeriodFrequencyTypeOptions = null;
final Collection<EnumOptionData> interestPeriodTypeOptions = null;
final Collection<EnumOptionData> interestCalculationTypeOptions = null;
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = null;
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<SavingsAccountTransactionData> 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);
}
}
}
@@ -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);
}
@@ -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();
}
}
@@ -9,4 +9,10 @@ public interface SavingsDropdownReadPlatformService {
Collection<EnumOptionData> retrieveInterestRatePeriodFrequencyTypeOptions();
Collection<EnumOptionData> retrieveLockinPeriodFrequencyTypeOptions();
Collection<EnumOptionData> retrieveInterestPeriodTypeOptions();
Collection<EnumOptionData> retrieveInterestCalculationTypeOptions();
Collection<EnumOptionData> retrieveInterestCalculationDaysInYearTypeOptions();
}
@@ -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<EnumOptionData> retrieveInterestRatePeriodFrequencyTypeOptions() {
List<EnumOptionData> 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<EnumOptionData> retrieveLockinPeriodFrequencyTypeOptions() {
List<EnumOptionData> 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<EnumOptionData> retrieveInterestPeriodTypeOptions() {
List<EnumOptionData> 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<EnumOptionData> retrieveInterestCalculationTypeOptions() {
List<EnumOptionData> allowedOptions = Arrays.asList( //
SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE), //
SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType.AVERAGE_DAILY_BALANCE) //
);
return allowedOptions;
}
@Override
public Collection<EnumOptionData> retrieveInterestCalculationDaysInYearTypeOptions() {
List<EnumOptionData> allowedOptions = Arrays.asList( //
SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_360), //
SavingsEnumerations.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365) //
);
return allowedOptions;
}
}
@@ -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;
}
}
@@ -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<SavingsProductData> {
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);
}
}