diff --git a/mifosng-db/migrations/core_db/V81__savings_related_changes.sql b/mifosng-db/migrations/core_db/V81__savings_related_changes.sql new file mode 100644 index 000000000..5cf68d763 --- /dev/null +++ b/mifosng-db/migrations/core_db/V81__savings_related_changes.sql @@ -0,0 +1,69 @@ +ALTER TABLE `m_note` +ADD COLUMN `savings_account_id` BIGINT(20) NULL DEFAULT NULL AFTER `loan_transaction_id`, +ADD COLUMN `savings_account_transaction_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_account_id`; + +ALTER TABLE `m_note` ADD CONSTRAINT `FK_savings_account_id` FOREIGN KEY (`savings_account_id` ) REFERENCES `m_savings_account` (`id` ) ON DELETE NO ACTION ON UPDATE NO ACTION, ADD INDEX `FK_savings_account_id` (`savings_account_id` ASC) ; + + +ALTER TABLE `m_savings_account_transaction` +CHANGE COLUMN `is_reversed` `is_reversed` TINYINT(1) NOT NULL AFTER `transaction_type_enum`, +CHANGE COLUMN `balance_end_date_derived` `balance_end_date_derived` DATE NULL DEFAULT NULL AFTER `amount`, +CHANGE COLUMN `balance_number_of_days_derived` `balance_number_of_days_derived` INT(11) NULL DEFAULT NULL AFTER `balance_end_date_derived`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `field_officer_id` bigint(20) DEFAULT NULL AFTER `product_id`, +ADD COLUMN `submittedon_date` DATE NOT NULL AFTER `status_enum`, +ADD COLUMN `submittedon_userid` bigint(20) DEFAULT NULL AFTER `submittedon_date`, +ADD COLUMN `approvedon_date` DATE DEFAULT NULL AFTER `submittedon_userid`, +ADD COLUMN `approvedon_userid` bigint(20) DEFAULT NULL AFTER `approvedon_date`, +ADD COLUMN `rejectedon_date` DATE DEFAULT NULL AFTER `approvedon_userid`, +ADD COLUMN `rejectedon_userid` bigint(20) DEFAULT NULL AFTER `rejectedon_date`, +ADD COLUMN `withdrawnon_date` DATE DEFAULT NULL AFTER `rejectedon_userid`, +ADD COLUMN `withdrawnon_userid` bigint(20) DEFAULT NULL AFTER `withdrawnon_date`; + +ALTER TABLE `m_savings_account` +CHANGE COLUMN `activation_date` `activatedon_date` DATE NULL DEFAULT NULL AFTER `withdrawnon_userid`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `activatedon_userid` bigint(20) DEFAULT NULL AFTER `activatedon_date`, +ADD COLUMN `closedon_date` DATE DEFAULT NULL AFTER `activatedon_userid`, +ADD COLUMN `closedon_userid` bigint(20) DEFAULT NULL AFTER `closedon_date`; + +UPDATE `m_savings_account` +SET +`submittedon_date`=`activatedon_date`, +`approvedon_date`=`activatedon_date`; + + +INSERT INTO `mifostenant-default`.`m_permission` +(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) +VALUES +('transaction_savings', 'APPROVE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVE', 1), +('transaction_savings', 'REJECT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'REJECT', 1), +('transaction_savings', 'WITHDRAW_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAW', 1), +('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'APPROVALUNDO', 1), +('transaction_savings', 'CLOSE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'CLOSE', 1); + +INSERT INTO `mifostenant-default`.`m_permission` +(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) +VALUES +('transaction_savings', 'APPROVE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVE', 0), +('transaction_savings', 'REJECT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'REJECT', 0), +('transaction_savings', 'WITHDRAW_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAW', 0), +('transaction_savings', 'APPROVALUNDO_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'APPROVALUNDO', 0), +('transaction_savings', 'CLOSE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'CLOSE', 0); + + +-- Remove permissions with 'in the past' permissions + +DELETE FROM `m_permission` WHERE `id`='210'; + +DELETE FROM `m_permission` WHERE `id`='212'; +DELETE FROM `m_permission` WHERE `id`='214'; +DELETE FROM `m_permission` WHERE `id`='217'; +DELETE FROM `m_permission` WHERE `id`='220'; +DELETE FROM `m_permission` WHERE `id`='233'; +DELETE FROM `m_permission` WHERE `id`='235'; +DELETE FROM `m_permission` WHERE `id`='237'; +DELETE FROM `m_permission` WHERE `id`='240'; +DELETE FROM `m_permission` WHERE `id`='243'; \ No newline at end of file diff --git a/mifosng-provider/build.gradle b/mifosng-provider/build.gradle index 4f6b03845..4618a16e8 100644 --- a/mifosng-provider/build.gradle +++ b/mifosng-provider/build.gradle @@ -53,7 +53,7 @@ project.ext.springVersion = '3.2.3.RELEASE' project.ext.springSecurityVersion = '3.1.4.RELEASE' project.ext.hibernateCoreVersion = '4.1.9.Final' project.ext.jerseyVersion = '1.17' -project.ext.springDataJpaVersion = '1.3.0.RELEASE' +project.ext.springDataJpaVersion = '1.3.2.RELEASE' project.ext.mysqlUser='root' project.ext.mysqlPassword='mysql' @@ -123,8 +123,8 @@ dependencies { [group: 'joda-time', name: 'joda-time', version: '2.1'], [group: 'net.sourceforge.javacsv', name: 'javacsv', version: '2.0'], - [group: 'org.apache.commons', name: 'commons-email', version: '1.2'], - [group: 'commons-lang', name: 'commons-lang', version: '2.4'], + [group: 'org.apache.commons', name: 'commons-email', version: '1.3.1'], + [group: 'org.apache.commons', name: 'commons-lang3', version: '3.1'], [group: 'org.slf4j', name: 'slf4j-api', version: '1.7.5'], [group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.5'], @@ -164,7 +164,8 @@ dependencies { [group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'] ) - testCompile 'junit:junit:4.11', + testCompile 'junit:junit:4.10', + 'junit:junit-dep:4.10', 'org.mockito:mockito-all:1.9.5', 'com.jayway.restassured:rest-assured:1.7.2' diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 620d163d7..daf95146e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -23,7 +23,8 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.*; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.*; import com.google.gson.JsonElement; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 085c1e108..2feeb8706 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -21,7 +21,8 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.*; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.*; import com.google.gson.JsonElement; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 2ebe99ad5..17b2ca473 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -415,42 +415,6 @@ public class CommandWrapper { return isUserResource() && isUpdate() && loggedInUserId.equals(this.entityId); } - public boolean isDepositProductResource() { - return this.entityName.equalsIgnoreCase("DEPOSITPRODUCT"); - } - - public boolean isDepositAccountResource() { - return this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isApprovalOfDeposit() { - return this.actionName.equalsIgnoreCase("APPROVE") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isWithdrawDepositAmount() { - return this.actionName.equalsIgnoreCase("WITHDRAWAL") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isWithdrawInterest() { - return this.actionName.equalsIgnoreCase("INTEREST") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isRenewOfDepositApplicaion() { - return this.actionName.equalsIgnoreCase("RENEW") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isRejectionOfDepositApplicaion() { - return this.actionName.equalsIgnoreCase("REJECT") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isWithdrawByApplicant() { - return this.actionName.equalsIgnoreCase("WITHDRAW") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - - public boolean isUndoApprovalOfDepositApplication() { - return this.actionName.equalsIgnoreCase("APPROVALUNDO") && this.entityName.equalsIgnoreCase("DEPOSITACCOUNT"); - } - public boolean isSavingsProductResource() { return this.entityName.equalsIgnoreCase("SAVINGSPRODUCT"); } @@ -459,6 +423,22 @@ public class CommandWrapper { return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } + public boolean isRejectionOfSavingsAccountApplication() { + return this.actionName.equalsIgnoreCase("REJECT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + + public boolean isWithdrawFromSavingsAccountApplicationByApplicant() { + return this.actionName.equalsIgnoreCase("WITHDRAW") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + + public boolean isApprovalOfSavingsAccountApplication() { + return this.actionName.equalsIgnoreCase("APPROVE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + + public boolean isUndoApprovalOfSavingsAccountApplication() { + return this.actionName.equalsIgnoreCase("APPROVALUNDO") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + public boolean isSavingsAccountActivation() { return this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index b49d75400..45db68066 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -757,6 +757,42 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder rejectSavingsAccountApplication(final Long accountId) { + this.actionName = "REJECT"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId + "?command=reject"; + return this; + } + + public CommandWrapperBuilder withdrawSavingsAccountApplication(final Long accountId) { + this.actionName = "WITHDRAW"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId + "?command=withdrawnByApplicant"; + return this; + } + + public CommandWrapperBuilder approveSavingsAccountApplication(final Long accountId) { + this.actionName = "APPROVE"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId + "?command=approve"; + return this; + } + + public CommandWrapperBuilder undoSavingsAccountApplication(final Long accountId) { + this.actionName = "APPROVALUNDO"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId + "?command=undoapproval"; + return this; + } + public CommandWrapperBuilder savingsAccountActivation(final Long accountId) { this.actionName = "ACTIVATE"; this.entityName = "SAVINGSACCOUNT"; @@ -1036,7 +1072,7 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder assignRole(Long groupId) { + public CommandWrapperBuilder assignRole(final Long groupId) { this.actionName = "ASSIGNROLE"; this.entityName = "GROUP"; this.groupId = groupId; @@ -1045,7 +1081,7 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder unassignRole(Long groupId, Long roleId) { + public CommandWrapperBuilder unassignRole(final Long groupId, final Long roleId) { this.actionName = "UNASSIGNROLE"; this.entityName = "GROUP"; this.groupId = groupId; @@ -1054,7 +1090,7 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder updateRole(Long groupId, Long roleId) { + public CommandWrapperBuilder updateRole(final Long groupId, final Long roleId) { this.actionName = "UPDATEROLE"; this.entityName = "GROUP"; this.groupId = groupId; @@ -1063,7 +1099,7 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder unassignClientStaff(Long clientId) { + public CommandWrapperBuilder unassignClientStaff(final Long clientId) { this.actionName = "UNASSIGNSTAFF"; this.entityName = "CLIENT"; this.entityId = clientId; @@ -1072,7 +1108,7 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder assignClientStaff(Long clientId) { + public CommandWrapperBuilder assignClientStaff(final Long clientId) { this.actionName = "ASSIGNSTAFF"; this.entityName = "CLIENT"; this.entityId = clientId; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index bc60b106e..68c98a282 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -374,11 +374,23 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } } else if (wrapper.isSavingsAccountResource()) { if (wrapper.isCreate()) { - handler = this.applicationContext.getBean("createSavingsAccountCommandHandler", NewCommandSourceHandler.class); + handler = this.applicationContext + .getBean("savingsAccountApplicationSubmittalCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isUpdate()) { - handler = this.applicationContext.getBean("updateSavingsAccountCommandHandler", NewCommandSourceHandler.class); + handler = this.applicationContext.getBean("savingsAccountApplicationModificationCommandHandler", + NewCommandSourceHandler.class); } else if (wrapper.isDelete()) { - handler = this.applicationContext.getBean("deleteSavingsAccountCommandHandler", NewCommandSourceHandler.class); + handler = this.applicationContext.getBean("savingsAccountApplicationDeletionCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isRejectionOfSavingsAccountApplication()) { + handler = this.applicationContext.getBean("savingsAccountApplicationRejectedCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isWithdrawFromSavingsAccountApplicationByApplicant()) { + handler = this.applicationContext.getBean("savingsAccountApplicationWithdrawnByApplicantCommandHandler", + NewCommandSourceHandler.class); + } else if (wrapper.isApprovalOfSavingsAccountApplication()) { + handler = this.applicationContext.getBean("savingsAccountApplicationApprovalCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUndoApprovalOfSavingsAccountApplication()) { + handler = this.applicationContext.getBean("savingsAccountApplicationApprovalUndoCommandHandler", + NewCommandSourceHandler.class); } else if (wrapper.isSavingsAccountDeposit()) { handler = this.applicationContext.getBean("depositSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isSavingsAccountWithdrawal()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index 60e21761f..0f0847c1c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -46,6 +46,7 @@ public final class JsonCommand { private final Long supportedEntityId; private final String transactionId; private final String url; + public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId, final String supportedEntityType, final Long supportedEntityId, @@ -64,8 +65,7 @@ public final class JsonCommand { public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId, - final String supportedEntityType, final Long supportedEntityId, final String transactionId, - final String url) { + final String supportedEntityType, final Long supportedEntityId, final String transactionId, final String url) { this.commandId = commandId; this.jsonCommand = jsonCommand; this.parsedCommand = parsedCommand; @@ -148,9 +148,9 @@ public final class JsonCommand { public String getTransactionId() { return this.transactionId; } - + public String getUrl() { - return this.url; + return this.url; } private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) { @@ -187,10 +187,10 @@ public final class JsonCommand { boolean differenceExists = false; if (baseValue != null) { - if(workingCopyValue != null) { - differenceExists = !baseValue.equals(workingCopyValue); + if (workingCopyValue != null) { + differenceExists = !baseValue.equals(workingCopyValue); } else { - differenceExists = true; + differenceExists = true; } } else { differenceExists = workingCopyValue != null; @@ -203,11 +203,11 @@ public final class JsonCommand { boolean differenceExists = false; if (baseValue != null) { - if(workingCopyValue != null) { - differenceExists = baseValue.compareTo(workingCopyValue) != 0; - }else { - differenceExists = true; - } + if (workingCopyValue != null) { + differenceExists = baseValue.compareTo(workingCopyValue) != 0; + } else { + differenceExists = true; + } } else { differenceExists = workingCopyValue != null; } @@ -301,6 +301,15 @@ public final class JsonCommand { return StringUtils.defaultIfEmpty(value, ""); } + public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final BigDecimal workingValue = bigDecimalValueOfParameterNamedDefaultToNullIfZero(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + public boolean isChangeInBigDecimalParameterNamed(final String parameterName, final BigDecimal existingValue) { boolean isChanged = false; if (parameterExists(parameterName)) { @@ -309,24 +318,53 @@ public final class JsonCommand { } return isChanged; } - + public boolean isChangeInBigDecimalParameterNamedWithNullCheck(final String parameterName, final BigDecimal existingValue) { boolean isChanged = false; if (parameterExists(parameterName)) { final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName); - if(workingValue == null && existingValue != null){ + if (workingValue == null && existingValue != null) { isChanged = true; - }else{ + } else { isChanged = differenceExists(existingValue, workingValue); } } return isChanged; } + private static BigDecimal defaultToNullIfZero(final BigDecimal value) { + BigDecimal result = value; + if (value != null && BigDecimal.ZERO.compareTo(value) == 0) { + result = null; + } + return result; + } + + private static Integer defaultToNullIfZero(final Integer value) { + Integer result = value; + if (value != null && value == 0) { + result = null; + } + return result; + } + public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) { return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, parsedCommand); } + public BigDecimal bigDecimalValueOfParameterNamedDefaultToNullIfZero(final String parameterName) { + return defaultToNullIfZero(bigDecimalValueOfParameterNamed(parameterName)); + } + + public boolean isChangeInIntegerParameterNamedDefaultingZeroToNull(final String parameterName, final Integer existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final Integer workingValue = integerValueOfParameterNamedDefaultToNullIfZero(parameterName); + isChanged = differenceExists(existingValue, workingValue); + } + return isChanged; + } + public boolean isChangeInIntegerParameterNamed(final String parameterName, final Integer existingValue) { boolean isChanged = false; if (parameterExists(parameterName)) { @@ -340,19 +378,23 @@ public final class JsonCommand { boolean isChanged = false; if (parameterExists(parameterName)) { final Integer workingValue = integerValueOfParameterNamed(parameterName); - if(workingValue == null && existingValue != null){ + if (workingValue == null && existingValue != null) { isChanged = true; - }else{ + } else { isChanged = differenceExists(existingValue, workingValue); } } return isChanged; } - + public Integer integerValueOfParameterNamed(final String parameterName) { return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, parsedCommand); } + public Integer integerValueOfParameterNamedDefaultToNullIfZero(final String parameterName) { + return defaultToNullIfZero(integerValueOfParameterNamed(parameterName)); + } + public boolean isChangeInIntegerSansLocaleParameterNamed(final String parameterName, final Integer existingValue) { boolean isChanged = false; if (parameterExists(parameterName)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 9f914c8f8..54ef17e91 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -209,25 +209,24 @@ public class DataValidatorBuilder { public DataValidatorBuilder isOneOfTheseValues(final Object... values) { if (value == null && ignoreNullValue) { return this; } - if (value != null) { - List valuesList = Arrays.asList(values); - String valuesListStr = StringUtils.join(valuesList, ", "); + List valuesList = Arrays.asList(values); + String valuesListStr = StringUtils.join(valuesList, ", "); - 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(valuesListStr).append(" ] ").append("."); + if (this.value == null || !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(valuesListStr).append(" ] ").append("."); - ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), - defaultEnglishMessage.toString(), parameter, this.value, values); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), + parameter, this.value, values); - dataValidationErrors.add(error); - } + dataValidationErrors.add(error); } + return this; } - + public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) { if (value == null && ignoreNullValue) { return this; } @@ -238,8 +237,8 @@ public class DataValidatorBuilder { if (valuesList.contains(this.value)) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".is.one.of.unwanted.enumerations"); - StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must not be any of [ ") - .append(valuesListStr).append(" ] ").append("."); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter) + .append(" must not be any of [ ").append(valuesListStr).append(" ] ").append("."); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, this.value, values); @@ -268,6 +267,9 @@ public class DataValidatorBuilder { return this; } + /* + * should be used with .notNull() before it + */ public DataValidatorBuilder zeroOrPositiveAmount() { if (value == null && ignoreNullValue) { return this; } @@ -286,6 +288,30 @@ public class DataValidatorBuilder { return this; } + /* + * should be used with .notNull() before it + */ + public DataValidatorBuilder integerZeroOrGreater() { + if (value == null && ignoreNullValue) { return this; } + + if (value != null) { + Integer number = Integer.valueOf(value.toString()); + if (number < 0) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".not.zero.or.greater"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append( + " must be zero or greater."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, number, 0); + dataValidationErrors.add(error); + } + } + return this; + } + + /* + * should be used with .notNull() before it + */ public DataValidatorBuilder integerGreaterThanZero() { if (value == null && ignoreNullValue) { return this; } @@ -304,6 +330,9 @@ public class DataValidatorBuilder { return this; } + /* + * should be used with .notNull() before it + */ public DataValidatorBuilder longGreaterThanZero() { if (value == null && ignoreNullValue) { return this; } @@ -337,11 +366,11 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder jsonArrayNotEmpty() { if (value == null && ignoreNullValue) { return this; } - JsonArray array = (JsonArray)value; + JsonArray array = (JsonArray) value; if (value != null && !array.iterator().hasNext()) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".cannot.be.empty"); @@ -427,14 +456,14 @@ public class DataValidatorBuilder { dataValidationErrors.add(error); return this; } - + public DataValidatorBuilder cantBeBlankWhenParameterProvidedIs(final String parameterName, final Object parameterValue) { if (value != null && StringUtils.isNotBlank(value.toString())) { return this; } StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".must.be.provided.when.").append(parameterName).append(".is.").append(parameterValue); - StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter) - .append(" must be provided when ").append(parameterName).append(" is ").append(parameterValue); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be provided when ") + .append(parameterName).append(" is ").append(parameterValue); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, value, parameterName, parameterValue); dataValidationErrors.add(error); @@ -457,12 +486,12 @@ public class DataValidatorBuilder { } public DataValidatorBuilder inMinAndMaxAmountRange(final BigDecimal minimumAmount, final BigDecimal maximumAmount) { - if (minimumAmount != null && maximumAmount != null && value != null){ + if (minimumAmount != null && maximumAmount != null && value != null) { BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString())); if (amount.compareTo(minimumAmount) == -1 || amount.compareTo(maximumAmount) == 1) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".amount.is.not.within.min.max.range"); - StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" amount ").append(amount) + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter).append(" amount ").append(amount) .append(" must be between ").append(minimumAmount).append(" and ").append(maximumAmount).append(" ."); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, amount, minimumAmount, maximumAmount); @@ -472,14 +501,14 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder notLessThanMin(final BigDecimal min) { - if (min != null && value != null){ + if (min != null && value != null) { BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString())); if (amount.compareTo(min) == -1) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".is.less.than.min"); - StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" value ").append(amount) + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter).append(" value ").append(amount) .append(" must not be less than minimum value ").append(min); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, amount, min); @@ -489,14 +518,14 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder notGreaterThanMax(final BigDecimal max) { - if (max != null && value != null){ + if (max != null && value != null) { BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString())); if (amount.compareTo(max) == 1) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".is.greater.than.max"); - StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" value ").append(amount) + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter).append(" value ").append(amount) .append(" must not be more than maximum value ").append(max); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, amount, max); @@ -541,7 +570,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder notLessThanMin(final Integer min) { if (value == null && ignoreNullValue) { return this; } @@ -559,7 +588,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder notGreaterThanMax(final Integer max) { if (value == null && ignoreNullValue) { return this; } @@ -577,26 +606,25 @@ public class DataValidatorBuilder { } return this; } - - public DataValidatorBuilder matchesRegularExpression(final String expression) { - if (value == null && ignoreNullValue) { return this; } - if (value != null && !value.toString().matches(expression)) { - StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + public DataValidatorBuilder matchesRegularExpression(final String expression) { + if (value == null && ignoreNullValue) { return this; } + + if (value != null && !value.toString().matches(expression)) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) .append(".does.not.match.regexp"); StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter) - .append(" must match the provided regular expression [ ") - .append(expression).append(" ] ").append("."); + .append(" must match the provided regular expression [ ").append(expression).append(" ] ").append("."); - ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), - defaultEnglishMessage.toString(), parameter, this.value, expression); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), + parameter, this.value, expression); dataValidationErrors.add(error); } - - return this; + + return this; } - + private DataValidatorBuilder validateStringFor(final String validInputs) { if (this.value == null && this.ignoreNullValue) { return this; } final String[] inputs = validInputs.split(VALID_INPUT_SEPERATOR); @@ -610,15 +638,15 @@ public class DataValidatorBuilder { if (validationErr) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".value.should.true.or.false"); - final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter) - .append(" value should true or false "); + final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append( + " value should true or false "); final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), this.parameter, this.value); this.dataValidationErrors.add(error); } return this; } - + public DataValidatorBuilder validateForBooleanValue() { return validateStringFor("TRUE" + VALID_INPUT_SEPERATOR + "FALSE"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/domain/LocalDateInterval.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/domain/LocalDateInterval.java index 80dd741ff..dc5617906 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/domain/LocalDateInterval.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/domain/LocalDateInterval.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.infrastructure.core.domain; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -38,6 +40,14 @@ public class LocalDateInterval { return Days.daysBetween(this.startDate, this.endDate).getDays(); } + public boolean containsPortionOf(final LocalDateInterval interval) { + return contains(interval.startDate) || contains(interval.endDate); + } + + public boolean contains(final LocalDateInterval interval) { + return contains(interval.startDate) && contains(interval.endDate); + } + public boolean contains(final LocalDate target) { return isBetweenInclusive(this.startDate, this.endDate, target); } @@ -49,4 +59,9 @@ public class LocalDateInterval { public boolean fallsBefore(final LocalDate dateToCheck) { return this.endDate.isBefore(dateToCheck); } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientDataValidator.java index 5f1afbb99..733a27a2a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientDataValidator.java @@ -5,7 +5,7 @@ */ package org.mifosplatform.portfolio.client.data; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.activationDateParamName; import java.lang.reflect.Type; import java.util.ArrayList; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java index e74aefb44..c1c958095 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java @@ -452,7 +452,7 @@ public final class Client extends AbstractPersistable { this.staff = null; } - public void assignStaff(Staff staff) { + public void assignStaff(final Staff staff) { this.staff = staff; } @@ -466,4 +466,8 @@ public final class Client extends AbstractPersistable { public Integer getStatus() { return status; } + + public boolean isActivatedAfter(final LocalDate submittedOn) { + return getActivationLocalDate().isAfter(submittedOn); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java index 5674c300a..5270fedcf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java @@ -179,6 +179,10 @@ public final class Group extends AbstractPersistable { this.status = GroupingTypeStatus.ACTIVE.getValue(); } + public boolean isActivatedAfter(final LocalDate submittedOn) { + return getActivationLocalDate().isAfter(submittedOn); + } + public boolean isNotActive() { return !isActive(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 5362973de..aae141811 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -137,9 +137,9 @@ public class Loan extends AbstractPersistable { @Column(name = "loan_status_id", nullable = false) private Integer loanStatus; - @Column(name = "sync_disbursement_with_meeting", nullable = true) - private Boolean syncDisbursementWithMeeting; - + @Column(name = "sync_disbursement_with_meeting", nullable = true) + private Boolean syncDisbursementWithMeeting; + // loan application states @Temporal(TemporalType.DATE) @Column(name = "submittedon_date") @@ -231,10 +231,10 @@ public class Loan extends AbstractPersistable { @SuppressWarnings("unused") @Column(name = "total_overpaid_derived", scale = 6, precision = 19) private BigDecimal totalOverpaid; - + @Column(name = "loan_counter") private Integer loanCounter; - + @Column(name = "loan_product_counter") private Integer loanProductCounter; @@ -294,7 +294,7 @@ public class Loan extends AbstractPersistable { public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, - final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, + final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Boolean syncDisbursementWithMeeting) { final LoanStatus status = null; final CodeValue loanPurpose = null; @@ -307,7 +307,7 @@ public class Loan extends AbstractPersistable { public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, - final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, + final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Boolean syncDisbursementWithMeeting) { final LoanStatus status = null; final CodeValue loanPurpose = null; @@ -361,7 +361,7 @@ public class Loan extends AbstractPersistable { this.collateral = null; } this.loanOfficerHistory = null; - + this.syncDisbursementWithMeeting = syncDisbursementWithMeeting; } @@ -830,7 +830,7 @@ public class Loan extends AbstractPersistable { } // add clientId, groupId and loanType changes to actual changes - + final String clientIdParamName = "clientId"; final Long clientId = this.client == null ? null : this.client.getId(); if (command.isChangeInLongParameterNamed(clientIdParamName, clientId)) { @@ -932,11 +932,11 @@ public class Loan extends AbstractPersistable { final String syncDisbursementParameterName = "syncDisbursementWithMeeting"; if (command.isChangeInBooleanParameterNamed(syncDisbursementParameterName, isSyncDisbursementWithMeeting())) { - final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(syncDisbursementParameterName); - actualChanges.put(syncDisbursementParameterName, valueAsInput); - this.syncDisbursementWithMeeting = valueAsInput; + final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(syncDisbursementParameterName); + actualChanges.put(syncDisbursementParameterName, valueAsInput); + this.syncDisbursementWithMeeting = valueAsInput; } - + final String interestChargedFromDateParamName = "interestChargedFromDate"; if (command.isChangeInLocalDateParameterNamed(interestChargedFromDateParamName, getInterestChargedFromDate())) { final String valueAsInput = command.stringValueOfParameterNamed(interestChargedFromDateParamName); @@ -1003,14 +1003,14 @@ public class Loan extends AbstractPersistable { actualChanges.put(collateralParamName, listOfLoanCollateralData(possiblyModifedLoanCollateralItems)); } } - + final String loanTermFrequencyParamName = "loanTermFrequency"; if (command.isChangeInIntegerParameterNamed(loanTermFrequencyParamName, this.termFrequency)) { final Integer newValue = command.integerValueOfParameterNamed(loanTermFrequencyParamName); actualChanges.put(externalIdParamName, newValue); this.termFrequency = newValue; } - + final String loanTermFrequencyTypeParamName = "loanTermFrequencyType"; if (command.isChangeInIntegerParameterNamed(loanTermFrequencyTypeParamName, this.termPeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(loanTermFrequencyTypeParamName); @@ -1100,18 +1100,12 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn); } - if (!(client == null)) { - if (submittedOn.isBefore(client.getActivationLocalDate())) { - final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; - throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, - submittedOn); - } - } else if (!(group == null)) { - if (submittedOn.isBefore(group.getActivationLocalDate())) { - final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; - throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, - submittedOn); - } + if (client != null && client.isActivatedAfter(submittedOn)) { + final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn); + } else if (group != null && group.isActivatedAfter(submittedOn)) { + final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date."; + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn); } if (submittedOn.isAfter(getExpectedDisbursedOnLocalDate())) { @@ -1379,14 +1373,17 @@ public class Loan extends AbstractPersistable { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); - //If ExpectedFirstRepaymentOnDate is null and repayment is synced with meeting then use firstRepaymentMeetingDate - //to generate repayment schedule dates - final LocalDate firstRepaymentOnDate = (getExpectedFirstRepaymentOnDate() == null) ? firstRepaymentMeetingDate : getExpectedFirstRepaymentOnDate(); + // If ExpectedFirstRepaymentOnDate is null and repayment is synced with + // meeting then use firstRepaymentMeetingDate + // to generate repayment schedule dates + final LocalDate firstRepaymentOnDate = (getExpectedFirstRepaymentOnDate() == null) ? firstRepaymentMeetingDate + : getExpectedFirstRepaymentOnDate(); LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), firstRepaymentOnDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail); - LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, isHolidayEnabled, holidays); + LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, + isHolidayEnabled, holidays); updateLoanSchedule(loanSchedule); } @@ -2348,34 +2345,37 @@ public class Loan extends AbstractPersistable { return this.closedOnDate; } - public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List holidays) { - //update repayment dates of repayment schedule installaments - LocalDate tmpFromDate = this.getDisbursementDate();//first repayment's from date is same as disbursement date. + public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, + final boolean isHolidayEnabled, final List holidays) { + // update repayment dates of repayment schedule installaments + LocalDate tmpFromDate = this.getDisbursementDate();// first repayment's + // from date is same + // as disbursement + // date. LocalDate newRepaymentDate = null; for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); - //FIXME: AA this won't update repayment dates before current date. + // FIXME: AA this won't update repayment dates before current date. if (oldDueDate.isAfter(meetingStartDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) { final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery(); // reset repayment date with new meeting date - newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, loanRepaymentInterval, frequency); - if(isHolidayEnabled){ + newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, + loanRepaymentInterval, frequency); + if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); - //reset from date to get actual daysInPeriod + // reset from date to get actual daysInPeriod loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); - tmpFromDate = newRepaymentDate;//update with new repayment date - }else{ + tmpFromDate = newRepaymentDate;// update with new repayment date + } else { tmpFromDate = oldDueDate; } - + } } - - public void applyHolidayToRepaymentScheduleDates(final Holiday holiday) { // first repayment's from date is same as disbursement date. @@ -2383,9 +2383,10 @@ public class Loan extends AbstractPersistable { // Loop through all loanRepayments for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); - - //update from date if it's not same as previous installament's due date. - if(!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)){ + + // update from date if it's not same as previous installament's due + // date. + if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) { loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); } if (oldDueDate.isAfter(holiday.getToDateLocalDate())) break; @@ -2412,11 +2413,11 @@ public class Loan extends AbstractPersistable { return this.loanProductCounter; } - public void updateClientLoanCounter(Integer newLoanCounter) { + public void updateClientLoanCounter(final Integer newLoanCounter) { this.loanCounter = newLoanCounter; } - public void updateLoanProductLoanCounter(Integer newLoanProductLoanCounter) { + public void updateLoanProductLoanCounter(final Integer newLoanProductLoanCounter) { this.loanProductCounter = newLoanProductLoanCounter; } @@ -2432,7 +2433,6 @@ public class Loan extends AbstractPersistable { return this.termFrequency; } - public Integer getTermPeriodFrequencyType() { return this.termPeriodFrequencyType; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java index 80a5f2b84..be307af2b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationApprovalCommandHandler implements NewCommandSourceHa @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.approveLoanApplication(command.entityId(), command); + return this.writePlatformService.approveApplication(command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java index 86cb5cb39..98b07e0c6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationApprovalUndoCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationApprovalUndoCommandHandler implements NewCommandSour @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.undoLoanApplicationApproval(command.entityId(), command); + return this.writePlatformService.undoApplicationApproval(command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java index c15ee9a25..4fa90a3af 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationDeletionCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationDeletionCommandHandler implements NewCommandSourceHa @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.deleteLoanApplication(command.entityId()); + return this.writePlatformService.deleteApplication(command.entityId()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java index f5233d4b2..d72902537 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationModificationCommandHandler implements NewCommandSour @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.modifyLoanApplication(command.getLoanId(), command); + return this.writePlatformService.modifyApplication(command.getLoanId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java index e97c87eb8..d8d8288e4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationRejectedCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationRejectedCommandHandler implements NewCommandSourceHa @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.rejectLoanApplication(command.entityId(), command); + return this.writePlatformService.rejectApplication(command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java index ecd8b7527..e01da326d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationSubmittalCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationSubmittalCommandHandler implements NewCommandSourceH @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.submitLoanApplication(command); + return this.writePlatformService.submitApplication(command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java index 839151645..1f6a66f9a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationWithdrawnByApplicantCommandHandler.java @@ -27,6 +27,6 @@ public class LoanApplicationWithdrawnByApplicantCommandHandler implements NewCom @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.applicantWithdrawsFromLoanApplication(command.entityId(), command); + return this.writePlatformService.applicantWithdrawsFromApplication(command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 8689647e5..e66cbc7ce 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -647,8 +647,7 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000); } - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", - "Validation errors exist.", dataValidationErrors); } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java index 0151ac5bc..2952ce968 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformService.java @@ -10,17 +10,17 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; public interface LoanApplicationWritePlatformService { - CommandProcessingResult submitLoanApplication(JsonCommand command); + CommandProcessingResult submitApplication(JsonCommand command); - CommandProcessingResult modifyLoanApplication(Long loanId, JsonCommand command); + CommandProcessingResult modifyApplication(Long loanId, JsonCommand command); - CommandProcessingResult deleteLoanApplication(Long loanId); + CommandProcessingResult deleteApplication(Long loanId); - CommandProcessingResult approveLoanApplication(Long loanId, JsonCommand command); + CommandProcessingResult approveApplication(Long loanId, JsonCommand command); - CommandProcessingResult undoLoanApplicationApproval(Long loanId, JsonCommand command); + CommandProcessingResult undoApplicationApproval(Long loanId, JsonCommand command); - CommandProcessingResult rejectLoanApplication(Long loanId, JsonCommand command); + CommandProcessingResult rejectApplication(Long loanId, JsonCommand command); - CommandProcessingResult applicantWithdrawsFromLoanApplication(Long loanId, JsonCommand command); + CommandProcessingResult applicantWithdrawsFromApplication(Long loanId, JsonCommand command); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index b702d7fd2..5c07639bd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -138,7 +138,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult submitLoanApplication(final JsonCommand command) { + public CommandProcessingResult submitApplication(final JsonCommand command) { final AppUser currentUser = context.authenticatedUser(); @@ -205,7 +205,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult modifyLoanApplication(final Long loanId, final JsonCommand command) { + public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) { try { context.authenticatedUser(); @@ -384,7 +384,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult deleteLoanApplication(final Long loanId) { + public CommandProcessingResult deleteApplication(final Long loanId) { context.authenticatedUser(); @@ -408,7 +408,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult approveLoanApplication(final Long loanId, final JsonCommand command) { + public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) { final AppUser currentUser = context.authenticatedUser(); @@ -441,7 +441,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult undoLoanApplicationApproval(final Long loanId, final JsonCommand command) { + public CommandProcessingResult undoApplicationApproval(final Long loanId, final JsonCommand command) { context.authenticatedUser(); @@ -473,7 +473,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult rejectLoanApplication(final Long loanId, final JsonCommand command) { + public CommandProcessingResult rejectApplication(final Long loanId, final JsonCommand command) { final AppUser currentUser = context.authenticatedUser(); @@ -505,7 +505,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override - public CommandProcessingResult applicantWithdrawsFromLoanApplication(final Long loanId, final JsonCommand command) { + public CommandProcessingResult applicantWithdrawsFromApplication(final Long loanId, final JsonCommand command) { final AppUser currentUser = context.authenticatedUser(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java index 6f6242e94..8e449d281 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java @@ -21,6 +21,7 @@ import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.useradministration.domain.AppUser; @Entity @@ -53,10 +54,10 @@ public class Note extends AbstractAuditableCustom { @Column(name = "note_type_enum") private final Integer noteTypeId; - // @SuppressWarnings("unused") - // @ManyToOne - // @JoinColumn(name = "saving_account_id", nullable = true) - // private SavingAccount savingAccount; + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "savings_account_id", nullable = true) + private SavingsAccount savingsAccount; public static Note clientNoteFromJson(final Client client, final JsonCommand command) { final String note = command.stringValueOfParameterNamed("note"); @@ -76,10 +77,9 @@ public class Note extends AbstractAuditableCustom { return new Note(loan, loanTransaction, note); } - // public static Note savingNote(final SavingAccount account, final String - // note) { - // return new Note(account, note); - // } + public static Note savingNote(final SavingsAccount account, final String note) { + return new Note(account, note); + } private Note(final Client client, final String note) { this.client = client; @@ -118,12 +118,12 @@ public class Note extends AbstractAuditableCustom { this.noteTypeId = null; } - // public Note(final SavingAccount account, final String note) { - // this.savingAccount = account; - // this.client = account.getClient(); - // this.note = note; - // this.noteTypeId = NoteType.SAVING_ACCOUNT.getValue(); - // } + public Note(final SavingsAccount account, final String note) { + this.savingsAccount = account; + this.client = account.getClient(); + this.note = note; + this.noteTypeId = NoteType.SAVING_ACCOUNT.getValue(); + } public Map update(final JsonCommand command) { final Map actualChanges = new LinkedHashMap(7); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/NoteRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/NoteRepository.java index 9d4a628f4..d9deec1a6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/NoteRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/NoteRepository.java @@ -26,7 +26,7 @@ public interface NoteRepository extends JpaRepository, JpaSpecificat Note findByLoanTransactionIdAndId(Long loanTransactionId, Long id); - // Note findByDepositAccountIdAndId(Long depositAccountId, Long id); + List findBySavingsAccountId(Long savingAccountId); - // Note findBySavingAccountIdAndId(Long savingAccountId, Long id); + // Note findBySavingsAccountIdAndId(Long savingAccountId, Long id); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java similarity index 96% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java index 56ac47929..fb22b500a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsAccountTransactionType.java @@ -3,7 +3,9 @@ * 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; +package org.mifosplatform.portfolio.savings; + +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; /** * An enumeration of different transactions that can occur on a diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java similarity index 93% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index f49d0d677..6fd7be0b2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -3,7 +3,7 @@ * 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.api; +package org.mifosplatform.portfolio.savings; import java.util.Arrays; import java.util.HashSet; @@ -32,8 +32,10 @@ public class SavingsApiConstants { public static final String clientIdParamName = "clientId"; public static final String groupIdParamName = "groupId"; public static final String productIdParamName = "productId"; + public static final String fieldOfficerIdParamName = "fieldOfficerId"; + public static final String submittedOnDateParamName = "submittedOnDate"; public static final String activeParamName = "active"; - public static final String activationDateParamName = "activationDate"; + public static final String activationDateParamName = "activatedOnDate"; public static final String nameParamName = "name"; public static final String descriptionParamName = "description"; public static final String currencyCodeParamName = "currencyCode"; @@ -52,7 +54,7 @@ public class SavingsApiConstants { public static final String annualFeeOnMonthDayParamName = "annualFeeOnMonthDay"; public static final String accountingRuleParamName = "accountingRule"; public static final String paymentTypeIdParamName = "paymentTypeId"; - public static final String accountNumberParamName = "accountNumber"; + public static final String transactionAccountNumberParamName = "accountNumber"; public static final String checkNumberParamName = "checkNumber"; public static final String routingCodeParamName = "routingCode"; public static final String receiptNumberParamName = "receiptNumber"; @@ -89,7 +91,7 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, - productIdParamName, activeParamName, activationDateParamName, nominalAnnualInterestRateParamName, + productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, annualFeeAmountParamName, @@ -112,7 +114,7 @@ public class SavingsApiConstants { public static final Set SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName, - accountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); + transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); public static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundingInterestPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java similarity index 53% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundingInterestPeriodType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java index 20fd37504..42843f5a8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundingInterestPeriodType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsCompoundingInterestPeriodType.java @@ -3,7 +3,11 @@ * 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; +package org.mifosplatform.portfolio.savings; + +import java.util.ArrayList; +import java.util.List; + /** *

@@ -15,13 +19,13 @@ public enum SavingsCompoundingInterestPeriodType { INVALID(0, "savingsCompoundingInterestPeriodType.invalid"), // DAILY(1, "savingsCompoundingInterestPeriodType.daily"), // - WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), // - BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), // - MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"), // - QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), // - BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), // - ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"), // - NO_COMPOUNDING_SIMPLE_INTEREST(8, "savingsCompoundingInterestPeriodType.nocompounding"); +// WEEKLY(2, "savingsCompoundingInterestPeriodType.weekly"), // +// BIWEEKLY(3, "savingsCompoundingInterestPeriodType.biweekly"), // + MONTHLY(4, "savingsCompoundingInterestPeriodType.monthly"); +// QUATERLY(5, "savingsCompoundingInterestPeriodType.quarterly"), // +// BI_ANNUAL(6, "savingsCompoundingInterestPeriodType.biannual"), // +// ANNUAL(7, "savingsCompoundingInterestPeriodType.annual"), // +// NO_COMPOUNDING_SIMPLE_INTEREST(8, "savingsCompoundingInterestPeriodType.nocompounding"); private final Integer value; private final String code; @@ -39,6 +43,15 @@ public enum SavingsCompoundingInterestPeriodType { return code; } + public static Object[] integerValues() { + List values = new ArrayList(); + for (SavingsCompoundingInterestPeriodType enumType : values()) { + values.add(enumType.getValue()); + } + + return values.toArray(); + } + public static SavingsCompoundingInterestPeriodType fromInt(final Integer type) { SavingsCompoundingInterestPeriodType repaymentFrequencyType = SavingsCompoundingInterestPeriodType.INVALID; if (type != null) { @@ -47,28 +60,34 @@ public enum SavingsCompoundingInterestPeriodType { repaymentFrequencyType = SavingsCompoundingInterestPeriodType.DAILY; break; case 2: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.WEEKLY; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.WEEKLY; break; case 3: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.BIWEEKLY; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.BIWEEKLY; break; case 4: repaymentFrequencyType = SavingsCompoundingInterestPeriodType.MONTHLY; break; case 5: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.QUATERLY; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.QUATERLY; break; case 6: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.BI_ANNUAL; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.BI_ANNUAL; break; case 7: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.ANNUAL; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.ANNUAL; break; case 8: - repaymentFrequencyType = SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST; + // repaymentFrequencyType = + // SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST; break; } } return repaymentFrequencyType; } -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationDaysInYearType.java similarity index 96% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationDaysInYearType.java index 9ad691fa1..7e54f07bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationDaysInYearType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationDaysInYearType.java @@ -3,7 +3,7 @@ * 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; +package org.mifosplatform.portfolio.savings; /** *

    diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationType.java similarity index 97% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationType.java index c060a9818..20e337784 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculationType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsInterestCalculationType.java @@ -3,7 +3,7 @@ * 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; +package org.mifosplatform.portfolio.savings; /** *
      diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPeriodFrequencyType.java similarity index 97% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPeriodFrequencyType.java index 5397729f4..785a85049 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsPeriodFrequencyType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPeriodFrequencyType.java @@ -3,7 +3,7 @@ * 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; +package org.mifosplatform.portfolio.savings; /** * An enumeration of supported calendar periods used in savings. diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java new file mode 100644 index 000000000..fc48d091c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsPostingInterestPeriodType.java @@ -0,0 +1,52 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings; + +/** + * The interest posting period is the span of time at the end of which savings + * earned but not yet credited/posted in a client's account is credited/posted. + */ +public enum SavingsPostingInterestPeriodType { + + INVALID(0, "savingsPostingInterestPeriodType.invalid"), // + MONTHLY(4, "savingsPostingInterestPeriodType.monthly"), // + QUATERLY(5, "savingsPostingInterestPeriodType.quarterly"), // + ANNUAL(7, "savingsPostingInterestPeriodType.annual"); + + private final Integer value; + private final String code; + + private SavingsPostingInterestPeriodType(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 SavingsPostingInterestPeriodType fromInt(final Integer type) { + SavingsPostingInterestPeriodType repaymentFrequencyType = SavingsPostingInterestPeriodType.INVALID; + if (type != null) { + switch (type) { + case 4: + repaymentFrequencyType = SavingsPostingInterestPeriodType.MONTHLY; + break; + case 5: + repaymentFrequencyType = SavingsPostingInterestPeriodType.QUATERLY; + break; + case 7: + repaymentFrequencyType = SavingsPostingInterestPeriodType.ANNUAL; + break; + } + } + return repaymentFrequencyType; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsWithdrawalFeesType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsWithdrawalFeesType.java similarity index 95% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsWithdrawalFeesType.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsWithdrawalFeesType.java index 3811dfc43..1a585f2a3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsWithdrawalFeesType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsWithdrawalFeesType.java @@ -3,7 +3,7 @@ * 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; +package org.mifosplatform.portfolio.savings; public enum SavingsWithdrawalFeesType { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index 00c2746d2..47ecd2bb1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -31,6 +31,7 @@ import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSeriali import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; @@ -72,7 +73,8 @@ public class SavingsAccountTransactionsApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String retrieveTemplate(@PathParam("savingsId") final Long savingsId, @QueryParam("command") final String commandParam, + public String retrieveTemplate(@PathParam("savingsId") final Long savingsId, + // @QueryParam("command") final String commandParam, @Context final UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java index fc48527f6..a82b536c0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountsApiResource.java @@ -12,6 +12,7 @@ import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -36,7 +37,9 @@ import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSeriali import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.portfolio.group.service.SearchParameters; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.data.SavingsProductData; @@ -81,12 +84,15 @@ public class SavingsAccountsApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String retrieveTemplate(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId, - @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) { + public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId, + @QueryParam("productId") final Long productId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @Context final UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - final SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveTemplate(clientId, groupId, productId); + final SavingsAccountData savingsAccount = this.savingsAccountReadPlatformService.retrieveTemplate(clientId, groupId, productId, + staffInSelectedOfficeOnly); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); @@ -113,7 +119,7 @@ public class SavingsAccountsApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String create(final String apiRequestBodyAsJson) { + public String submitApplication(final String apiRequestBodyAsJson) { final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccount().withJson(apiRequestBodyAsJson).build(); @@ -147,6 +153,7 @@ public class SavingsAccountsApiResource { Collection transactions = null; Collection productOptions = null; + Collection fieldOfficerOptions = null; Collection interestCompoundingPeriodTypeOptions = null; Collection interestCompoundingPostingTypeOptions = null; Collection interestCalculationTypeOptions = null; @@ -174,16 +181,49 @@ public class SavingsAccountsApiResource { final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); if (settings.isTemplate()) { productOptions = this.savingsProductReadPlatformService.retrieveAllForLookup(); + + // FIXME - FOR CHANGE SAVINGS ACCOUNT APPLICATION need to determine + // office id of savings account + // if (savingsAccount.officeId() != null) { + // + // if (staffInSelectedOfficeOnly) { + // // only bring back loan officers in selected branch/office + // Collection fieldOfficersInBranch = + // this.staffReadPlatformService + // .retrieveAllLoanOfficersInOfficeById(officeId); + // + // if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) { + // fieldOfficerOptions = new + // ArrayList(fieldOfficersInBranch); + // } + // } else { + // // by default bring back all officers in selected + // // branch/office as well as officers in office above + // // this office + // final boolean restrictToLoanOfficersOnly = true; + // Collection loanOfficersInHierarchy = + // this.staffReadPlatformService + // .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, + // restrictToLoanOfficersOnly); + // + // if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) { + // fieldOfficerOptions = new + // ArrayList(loanOfficersInHierarchy); + // } + // } + // } + interestCompoundingPeriodTypeOptions = this.dropdownReadPlatformService.retrieveCompoundingInterestPeriodTypeOptions(); interestCompoundingPostingTypeOptions = this.dropdownReadPlatformService.retrieveInterestPostingPeriodTypeOptions(); interestCalculationTypeOptions = this.dropdownReadPlatformService.retrieveInterestCalculationTypeOptions(); + interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService.retrieveInterestCalculationDaysInYearTypeOptions(); lockinPeriodFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLockinPeriodFrequencyTypeOptions(); withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions(); } - return SavingsAccountData.withTemplateOptions(savingsAccount, productOptions, interestCompoundingPeriodTypeOptions, - interestCompoundingPostingTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + return SavingsAccountData.withTemplateOptions(savingsAccount, productOptions, fieldOfficerOptions, + interestCompoundingPeriodTypeOptions, interestCompoundingPostingTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); } @PUT @@ -210,7 +250,19 @@ public class SavingsAccountsApiResource { final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); CommandProcessingResult result = null; - if (is(commandParam, "activate")) { + if (is(commandParam, "reject")) { + final CommandWrapper commandRequest = builder.rejectSavingsAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawnByApplicant")) { + final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "approve")) { + final CommandWrapper commandRequest = builder.approveSavingsAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "undoapproval")) { + final CommandWrapper commandRequest = builder.undoSavingsAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "activate")) { final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } else if (is(commandParam, "calculateInterest")) { @@ -223,8 +275,8 @@ public class SavingsAccountsApiResource { if (result == null) { // - throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "calculateInterest", - "postInterest" }); + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject", "withdrawnByApplicant", "approve", + "undoapproval", "activate", "calculateInterest", "postInterest" }); } return this.toApiJsonSerializer.serialize(result); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java index 828546f0e..ae3ed42a7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java @@ -42,11 +42,12 @@ 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.paymentdetail.PaymentDetailConstants; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; import org.mifosplatform.portfolio.savings.data.SavingsProductData; -import org.mifosplatform.portfolio.savings.domain.SavingsCompoundingInterestPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestPostingPeriodType; import org.mifosplatform.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.portfolio.savings.service.SavingsProductReadPlatformService; @@ -180,7 +181,7 @@ public class SavingsProductsApiResource { .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY); final EnumOptionData interestPostingPeriodType = SavingsEnumerations - .interestPostingPeriodType(SavingsInterestPostingPeriodType.MONTHLY); + .interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY); final EnumOptionData interestCalculationType = SavingsEnumerations .interestCalculationType(SavingsInterestCalculationType.DAILY_BALANCE); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountApplicationTimelineData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountApplicationTimelineData.java new file mode 100644 index 000000000..774b4b84d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountApplicationTimelineData.java @@ -0,0 +1,109 @@ +/** + * 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.data; + +import org.joda.time.LocalDate; + +/** + * Immutable data object represent the important time-line events of a savings + * account application. + */ +@SuppressWarnings("unused") +public class SavingsAccountApplicationTimelineData { + + private final LocalDate submittedOnDate; + private final String submittedByUsername; + private final String submittedByFirstname; + private final String submittedByLastname; + private final LocalDate rejectedOnDate; + private final String rejectedByUsername; + private final String rejectedByFirstname; + private final String rejectedByLastname; + private final LocalDate withdrawnOnDate; + private final String withdrawnByUsername; + private final String withdrawnByFirstname; + private final String withdrawnByLastname; + private final LocalDate approvedOnDate; + private final String approvedByUsername; + private final String approvedByFirstname; + private final String approvedByLastname; + private final LocalDate activatedOnDate; + private final String activatedByUsername; + private final String activatedByFirstname; + private final String activatedByLastname; + private final LocalDate closedOnDate; + private final String closedByUsername; + private final String closedByFirstname; + private final String closedByLastname; + + public static SavingsAccountApplicationTimelineData templateDefault() { + + final LocalDate submittedOnDate = null; + final String submittedByUsername = null; + final String submittedByFirstname = null; + final String submittedByLastname = null; + final LocalDate rejectedOnDate = null; + final String rejectedByUsername = null; + final String rejectedByFirstname = null; + final String rejectedByLastname = null; + final LocalDate withdrawnOnDate = null; + final String withdrawnByUsername = null; + final String withdrawnByFirstname = null; + final String withdrawnByLastname = null; + final LocalDate approvedOnDate = null; + final String approvedByUsername = null; + final String approvedByFirstname = null; + final String approvedByLastname = null; + final LocalDate activatedOnDate = null; + final String activatedByUsername = null; + final String activatedByFirstname = null; + final String activatedByLastname = null; + final LocalDate closedOnDate = null; + final String closedByUsername = null; + final String closedByFirstname = null; + final String closedByLastname = null; + + return new SavingsAccountApplicationTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname, + rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, + withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, + activatedOnDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, + closedByFirstname, closedByLastname); + } + + public SavingsAccountApplicationTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, + final String submittedByFirstname, final String submittedByLastname, final LocalDate rejectedOnDate, + final String rejectedByUsername, final String rejectedByFirstname, final String rejectedByLastname, + final LocalDate withdrawnOnDate, final String withdrawnByUsername, final String withdrawnByFirstname, + final String withdrawnByLastname, final LocalDate approvedOnDate, final String approvedByUsername, + final String approvedByFirstname, final String approvedByLastname, final LocalDate activatedOnDate, + final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname, + final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname, final String closedByLastname) { + this.submittedOnDate = submittedOnDate; + this.submittedByUsername = submittedByUsername; + this.submittedByFirstname = submittedByFirstname; + this.submittedByLastname = submittedByLastname; + this.rejectedOnDate = rejectedOnDate; + this.rejectedByUsername = rejectedByUsername; + this.rejectedByFirstname = rejectedByFirstname; + this.rejectedByLastname = rejectedByLastname; + this.withdrawnOnDate = withdrawnOnDate; + this.withdrawnByUsername = withdrawnByUsername; + this.withdrawnByFirstname = withdrawnByFirstname; + this.withdrawnByLastname = withdrawnByLastname; + this.approvedOnDate = approvedOnDate; + this.approvedByUsername = approvedByUsername; + this.approvedByFirstname = approvedByFirstname; + this.approvedByLastname = approvedByLastname; + this.activatedOnDate = activatedOnDate; + this.activatedByUsername = activatedByUsername; + this.activatedByFirstname = activatedByFirstname; + this.activatedByLastname = activatedByLastname; + this.closedOnDate = closedOnDate; + this.closedByUsername = closedByUsername; + this.closedByFirstname = closedByFirstname; + this.closedByLastname = closedByLastname; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java index abdd18455..fcd1e5a58 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountData.java @@ -12,6 +12,7 @@ import org.joda.time.LocalDate; import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.organisation.staff.data.StaffData; /** * Immutable data object representing a savings account. @@ -21,14 +22,16 @@ public class SavingsAccountData { private final Long id; private final String accountNo; private final String externalId; - private final SavingsAccountStatusEnumData status; - private final LocalDate activationDate; private final Long groupId; private final String groupName; private final Long clientId; private final String clientName; private final Long savingsProductId; private final String savingsProductName; + private final Long fieldofficerId; + private final String fieldofficerName; + private final SavingsAccountStatusEnumData status; + private final SavingsAccountApplicationTimelineData timeline; private final CurrencyData currency; private final BigDecimal nominalAnnualInterestRate; private final EnumOptionData interestCompoundingPeriodType; @@ -53,6 +56,8 @@ public class SavingsAccountData { @SuppressWarnings("unused") private final Collection productOptions; @SuppressWarnings("unused") + private final Collection fieldOfficerOptions; + @SuppressWarnings("unused") private final Collection interestCompoundingPeriodTypeOptions; @SuppressWarnings("unused") private final Collection interestPostingPeriodTypeOptions; @@ -65,17 +70,19 @@ public class SavingsAccountData { @SuppressWarnings("unused") private final Collection withdrawalFeeTypeOptions; - 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 interestCompoundingPeriodType, - final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, - final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, - final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary) { + public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId, + final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, + final Long fieldOfficerId, final String fieldOfficerName, final SavingsAccountStatusEnumData status, + final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal interestRate, + final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, + final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, + final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, + final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType, + final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final LocalDate annualFeeNextDueDate, + final SavingsAccountSummaryData summary) { final Collection productOptions = null; + final Collection fieldOfficerOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; final Collection interestPostingPeriodTypeOptions = null; final Collection interestCalculationTypeOptions = null; @@ -85,32 +92,34 @@ public class SavingsAccountData { final Collection transactions = null; - return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, - annualFeeNextDueDate, summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, - final Collection productOptions, final Collection interestCompoundingPeriodTypeOptions, + final Collection productOptions, final Collection fieldOfficerOptions, + final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection transactions) { - return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.status, account.activationDate, - account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, - account.savingsProductName, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, - account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, - account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, - account.withdrawalFeeAmount, account.withdrawalFeeType, account.annualFeeAmount, account.annualFeeOnMonthDay, - account.annualFeeNextDueDate, account.summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, + account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.fieldofficerId, + account.fieldofficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, + account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, + account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, + account.lockinPeriodFrequencyType, account.withdrawalFeeAmount, account.withdrawalFeeType, account.annualFeeAmount, + account.annualFeeOnMonthDay, account.annualFeeNextDueDate, account.summary, transactions, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -119,10 +128,12 @@ public class SavingsAccountData { final Long id = null; final String accountNo = null; final String externalId = null; - final SavingsAccountStatusEnumData status = null; - final LocalDate activationDate = null; final Long productId = null; final String productName = null; + final Long fieldOfficerId = null; + final String fieldOfficerName = null; + final SavingsAccountStatusEnumData status = null; + final SavingsAccountApplicationTimelineData timeline = null; final CurrencyData currency = null; final BigDecimal nominalAnnualInterestRate = null; final EnumOptionData interestPeriodType = null; @@ -142,6 +153,7 @@ public class SavingsAccountData { final Collection transactions = null; final Collection productOptions = null; + final Collection fieldOfficerOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; final Collection interestPostingPeriodTypeOptions = null; final Collection interestCalculationTypeOptions = null; @@ -149,25 +161,26 @@ public class SavingsAccountData { final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; - return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, - annualFeeNextDueDate, summary, transactions, productOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); } - 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 nominalAnnualInterestRate, - final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType, - final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, - final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, - final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final EnumOptionData withdrawalFeeType, - final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final LocalDate annualFeeNextDueDate, - final SavingsAccountSummaryData summary, final Collection transactions, - final Collection productOptions, final Collection interestCompoundingPeriodTypeOptions, + private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, + final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldofficerId, + final String fieldofficerName, final SavingsAccountStatusEnumData status, final SavingsAccountApplicationTimelineData timeline, + final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestPeriodType, + final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, + final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, + final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final LocalDate annualFeeNextDueDate, final SavingsAccountSummaryData summary, + final Collection transactions, final Collection productOptions, + final Collection fieldOfficerOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, @@ -175,14 +188,16 @@ public class SavingsAccountData { this.id = id; this.accountNo = accountNo; this.externalId = externalId; - this.status = status; - this.activationDate = activationDate; this.groupId = groupId; this.groupName = groupName; this.clientId = clientId; this.clientName = clientName; this.savingsProductId = productId; this.savingsProductName = productName; + this.fieldofficerId = fieldofficerId; + this.fieldofficerName = fieldofficerName; + this.status = status; + this.timeline = timeline; this.currency = currency; this.nominalAnnualInterestRate = nominalAnnualInterestRate; this.interestCompoundingPeriodType = interestPeriodType; @@ -202,6 +217,7 @@ public class SavingsAccountData { this.transactions = transactions; this.productOptions = productOptions; + this.fieldOfficerOptions = fieldOfficerOptions; this.interestCompoundingPeriodTypeOptions = interestCompoundingPeriodTypeOptions; this.interestPostingPeriodTypeOptions = interestPostingPeriodTypeOptions; this.interestCalculationTypeOptions = interestCalculationTypeOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java index 6ebc77d60..64ea723eb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java @@ -5,25 +5,24 @@ */ package org.mifosplatform.portfolio.savings.data; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNoParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.clientIdParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.externalIdParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.groupIdParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.productIdParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -39,7 +38,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -56,7 +55,7 @@ public class SavingsAccountDataValidator { this.fromApiJsonHelper = fromApiJsonHelper; } - public void validateForCreate(final String json) { + public void validateForSubmit(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -85,13 +84,9 @@ public class SavingsAccountDataValidator { final Long productId = fromApiJsonHelper.extractLongNamed(productIdParamName, element); baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero(); - final Boolean active = fromApiJsonHelper.extractBooleanNamed(activeParamName, element); - baseDataValidator.reset().parameter(activeParamName).value(active).notNull(); - - LocalDate activationDate = null; - if (active != null && active.booleanValue()) { - activationDate = fromApiJsonHelper.extractLocalDateNamed(activationDateParamName, element); - baseDataValidator.reset().parameter(activationDateParamName).value(activationDate).notNull(); + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); + if (submittedOnDate == null) { + baseDataValidator.reset().parameter(submittedOnDateParamName).value(submittedOnDate).notNull(); } if (fromApiJsonHelper.parameterExists(accountNoParamName, element)) { @@ -140,32 +135,32 @@ public class SavingsAccountDataValidator { if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); - baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).positiveAmount(); + baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull() - .positiveAmount(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater(); if (lockinPeriodFrequency != null) { final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed( lockinPeriodFrequencyTypeParamName, element); baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull() - .inMinMaxRange(1, 3); + .inMinMaxRange(0, 3); } } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) { final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(1, 3); + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3); if (lockinPeriodFrequencyType != null) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull().positiveAmount(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull() + .integerZeroOrGreater(); } } @@ -173,17 +168,30 @@ public class SavingsAccountDataValidator { final BigDecimal withdrawalFeeAmount = fromApiJsonHelper .extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).positiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).zeroOrPositiveAmount(); + + if (withdrawalFeeAmount != null) { + final Integer withdrawalFeeType = fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); + } } if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { final Integer withdrawalFeeType = fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() + .isOneOfTheseValues(1, 2); + + if (withdrawalFeeType != null) { + final BigDecimal withdrawalFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, + element); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull() + .zeroOrPositiveAmount(); + } } if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).positiveAmount(); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount(); if (annualFeeAmount != null) { MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); @@ -191,6 +199,15 @@ public class SavingsAccountDataValidator { } } + if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { + + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + if (monthDayOfAnnualFee != null) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount(); + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -293,50 +310,38 @@ public class SavingsAccountDataValidator { if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); - baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).positiveAmount(); + baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull() + .zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { - final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull() - .positiveAmount(); - - if (lockinPeriodFrequency != null) { - final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed( - lockinPeriodFrequencyTypeParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull() - .inMinMaxRange(1, 3); - } + .integerZeroOrGreater(); } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) { final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(1, 3); - - if (lockinPeriodFrequencyType != null) { - final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, - element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull().positiveAmount(); - } + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3); } if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { - final BigDecimal withdrawalFeeAmount = fromApiJsonHelper .extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull().positiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull() + .zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { final Integer withdrawalFeeType = fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() + .isOneOfTheseValues(1, 2); } if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().positiveAmount(); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java index 9379d10d5..6318639b6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java @@ -16,22 +16,31 @@ public class SavingsAccountStatusEnumData { @SuppressWarnings("unused") private final String value; @SuppressWarnings("unused") - private final boolean unactivated; + private final boolean submittedAndPendingApproval; + @SuppressWarnings("unused") + private final boolean approved; + @SuppressWarnings("unused") + private final boolean rejected; + @SuppressWarnings("unused") + private final boolean withdrawnByApplicant; @SuppressWarnings("unused") private final boolean active; @SuppressWarnings("unused") private final boolean closed; - public SavingsAccountStatusEnumData(final Long id, final String code, final String value, final boolean unactivated, - final boolean active, final boolean closed) { + public SavingsAccountStatusEnumData(final Long id, final String code, final String value, final boolean submittedAndPendingApproval, + final boolean approved, final boolean rejected, final boolean withdrawnByApplicant, final boolean active, final boolean closed) { this.id = id; this.code = code; this.value = value; - this.unactivated = unactivated; + this.submittedAndPendingApproval = submittedAndPendingApproval; + this.approved = approved; + this.rejected = rejected; + this.withdrawnByApplicant = withdrawnByApplicant; this.active = active; this.closed = closed; } - + public Long id() { return this.id; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index e47482e48..5f2eae32f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -5,15 +5,15 @@ */ package org.mifosplatform.portfolio.savings.data; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNumberParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.bankNumberParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.checkNumberParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.paymentTypeIdParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.receiptNumberParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.routingCodeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.activationDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.bankNumberParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.checkNumberParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.paymentTypeIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.receiptNumberParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.routingCodeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.transactionAccountNumberParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.transactionAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.transactionDateParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -32,7 +32,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -74,7 +74,7 @@ public class SavingsAccountTransactionDataValidator { // Validate all string payment detail fields for max length final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element); baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero(); - final Set paymentDetailParameters = new HashSet(Arrays.asList(accountNumberParamName, checkNumberParamName, + final Set paymentDetailParameters = new HashSet(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); for (String paymentDetailParameterName : paymentDetailParameters) { final String paymentDetailParameterValue = fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java index 377b68f0f..e429031c7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -5,7 +5,7 @@ */ package org.mifosplatform.portfolio.savings.data; -import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType; +import org.mifosplatform.portfolio.savings.SavingsAccountTransactionType; /** * Immutable data object represent savings account transaction type diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index a11ab282f..586722a67 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -5,24 +5,24 @@ */ package org.mifosplatform.portfolio.savings.data; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nameParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -40,6 +40,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -90,7 +91,7 @@ public class SavingsProductDataValidator { final Integer interestCompoundingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed( interestCompoundingPeriodTypeParamName, element); baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull() - .isOneOfTheseValues(new Object[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues()); final Integer interestPostingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPostingPeriodTypeParamName, element); @@ -109,14 +110,13 @@ public class SavingsProductDataValidator { if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element); - baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).positiveAmount(); + baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull() - .positiveAmount(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater(); if (lockinPeriodFrequency != null) { final Integer lockinPeriodFrequencyType = fromApiJsonHelper.extractIntegerSansLocaleNamed( @@ -134,24 +134,39 @@ public class SavingsProductDataValidator { if (lockinPeriodFrequencyType != null) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); - baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull().positiveAmount(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull() + .integerZeroOrGreater(); + } + } + + if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { + + final BigDecimal withdrawalFeeAmount = fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).zeroOrPositiveAmount(); + + if (withdrawalFeeAmount != null) { + final Integer withdrawalFeeType = fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); } } if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { final Integer withdrawalFeeType = fromApiJsonHelper.extractIntegerSansLocaleNamed(withdrawalFeeTypeParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).isOneOfTheseValues(1, 2); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() + .isOneOfTheseValues(1, 2); if (withdrawalFeeType != null) { final BigDecimal withdrawalFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull().positiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull() + .zeroOrPositiveAmount(); } } if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().positiveAmount(); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).zeroOrPositiveAmount(); if (annualFeeAmount != null) { MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); @@ -159,6 +174,15 @@ public class SavingsProductDataValidator { } } + if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { + + MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); + if (monthDayOfAnnualFee != null) { + final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().zeroOrPositiveAmount(); + } + } + // accounting related data validation final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); @@ -232,7 +256,7 @@ public class SavingsProductDataValidator { final Integer interestCompoundingPeriodType = fromApiJsonHelper.extractIntegerSansLocaleNamed( interestCompoundingPeriodTypeParamName, element); baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull() - .inMinMaxRange(1, 8); + .isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues()); } if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) { @@ -249,11 +273,17 @@ public class SavingsProductDataValidator { .notNull().isOneOfTheseValues(360, 365); } - if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { + if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) { + final BigDecimal minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, + element); + baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull() + .zeroOrPositiveAmount(); + } + if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) { final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element); baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull() - .positiveAmount(); + .integerZeroOrGreater(); } if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) { @@ -265,7 +295,8 @@ public class SavingsProductDataValidator { if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) { final BigDecimal withdrawalFeeAmount = fromApiJsonHelper .extractBigDecimalWithLocaleNamed(withdrawalFeeAmountParamName, element); - baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull().positiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(withdrawalFeeAmount).ignoreIfNull() + .zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { @@ -276,7 +307,7 @@ public class SavingsProductDataValidator { if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().positiveAmount(); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(annualFeeOnMonthDayParamName, element)) { @@ -319,9 +350,6 @@ public class SavingsProductDataValidator { /** * Validation for advanced accounting options - * - * @param baseDataValidator - * @param element */ private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { if (fromApiJsonHelper.parameterExists(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestCompoundingPeriodSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestCompoundingPeriodSummary.java deleted file mode 100644 index a21253e49..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/InterestCompoundingPeriodSummary.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; -import java.math.MathContext; - -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; -import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.organisation.monetary.domain.Money; - -public class InterestCompoundingPeriodSummary { - - private final LocalDateInterval periodInterval; - @SuppressWarnings("unused") - private final BigDecimal openingBalance; - private final BigDecimal closingBalance; - private final BigDecimal interestEarnedUnrounded; - private final BigDecimal cumulativeCompoundedInterestToDate; - - public static InterestCompoundingPeriodSummary create(final LocalDateInterval periodInterval, final BigDecimal openingBalance, - final BigDecimal closingBalance, final BigDecimal interestEarnedUnrounded, final BigDecimal cumulativeCompoundedInterestToDate) { - return new InterestCompoundingPeriodSummary(periodInterval, openingBalance, closingBalance, interestEarnedUnrounded, - cumulativeCompoundedInterestToDate); - } - - public InterestCompoundingPeriodSummary(final LocalDateInterval periodInterval, final BigDecimal openingBalance, - final BigDecimal closingBalance, final BigDecimal interestEarnedUnrounded, final BigDecimal cumulativeCompoundedInterestToDate) { - this.periodInterval = periodInterval; - this.openingBalance = openingBalance; - this.closingBalance = closingBalance; - this.interestEarnedUnrounded = interestEarnedUnrounded; - this.cumulativeCompoundedInterestToDate = cumulativeCompoundedInterestToDate; - } - - public Money closingBalance(final MonetaryCurrency currency) { - return Money.of(currency, this.closingBalance); - } - - public BigDecimal closingBalance() { - return this.closingBalance; - } - - public BigDecimal closingBalanceWithUnroundedInterest(final MathContext mc) { - return this.closingBalance.add(interestEarnedUnrounded, mc); - } - - public BigDecimal compoundedInterest() { - return cumulativeCompoundedInterestToDate; - } - - public BigDecimal interestUnrounded() { - return this.interestEarnedUnrounded; - } - - public boolean fallsBefore(final LocalDate dateToCheck) { - return this.periodInterval.fallsBefore(dateToCheck); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index b5ae0460f..f64be5009 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -5,15 +5,17 @@ */ package org.mifosplatform.portfolio.savings.domain; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.localeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -21,6 +23,7 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import javax.persistence.CascadeType; @@ -40,12 +43,13 @@ 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.MonthDay; +import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.service.DateUtils; @@ -53,15 +57,23 @@ import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; -import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; +import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.InvalidSavingsAccountStateTransitionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -69,8 +81,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @UniqueConstraint(columnNames = { "external_id" }, name = "sa_external_id_UNIQUE") }) public class SavingsAccount extends AbstractPersistable { - private final static Logger logger = LoggerFactory.getLogger(SavingsAccount.class); - @Column(name = "account_no", length = 20, unique = true, nullable = false) private String accountNumber; @@ -89,12 +99,70 @@ public class SavingsAccount extends AbstractPersistable { @JoinColumn(name = "product_id", nullable = false) private SavingsProduct product; + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "field_officer_id", nullable = true) + private Staff fieldOfficer; + @Column(name = "status_enum", nullable = false) private Integer status; @Temporal(TemporalType.DATE) - @Column(name = "activation_date", nullable = true) - private Date activationDate; + @Column(name = "submittedon_date", nullable = true) + private Date submittedOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "submittedon_userid", nullable = true) + private AppUser submittedBy; + + @SuppressWarnings("unused") + @Temporal(TemporalType.DATE) + @Column(name = "rejectedon_date") + private Date rejectedOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "rejectedon_userid", nullable = true) + private AppUser rejectedBy; + + @SuppressWarnings("unused") + @Temporal(TemporalType.DATE) + @Column(name = "withdrawnon_date") + private Date withdrawnOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "withdrawnon_userid", nullable = true) + private AppUser withdrawnBy; + + @Temporal(TemporalType.DATE) + @Column(name = "approvedon_date") + private Date approvedOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "approvedon_userid", nullable = true) + private AppUser approvedBy; + + @Temporal(TemporalType.DATE) + @Column(name = "activatedon_date", nullable = true) + private Date activatedOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "activatedon_userid", nullable = true) + private AppUser activatedBy; + + @SuppressWarnings("unused") + @Temporal(TemporalType.DATE) + @Column(name = "closedon_date") + private Date closedOnDate; + + @SuppressWarnings("unused") + @ManyToOne(optional = true) + @JoinColumn(name = "closedon_userid", nullable = true) + private AppUser closedBy; @Embedded private MonetaryCurrency currency; @@ -113,7 +181,7 @@ public class SavingsAccount extends AbstractPersistable { private Integer interestCompoundingPeriodType; /** - * A value from the {@link SavingsInterestPostingPeriodType} enumeration. + * A value from the {@link SavingsPostingInterestPeriodType} enumeration. */ @Column(name = "interest_posting_period_enum", nullable = false) private Integer interestPostingPeriodType; @@ -186,27 +254,26 @@ public class SavingsAccount extends AbstractPersistable { // } - public static SavingsAccount createNewAccount(final Client client, final Group group, final SavingsProduct product, - final String accountNo, final String externalId, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + public static SavingsAccount createNewApplicationForSubmittal(final Client client, final Group group, final SavingsProduct product, + final Staff fieldOfficer, final String accountNo, final String externalId, final LocalDate submittedOnDate, + final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) { - final SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED; - final LocalDate activationDate = null; - return new SavingsAccount(client, group, product, accountNo, externalId, status, activationDate, interestRate, + final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay); } - private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final String accountNo, - final String externalId, final SavingsAccountStatusType status, final LocalDate activationDate, + private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, + final String accountNo, final String externalId, final SavingsAccountStatusType status, final LocalDate submittedOnDate, final BigDecimal nominalAnnualInterestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, @@ -214,6 +281,7 @@ public class SavingsAccount extends AbstractPersistable { this.client = client; this.group = group; this.product = product; + this.fieldOfficer = fieldOfficer; if (StringUtils.isBlank(accountNo)) { this.accountNumber = new RandomPasswordGenerator(19).generate(); this.accountNumberRequiresAutoGeneration = true; @@ -223,9 +291,7 @@ public class SavingsAccount extends AbstractPersistable { this.currency = product.currency(); this.externalId = externalId; this.status = status.getValue(); - if (activationDate != null) { - this.activationDate = activationDate.toDate(); - } + this.submittedOnDate = submittedOnDate.toDate(); this.nominalAnnualInterestRate = nominalAnnualInterestRate; this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue(); this.interestPostingPeriodType = interestPostingPeriodType.getValue(); @@ -278,115 +344,41 @@ public class SavingsAccount extends AbstractPersistable { return SavingsAccountStatusType.fromInt(this.status).isActive(); } - public void activate(final DateTimeFormatter formatter, final LocalDate activationDate, final List existingTransactionIds, - final List existingReversedTransactionIds) { + public void postInterest(final SavingsHelper savingsHelper, final MathContext mc, final LocalDate interestPostingUpToDate, + final List existingTransactionIds, final List existingReversedTransactionIds) { - if (isActive()) { - final String defaultUserMessage = "Cannot activate account. Account is already active."; - final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.already.active", - defaultUserMessage, "activationDate", activationDate.toString(formatter)); - - final List dataValidationErrors = new ArrayList(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isDateInTheFuture(activationDate)) { - - final String defaultUserMessage = "Activation date cannot be in the future."; - final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future", - defaultUserMessage, "activationDate", activationDate); - - final List dataValidationErrors = new ArrayList(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if(this.client.getActivationLocalDate().isAfter(activationDate)) - { - final String errorMessage = "The date on which a savings account is activate cannot be before its client activated date: " - + this.client.getActivationLocalDate().toString(); - throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.client.activation.date", errorMessage, activationDate, - this.client.getActivationLocalDate()); - } - - this.activationDate = activationDate.toDate(); - this.status = SavingsAccountStatusType.ACTIVE.getValue(); - - this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); - - // auto enter deposit for minimum required opening balance when - // activating account. - final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); - if (minRequiredOpeningBalance.isGreaterThanZero()) { - /** - * Vishwas: TODO activate API should be modified to accept payment - * details (for entering minimum balance) - **/ - deposit(formatter, activationDate, minRequiredOpeningBalance.getAmount(), existingTransactionIds, - existingReversedTransactionIds, null); - } - } - - private Date calculateDateAccountIsLockedUntil(final LocalDate activationLocalDate) { - - Date lockedInUntilLocalDate = null; - final PeriodFrequencyType lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); - switch (lockinPeriodFrequencyType) { - case INVALID: - break; - case DAYS: - lockedInUntilLocalDate = activationLocalDate.plusDays(this.lockinPeriodFrequency).toDate(); - break; - case WEEKS: - lockedInUntilLocalDate = activationLocalDate.plusWeeks(this.lockinPeriodFrequency).toDate(); - break; - case MONTHS: - lockedInUntilLocalDate = activationLocalDate.plusMonths(this.lockinPeriodFrequency).toDate(); - break; - case YEARS: - lockedInUntilLocalDate = activationLocalDate.plusYears(this.lockinPeriodFrequency).toDate(); - break; - } - - return lockedInUntilLocalDate; - } - - public void postInterest(final LocalDate interestPostingUpToDate, final List existingTransactionIds, - final List existingReversedTransactionIds) { - - final SavingsInterestPostingPeriodType postingPeriodType = SavingsInterestPostingPeriodType.fromInt(this.interestPostingPeriodType); - final List postingLocalDates = determineInterestPostingDates(getActivationLocalDate(), interestPostingUpToDate, - postingPeriodType); - - final List compoundingPeriods = calculateInterest(interestPostingUpToDate); + final List postingPeriods = calculateInterestUsing(savingsHelper, mc, interestPostingUpToDate); Money interestPostedToDate = Money.zero(this.currency); boolean recalucateDailyBalanceDetails = false; existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - for (LocalDate postingDate : postingLocalDates) { - Money interestEarnedToBePostedForPeriod = findInterestEarnedToBePostedOn(interestPostedToDate, postingDate, compoundingPeriods); - interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); + for (PostingPeriod interestPostingPeriod : postingPeriods) { - SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(postingDate); - if (postingTransaction == null) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, postingDate, - interestEarnedToBePostedForPeriod); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } else { - boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); - if (correctionRequired) { - postingTransaction.reverse(); - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, postingDate, - interestEarnedToBePostedForPeriod); + final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); + final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); + + if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) { + + interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); + + SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + if (postingTransaction == null) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, + interestPostingTransactionDate, interestEarnedToBePostedForPeriod); this.transactions.add(newPostingTransaction); recalucateDailyBalanceDetails = true; + } else { + boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); + if (correctionRequired) { + postingTransaction.reverse(); + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, + interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } } } } @@ -419,52 +411,22 @@ public class SavingsAccount extends AbstractPersistable { return postingTransation; } - private Money findInterestEarnedToBePostedOn(final Money interestPostedToDate, final LocalDate postingDate, - final List compoundingPeriods) { - - Money interestToBePosted = Money.zero(this.currency); - - BigDecimal lastRelevantInterest = BigDecimal.ZERO; - for (InterestCompoundingPeriodSummary interestCompoundingPeriodSummary : compoundingPeriods) { - if (interestCompoundingPeriodSummary.fallsBefore(postingDate)) { - interestToBePosted = Money.of(this.currency, interestCompoundingPeriodSummary.compoundedInterest()); - lastRelevantInterest = interestCompoundingPeriodSummary.compoundedInterest(); - } - } - logger.info("Interest is: " + lastRelevantInterest + " :>> "); - - return interestToBePosted.minus(interestPostedToDate); - } - - private List determineInterestPostingDates(final LocalDate activationLocalDate, final LocalDate interestPostingUpToDate, - final SavingsInterestPostingPeriodType postingPeriodType) { - - List postingDates = new ArrayList(); - - LocalDate periodStartDate = activationLocalDate; - LocalDate periodEndDate = periodStartDate; - - while (!periodStartDate.isAfter(interestPostingUpToDate) && !periodEndDate.isAfter(interestPostingUpToDate)) { - - final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType); - if (!interestPostingLocalDate.isAfter(DateUtils.getLocalDateOfTenant())) { - postingDates.add(interestPostingLocalDate); - } - - periodEndDate = interestPostingLocalDate; - periodStartDate = interestPostingLocalDate; - } - - return postingDates; - } - /** * All interest calculation based on END-OF-DAY-BALANCE. * * Interest calculation is performed on-the-fly over all account * transactions. + * + * + * 1. Calculate Interest From Beginning Of Account 1a. determine the + * 'crediting' periods that exist for this savings acccount 1b. determine + * the 'compounding' periods that exist within each 'crediting' period + * calculate the amount of interest due at the end of each 'crediting' + * period check if an existing 'interest posting' transaction exists for + * date and matches the amount posted */ - public List calculateInterest(final LocalDate interestCalculationUpToDate) { + public List calculateInterestUsing(final SavingsHelper savingsHelper, final MathContext mc, + final LocalDate upToInterestCalculationDate) { // no openingBalance concept supported yet but probably will to allow // for migrations. @@ -474,239 +436,59 @@ public class SavingsAccount extends AbstractPersistable { // correct. recalculateDailyBalances(openingAccountBalance); - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); + // 1. default to calculate interest based on entire history OR + // 2. determine latest 'posting period' and find interest credited to + // that period + + // A generate list of EndOfDayBalances (not including interest postings) + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType .fromInt(this.interestCompoundingPeriodType); - final List interestCompoundingPeriods = determineInterestCompoundingPeriods(getActivationLocalDate(), - interestCalculationUpToDate, compoundingPeriodType); - // determine opening balance, daily interest rate to apply, interest - // due, interest to compound for each period - final List compoundingPeriods = determineInterestCompoundingPeriodSummaries( - openingAccountBalance, this.nominalAnnualInterestRate, daysInYearType, interestCompoundingPeriods); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); - this.summary.updateFromInterestPeriodSummaries(currency, compoundingPeriods); + List postingPeriodIntervals = savingsHelper.determineInterestPostingPeriods(getActivationLocalDate(), + upToInterestCalculationDate, postingPeriodType); - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + List allPostingPeriods = new ArrayList(); - return compoundingPeriods; - } - - private List determineInterestCompoundingPeriodSummaries(final Money openingAccountBalance, - final BigDecimal annualInterestRate, final SavingsInterestCalculationDaysInYearType daysInYearType, - final List interestCompoundingPeriods) { - - List summaries = new ArrayList(); - - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final BigDecimal percentageDivisor = BigDecimal.valueOf(100l); - final BigDecimal periodsInOneYear = determinePeriodsInOneYear(daysInYearType); - final BigDecimal periodsInOneYearAsFraction = BigDecimal.ONE.divide(periodsInOneYear, mc); - final BigDecimal annualInterestRateAsFraction = annualInterestRate.divide(percentageDivisor, mc); + Money periodStartingBalance = Money.zero(this.currency); final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + BigDecimal interestRateAsFraction = nominalAnnualInterestRate.divide(BigDecimal.valueOf(100l), mc); + for (LocalDateInterval periodInterval : postingPeriodIntervals) { - final SavingsInterestCalculatorFactory interestCalculatorFactory = new SavingsInterestCalculatorFactory(); - final SavingsCompoundInterestCalculator interestCalculator = interestCalculatorFactory.createFrom(interestCalculationType, - periodsInOneYearAsFraction, annualInterestRateAsFraction); + PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, + retreiveOrderedListOfTransactions(), currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, + daysInYearType.getValue()); - BigDecimal periodOpeningBalance = openingAccountBalance.copy().getAmount(); - BigDecimal compoundedInterestToDate = BigDecimal.ZERO; - for (LocalDateInterval periodInterval : interestCompoundingPeriods) { + periodStartingBalance = postingPeriod.closingBalance(); - InterestCompoundingPeriodSummary compoundingSummary = calculateInterestCompoundingPeriodSummary(periodInterval, - periodOpeningBalance, compoundedInterestToDate, interestCalculator); - - periodOpeningBalance = compoundingSummary.closingBalance(); - compoundedInterestToDate = compoundingSummary.compoundedInterest(); - - summaries.add(compoundingSummary); + allPostingPeriods.add(postingPeriod); } - return summaries; + savingsHelper.calculateInterestForAllPostingPeriods(currency, allPostingPeriods); + + this.summary.updateFromInterestPeriodSummaries(currency, allPostingPeriods); + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + + return allPostingPeriods; } - private InterestCompoundingPeriodSummary calculateInterestCompoundingPeriodSummary(final LocalDateInterval periodInterval, - final BigDecimal periodOpeningBalance, final BigDecimal compoundedInterestToDate, - final SavingsCompoundInterestCalculator interestCalculator) { + private List retreiveOrderedListOfTransactions() { + List listOfTransactionsSorted = retreiveListOfTransactions(); - BigDecimal periodClosingBalance = periodOpeningBalance; - List dailyBalances = new ArrayList(); - for (SavingsAccountTransaction transaction : this.transactions) { + List orderedNonInterestPostingTransactions = new ArrayList(); - if (transaction.isAcceptableForDailyBalance(periodInterval)) { - SavingsAccountDailyBalance dailyBalance = transaction.toCompoundedDailyBalance(periodInterval.endDate(), - compoundedInterestToDate); - dailyBalances.add(dailyBalance); - - periodClosingBalance = dailyBalance.endOfDayBalance(); + for (SavingsAccountTransaction transaction : listOfTransactionsSorted) { + if (!transaction.isInterestPosting() && transaction.isNotReversed()) { + orderedNonInterestPostingTransactions.add(transaction); } } - if (dailyBalances.isEmpty()) { - SavingsAccountDailyBalance dailyBalance = SavingsAccountDailyBalance.createFrom(periodInterval, periodOpeningBalance, - compoundedInterestToDate); - dailyBalances.add(dailyBalance); - } - - final BigDecimal totalInterestEarnedUnrounded = interestCalculator.calculate(dailyBalances, - periodInterval.daysInPeriodInclusiveOfEndDate()); - - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final BigDecimal cumulativeCompoundedInterestToDate = compoundedInterestToDate.add(totalInterestEarnedUnrounded, mc); - - return InterestCompoundingPeriodSummary.create(periodInterval, periodOpeningBalance, periodClosingBalance, - totalInterestEarnedUnrounded, cumulativeCompoundedInterestToDate); - } - - private List determineInterestCompoundingPeriods(final LocalDate activationLocalDate, - final LocalDate interestCalculationEndDate, final SavingsCompoundingInterestPeriodType interestPeriodType) { - LocalDate periodStartDate = activationLocalDate; - LocalDate periodEndDate = periodStartDate; - - final List interestPeriods = new ArrayList(); - - while (!periodStartDate.isAfter(interestCalculationEndDate) && !periodEndDate.isAfter(interestCalculationEndDate)) { - - periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType); - if (periodEndDate.isAfter(interestCalculationEndDate)) { - periodEndDate = interestCalculationEndDate; - } - interestPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate)); - - // move periodStartDate forward to day after this period - periodStartDate = periodEndDate.plusDays(1); - } - - return interestPeriods; - } - - private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate periodStartDate, - final SavingsInterestPostingPeriodType interestPostingPeriodType) { - - LocalDate periodEndDate = DateUtils.getLocalDateOfTenant(); - - switch (interestPostingPeriodType) { - case INVALID: - 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 BI_ANNUAL: - // jan 1st to 30, jul 1st to dec 31 - year = periodStartDate.getYearOfEra(); - monthofYear = periodStartDate.getMonthOfYear(); - if (monthofYear <= 6) { - periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); - } else if (monthofYear <= 12) { - periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); - } - break; - case ANNUAL: - periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - break; - } - - // interest posting always occurs on next day after the period end date. - periodEndDate = periodEndDate.plusDays(1); - - return periodEndDate; - } - - private LocalDate determineInterestPeriodEndDateFrom(final LocalDate periodStartDate, - final SavingsCompoundingInterestPeriodType 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 BI_ANNUAL: - // jan 1st to 30, jul 1st to dec 31 - year = periodStartDate.getYearOfEra(); - monthofYear = periodStartDate.getMonthOfYear(); - if (monthofYear <= 6) { - periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); - } else if (monthofYear <= 12) { - periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); - } - break; - case ANNUAL: - periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - break; - case NO_COMPOUNDING_SIMPLE_INTEREST: - periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - break; - } - - return periodEndDate; - } - - 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; + return orderedNonInterestPostingTransactions; } private List retreiveListOfTransactions() { @@ -749,11 +531,13 @@ public class SavingsAccount extends AbstractPersistable { LocalDate endOfBalanceDate = DateUtils.getLocalDateOfTenant(); for (int i = accountTransactionsSorted.size() - 1; i >= 0; i--) { SavingsAccountTransaction transaction = accountTransactionsSorted.get(i); - transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); + if (transaction.isNotReversed()) { + transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); - // this transactions transaction date is end of balance date for - // previous transaction. - endOfBalanceDate = transaction.transactionLocalDate().minusDays(1); + // this transactions transaction date is end of balance date for + // previous transaction. + endOfBalanceDate = transaction.transactionLocalDate().minusDays(1); + } } } @@ -800,22 +584,19 @@ public class SavingsAccount extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final Money amount = Money.of(this.currency, transactionAmount); - + final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, paymentDetail, transactionDate, amount); this.transactions.add(transaction); - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - final LocalDate today = DateUtils.getLocalDateOfTenant(); - calculateInterest(today); + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); return transaction; } private LocalDate getActivationLocalDate() { LocalDate activationLocalDate = null; - if (this.activationDate != null) { - activationLocalDate = new LocalDate(this.activationDate); + if (this.activatedOnDate != null) { + activationLocalDate = new LocalDate(this.activatedOnDate); } return activationLocalDate; } @@ -877,12 +658,6 @@ public class SavingsAccount extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final Money transactionAmountMoney = Money.of(this.currency, transactionAmount); - - if (this.summary.getAccountBalance(this.currency).isLessThan(transactionAmountMoney.plus(this.withdrawalFeeAmount))) { - // - throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), this.withdrawalFeeAmount, transactionAmount); - } - final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, paymentDetail, transactionDate, transactionAmountMoney); this.transactions.add(transaction); @@ -890,7 +665,7 @@ public class SavingsAccount extends AbstractPersistable { if (isAutomaticWithdrawalFee()) { SavingsAccountTransaction withdrawalFeeTransaction = null; - Money feeAmount = null; + Money feeAmount = Money.zero(this.currency); switch (SavingsWithdrawalFeesType.fromInt(this.withdrawalFeeType)) { case INVALID: break; @@ -909,16 +684,51 @@ public class SavingsAccount extends AbstractPersistable { } } - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + validateAccountBalanceDoesNotBecomeNegative(retreiveListOfTransactions(), transactionAmountMoney); - final LocalDate today = DateUtils.getLocalDateOfTenant(); - calculateInterest(today); + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); return transaction; } - public SavingsAccountTransaction addAnnualFee(final DateTimeFormatter formatter, final LocalDate transactionDate, - final List existingTransactionIds, final List existingReversedTransactionIds) { + public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) { + + boolean transactionBeforeLastInterestPosting = false; + + for (SavingsAccountTransaction transaction : retreiveListOfTransactions()) { + if (transaction.isNotReversed() && transaction.isInterestPosting() && transaction.isAfter(transactionDate)) { + transactionBeforeLastInterestPosting = true; + break; + } + } + + return transactionBeforeLastInterestPosting; + } + + private void validateAccountBalanceDoesNotBecomeNegative(final List transactionsSortedByDate, + final Money transactionAmount) { + + Money runningBalance = Money.zero(this.currency); + + for (SavingsAccountTransaction transaction : transactionsSortedByDate) { + if (transaction.isNotReversed() && transaction.isDeposit() || transaction.isInterestPosting()) { + runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); + } else if (transaction.isNotReversed() && transaction.isWithdrawal()) { + runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); + } + + if (runningBalance.isLessThanZero()) { + // + final BigDecimal withdrawalFee = null; + throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, + transactionAmount.getAmount()); + } + } + } + + public SavingsAccountTransaction addAnnualFee(final SavingsHelper savingsHelper, final MathContext mc, + final DateTimeFormatter formatter, final LocalDate transactionDate, final List existingTransactionIds, + final List existingReversedTransactionIds) { if (isNotActive()) { @@ -979,7 +789,7 @@ public class SavingsAccount extends AbstractPersistable { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); final LocalDate today = DateUtils.getLocalDateOfTenant(); - calculateInterest(today); + calculateInterestUsing(savingsHelper, mc, today); updateAnnualFeeNextDueDate(); @@ -1015,14 +825,6 @@ public class SavingsAccount extends AbstractPersistable { return this.summary.getAccountBalance(this.currency).getAmount(); } - private boolean isNotEnoughFundsToWithdraw(final Money amount) { - return !isEnoughFundsToWithdraw(amount); - } - - private boolean isEnoughFundsToWithdraw(final Money amount) { - return this.summary.isLessThanOrEqualToAccountBalance(amount); - } - public void update(final JsonCommand command, final Map actualChanges) { final String localeAsInput = command.locale(); @@ -1062,16 +864,19 @@ public class SavingsAccount extends AbstractPersistable { this.nominalAnnualInterestRate = newValue; } - if (command.isChangeInBigDecimalParameterNamed(SavingsApiConstants.minRequiredOpeningBalanceParamName, + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(SavingsApiConstants.minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.minRequiredOpeningBalanceParamName); + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(SavingsApiConstants.minRequiredOpeningBalanceParamName); actualChanges.put(SavingsApiConstants.minRequiredOpeningBalanceParamName, newValue); actualChanges.put("locale", localeAsInput); this.minRequiredOpeningBalance = Money.of(this.currency, newValue).getAmount(); } - if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) { - final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.lockinPeriodFrequencyParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(SavingsApiConstants.lockinPeriodFrequencyParamName, + this.lockinPeriodFrequency)) { + final Integer newValue = command + .integerValueOfParameterNamedDefaultToNullIfZero(SavingsApiConstants.lockinPeriodFrequencyParamName); actualChanges.put(SavingsApiConstants.lockinPeriodFrequencyParamName, newValue); actualChanges.put("locale", localeAsInput); this.lockinPeriodFrequency = newValue; @@ -1080,54 +885,138 @@ public class SavingsAccount extends AbstractPersistable { if (command.isChangeInIntegerParameterNamed(SavingsApiConstants.lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(SavingsApiConstants.lockinPeriodFrequencyTypeParamName); actualChanges.put(SavingsApiConstants.lockinPeriodFrequencyTypeParamName, newValue); - if (newValue != null) { - this.lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue).getValue(); - } else { - this.lockinPeriodFrequencyType = null; - } + this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue; } - if (command.isChangeInBigDecimalParameterNamed(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName); + // set period type to null if frequency is null + if (this.lockinPeriodFrequency == null) { + this.lockinPeriodFrequencyType = null; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); actualChanges.put(withdrawalFeeAmountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.withdrawalFeeAmount = newValue; } - if (command.isChangeInIntegerParameterNamed(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { - final Integer newValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { + final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeTypeParamName); actualChanges.put(withdrawalFeeTypeParamName, newValue); - this.withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(newValue).getValue(); + this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue; } - if (command.isChangeInBigDecimalParameterNamed(annualFeeAmountParamName, this.annualFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + // set period type to null if frequency is null + if (this.withdrawalFeeAmount == null) { + this.withdrawalFeeType = null; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); actualChanges.put(annualFeeAmountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.annualFeeAmount = newValue; } - if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); final Integer newValue = monthDay.getDayOfMonth(); actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnDay = newValue; - - updateAnnualFeeNextDueDate(); } - if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); final Integer newValue = monthDay.getMonthOfYear(); actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnMonth = newValue; + } + // set period type to null if frequency is null + if (this.annualFeeAmount == null) { + this.annualFeeOnDay = null; + this.annualFeeOnMonth = null; + } + + if (this.annualFeeOnDay != null && this.annualFeeOnMonth != null) { updateAnnualFeeNextDueDate(); } + + validateLockinDetails(); + validateWithdrawalFeeDetails(); + validateAnnualFeeDetails(); + } + + private void validateAnnualFeeDetails() { + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (this.annualFeeAmount == null) { + + if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) { + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull(); + } + } else { + + if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) { + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull(); + } + + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount(); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateWithdrawalFeeDetails() { + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (this.withdrawalFeeAmount == null) { + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).ignoreIfNull() + .isOneOfTheseValues(1, 2); + + if (this.withdrawalFeeType != null) { + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).notNull(); + } + } else { + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).zeroOrPositiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).notNull() + .isOneOfTheseValues(1, 2); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateLockinDetails() { + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (this.lockinPeriodFrequency == null) { + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).ignoreIfNull() + .inMinMaxRange(0, 3); + + if (this.lockinPeriodFrequencyType != null) { + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull() + .integerZeroOrGreater(); + } + } else { + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequencyType).integerZeroOrGreater(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull() + .inMinMaxRange(0, 3); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } public Map deriveAccountingBridgeData(final CurrencyData currencyData, final List existingTransactionIds, @@ -1240,4 +1129,312 @@ public class SavingsAccount extends AbstractPersistable { return this.currency; } -} + public void validateNewApplicationState(final LocalDate todayDateOfTenant) { + + validateLockinDetails(); + validateWithdrawalFeeDetails(); + validateAnnualFeeDetails(); + + final LocalDate submittedOn = getSubmittedOnLocalDate(); + + if (submittedOn.isAfter(todayDateOfTenant)) { + final String errorMessage = "Savings account application submitted on date cannot be in the future."; + throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn); + } + + if (client != null && client.isActivatedAfter(submittedOn)) { + final String errorMessage = "Savings account application submitted on date cannot be earlier than client's activation date."; + throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, + submittedOn); + } else if (group != null && group.isActivatedAfter(submittedOn)) { + final String errorMessage = "Savings account application submitted on date cannot be earlier than group's activation date."; + throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, + submittedOn); + } + } + + private LocalDate getSubmittedOnLocalDate() { + LocalDate submittedOn = null; + if (this.submittedOnDate != null) { + submittedOn = new LocalDate(this.submittedOnDate); + } + return submittedOn; + } + + private LocalDate getApprovedOnLocalDate() { + LocalDate approvedOnLocalDate = null; + if (this.approvedOnDate != null) { + approvedOnLocalDate = new LocalDate(this.approvedOnDate); + } + return approvedOnLocalDate; + } + + public Client getClient() { + return this.client; + } + + public Map approveApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { + + final Map actualChanges = new LinkedHashMap(); + + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { + final String errorMessage = "Savings account application can only be approved when in 'Submitted and pending approval' state. "; + throw new InvalidSavingsAccountStateTransitionException("approval", "not.in.submittedandpendingapproval.state", errorMessage); + } + + this.status = SavingsAccountStatusType.APPROVED.getValue(); + actualChanges.put("status", SavingsEnumerations.status(this.status)); + + // only do below if status has changed in the 'approval' case + LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate"); + String approvedOnDateChange = command.stringValueOfParameterNamed("approvedOnDate"); + if (approvedOn == null) { + approvedOn = command.localDateValueOfParameterNamed("eventDate"); + approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); + } + + this.approvedOnDate = approvedOn.toDate(); + this.approvedBy = currentUser; + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("approvedOnDate", approvedOnDateChange); + + // FIXME - kw - format dates based on based locale when doing error + // messages related dates. + final LocalDate submittalDate = new LocalDate(this.submittedOnDate); + if (approvedOn.isBefore(submittalDate)) { + final String errorMessage = "Savings account application cannot be approved on date before its submittal date: " + + submittalDate.toString(); + throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, + getApprovedOnLocalDate(), submittalDate); + } + + if (approvedOn.isAfter(tenantsTodayDate)) { + final String errorMessage = "Savings account application cannot be approved on date in the future."; + throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, + getApprovedOnLocalDate()); + } + + // FIXME - kw - support field officer history for savings accounts + // if (this.fieldOfficer != null) { + // final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = + // LoanOfficerAssignmentHistory.createNew(this, + // this.fieldOfficer, approvedOn); + // this.loanOfficerHistory.add(loanOfficerAssignmentHistory); + // } + + return actualChanges; + } + + public Map undoApplicationApproval() { + final Map actualChanges = new LinkedHashMap(); + + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { + final String errorMessage = "Savings account application can only undo approval when in 'Approved' state. "; + throw new InvalidSavingsAccountStateTransitionException("approval", "not.in.approved.state", errorMessage); + } + + this.status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue(); + actualChanges.put("status", SavingsEnumerations.status(this.status)); + + this.approvedOnDate = null; + this.approvedBy = null; + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + actualChanges.put("approvedOnDate", ""); + + // FIXME - kw - support field officer history for savings accounts + // this.loanOfficerHistory.clear(); + + return actualChanges; + } + + public Map rejectApplication(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { + + final Map actualChanges = new LinkedHashMap(); + + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { + final String errorMessage = "Savings account application can only be rejected when in 'Submitted and pending approval' state. "; + throw new InvalidSavingsAccountStateTransitionException("reject", "not.in.submittedandpendingapproval.state", errorMessage); + } + + this.status = SavingsAccountStatusType.REJECTED.getValue(); + actualChanges.put("status", SavingsEnumerations.status(this.status)); + + LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); + + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.rejectedOnDate = rejectedOn.toDate(); + this.rejectedBy = currentUser; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = rejectedOn.toDate(); + this.closedBy = currentUser; + + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt)); + actualChanges.put("closedOnDate", rejectedOn.toString(fmt)); + + final LocalDate submittalDate = getSubmittedOnLocalDate(); + if (rejectedOn.isBefore(submittalDate)) { + final String errorMessage = "Savings account application cannot be rejected on date before its submittal date: " + + submittalDate.toString(); + throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, + getApprovedOnLocalDate(), submittalDate); + } + + if (rejectedOn.isAfter(tenantsTodayDate)) { + final String errorMessage = "Savings account application cannot be rejected on date in the future."; + throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, + getApprovedOnLocalDate()); + } + + return actualChanges; + } + + public Map applicantWithdrawsFromApplication(final AppUser currentUser, final JsonCommand command, + final LocalDate tenantsTodayDate) { + final Map actualChanges = new LinkedHashMap(); + + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { + final String errorMessage = "Savings account application can only be withdrawn by applicant when in 'Submitted and pending approval' state. "; + throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "not.in.submittedandpendingapproval.state", + errorMessage); + } + + this.status = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue(); + actualChanges.put("status", SavingsEnumerations.status(this.status)); + + LocalDate withdrawnOn = command.localDateValueOfParameterNamed("withdrawnOnDate"); + + final Locale locale = new Locale(command.locale()); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = withdrawnOn.toDate(); + this.withdrawnBy = currentUser; + this.closedOnDate = withdrawnOn.toDate(); + this.closedBy = currentUser; + + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt)); + actualChanges.put("closedOnDate", withdrawnOn.toString(fmt)); + + final LocalDate submittalDate = getSubmittedOnLocalDate(); + if (withdrawnOn.isBefore(submittalDate)) { + final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: " + + submittalDate.toString(); + throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.before.submittal.date", + errorMessage, getApprovedOnLocalDate(), submittalDate); + } + + if (withdrawnOn.isAfter(tenantsTodayDate)) { + final String errorMessage = "Savings account application cannot be withdrawn by applicant on date in the future."; + throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.a.future.date", errorMessage, + getApprovedOnLocalDate()); + } + + return actualChanges; + } + + public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, + final List existingTransactionIds, final List existingReversedTransactionIds) { + + final Map actualChanges = new LinkedHashMap(); + + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { + final String errorMessage = "Savings account application can only be activated when in 'approved' state. "; + throw new InvalidSavingsAccountStateTransitionException("activation", "not.in.approved.state", errorMessage); + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); + + this.status = SavingsAccountStatusType.ACTIVE.getValue(); + actualChanges.put("status", SavingsEnumerations.status(this.status)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + this.activatedOnDate = activationDate.toDate(); + this.activatedBy = currentUser; + this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); + + actualChanges.put("locale", command.locale()); + actualChanges.put("dateFormat", command.dateFormat()); + actualChanges.put("activationDate", activationDate.toString(fmt)); + + if (this.client.isActivatedAfter(activationDate)) { + final String errorMessage = "The date on which a savings account is activate cannot be before its client activated date: " + + this.client.getActivationLocalDate().toString(); + throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.client.activation.date", errorMessage, + activationDate, this.client.getActivationLocalDate()); + } + + final LocalDate submittalDate = getSubmittedOnLocalDate(); + if (activationDate.isBefore(submittalDate)) { + final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: " + + submittalDate.toString(); + throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.before.submittal.date", + errorMessage, getApprovedOnLocalDate(), submittalDate); + } + + if (activationDate.isAfter(tenantsTodayDate)) { + final String errorMessage = "Savings account application cannot be withdrawn by applicant on date in the future."; + throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.a.future.date", errorMessage, + getApprovedOnLocalDate()); + } + + // auto enter deposit for minimum required opening balance when + // activating account. + final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); + if (minRequiredOpeningBalance.isGreaterThanZero()) { + deposit(fmt, activationDate, minRequiredOpeningBalance.getAmount(), existingTransactionIds, existingReversedTransactionIds, + null); + } + + return actualChanges; + } + + private Date calculateDateAccountIsLockedUntil(final LocalDate activationLocalDate) { + + Date lockedInUntilLocalDate = null; + final PeriodFrequencyType lockinPeriodFrequencyType = PeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); + switch (lockinPeriodFrequencyType) { + case INVALID: + break; + case DAYS: + lockedInUntilLocalDate = activationLocalDate.plusDays(this.lockinPeriodFrequency).toDate(); + break; + case WEEKS: + lockedInUntilLocalDate = activationLocalDate.plusWeeks(this.lockinPeriodFrequency).toDate(); + break; + case MONTHS: + lockedInUntilLocalDate = activationLocalDate.plusMonths(this.lockinPeriodFrequency).toDate(); + break; + case YEARS: + lockedInUntilLocalDate = activationLocalDate.plusYears(this.lockinPeriodFrequency).toDate(); + break; + } + + return lockedInUntilLocalDate; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index 631a54ad6..2e954d19c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -5,36 +5,45 @@ */ package org.mifosplatform.portfolio.savings.domain; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountNoParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.productIdParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; -import java.util.List; -import java.util.Locale; import org.joda.time.LocalDate; import org.joda.time.MonthDay; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.Group; -import org.mifosplatform.portfolio.group.domain.GroupRepository; -import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; +import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -46,18 +55,24 @@ public class SavingsAccountAssembler { private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; private final ClientRepositoryWrapper clientRepository; - private final GroupRepository groupRepository; + private final GroupRepositoryWrapper groupRepository; + private final StaffRepositoryWrapper staffRepository; private final SavingsProductRepository savingProductRepository; + private final SavingsAccountRepositoryWrapper savingsAccountRepository; private final FromJsonHelper fromApiJsonHelper; @Autowired public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, - final ClientRepositoryWrapper clientRepository, final GroupRepository groupRepository, - final SavingsProductRepository savingProductRepository, final FromJsonHelper fromApiJsonHelper) { + final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, + final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, + final SavingsAccountRepositoryWrapper savingsAccountRepository, + final FromJsonHelper fromApiJsonHelper) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; this.groupRepository = groupRepository; + this.staffRepository = staffRepository; this.savingProductRepository = savingProductRepository; + this.savingsAccountRepository = savingsAccountRepository; this.fromApiJsonHelper = fromApiJsonHelper; } @@ -65,36 +80,39 @@ public class SavingsAccountAssembler { * Assembles a new {@link SavingsAccount} from JSON details passed in * request inheriting details where relevant from chosen * {@link SavingsProduct}. - * - * @param existingReversedTransactionIds - * @param existingTransactionIds */ - public SavingsAccount assembleFrom(final JsonCommand command, List existingTransactionIds, - List existingReversedTransactionIds) { + public SavingsAccount assembleFrom(final JsonCommand command) { final JsonElement element = command.parsedJson(); - final String accountNo = fromApiJsonHelper.extractStringNamed("accountNo", element); - final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element); - final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element); + final String externalId = fromApiJsonHelper.extractStringNamed(externalIdParamName, element); + final Long productId = fromApiJsonHelper.extractLongNamed(productIdParamName, element); final SavingsProduct product = this.savingProductRepository.findOne(productId); if (product == null) { throw new SavingsProductNotFoundException(productId); } - final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); - final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); - Client client = null; Group group = null; + Staff fieldOfficer = null; + + final Long clientId = fromApiJsonHelper.extractLongNamed(clientIdParamName, element); if (clientId != null) { client = this.clientRepository.findOneWithNotFoundDetection(clientId); } + final Long groupId = fromApiJsonHelper.extractLongNamed(groupIdParamName, element); if (groupId != null) { - group = this.groupRepository.findOne(groupId); - if (group == null) { throw new GroupNotFoundException(groupId); } + group = this.groupRepository.findOneWithNotFoundDetection(groupId); } + final Long fieldOfficerId = fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element); + if (fieldOfficerId != null) { + fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId); + } + + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); + BigDecimal interestRate = null; if (command.parameterExists(nominalAnnualInterestRateParamName)) { interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName); @@ -102,9 +120,6 @@ public class SavingsAccountAssembler { interestRate = product.nominalAnnualInterestRate(); } - final boolean active = fromApiJsonHelper.extractBooleanNamed(activeParamName, element); - final LocalDate activationDate = fromApiJsonHelper.extractLocalDateNamed(activationDateParamName, element); - SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null; final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName); if (interestPeriodTypeValue != null) { @@ -113,10 +128,10 @@ public class SavingsAccountAssembler { interestCompoundingPeriodType = product.interestCompoundingPeriodType(); } - SavingsInterestPostingPeriodType interestPostingPeriodType = null; + SavingsPostingInterestPeriodType interestPostingPeriodType = null; final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName); if (interestPostingPeriodTypeValue != null) { - interestPostingPeriodType = SavingsInterestPostingPeriodType.fromInt(interestPostingPeriodTypeValue); + interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue); } else { interestPostingPeriodType = product.interestPostingPeriodType(); } @@ -194,17 +209,20 @@ public class SavingsAccountAssembler { monthDayOfAnnualFee = product.monthDayOfAnnualFee(); } - final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, interestRate, - interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, - annualFeeAmount, monthDayOfAnnualFee); + final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, + submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, monthDayOfAnnualFee); account.setHelpers(this.savingsAccountTransactionSummaryWrapper); - if (active) { - final Locale locale = command.extractLocale(); - final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - account.activate(formatter, activationDate, existingTransactionIds, existingReversedTransactionIds); - } + account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); + + return account; + } + + public SavingsAccount assembleFrom(final Long savingsId) { + SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper); return account; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java deleted file mode 100644 index 96a6b13ee..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountDailyBalance.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; - -import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; -import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.organisation.monetary.domain.Money; - -public class SavingsAccountDailyBalance { - - private final LocalDateInterval interestPeriodInterval; - private final BigDecimal endOfDayBalance; - private final BigDecimal cumulativeBalance; - private final BigDecimal compoundedInterestToDate; - - public static SavingsAccountDailyBalance createFrom(final LocalDateInterval interestPeriodInterval, final BigDecimal endOfDayBalance, - final BigDecimal compoundedInterestToDate) { - return new SavingsAccountDailyBalance(interestPeriodInterval, endOfDayBalance, compoundedInterestToDate); - } - - private SavingsAccountDailyBalance(final LocalDateInterval interestPeriodInterval, final BigDecimal endOfDayBalance, - final BigDecimal compoundedInterestToDate) { - this.interestPeriodInterval = interestPeriodInterval; - this.endOfDayBalance = endOfDayBalance; - this.compoundedInterestToDate = compoundedInterestToDate; - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final BigDecimal numberOfDaysBigDecimal = BigDecimal.valueOf(interestPeriodInterval.daysInPeriodInclusiveOfEndDate()); - this.cumulativeBalance = endOfDayBalance.multiply(numberOfDaysBigDecimal, mc); - } - - public Money dailyRunningBalance(final MonetaryCurrency currency) { - return Money.of(currency, endOfDayBalance); - } - - public BigDecimal endOfDayBalance() { - return this.endOfDayBalance; - } - - public BigDecimal cumulativeBalance() { - return this.cumulativeBalance; - } - - public Integer numberOfDays() { - return interestPeriodInterval.daysInPeriodInclusiveOfEndDate(); - } - - public boolean isGreaterThanZero() { - return this.endOfDayBalance.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()); - - final BigDecimal compoundedEndOfDayBalance = this.endOfDayBalance.add(this.compoundedInterestToDate, mc); - return compoundedEndOfDayBalance.multiply(dailyInterestRate, mc).multiply(numberOfDaysBigDecimal, mc); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java index 8f49ceb67..84cc7abec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java @@ -11,8 +11,11 @@ package org.mifosplatform.portfolio.savings.domain; public enum SavingsAccountStatusType { INVALID(0, "savingsAccountStatusType.invalid"), // - UNACTIVATED(100, "savingsAccountStatusType.unactivated"), // + SUBMITTED_AND_PENDING_APPROVAL(100, "savingsAccountStatusType.submitted.and.pending.approval"), // + APPROVED(200, "savingsAccountStatusType.approved"), // ACTIVE(300, "savingsAccountStatusType.active"), // + WITHDRAWN_BY_APPLICANT(400, "savingsAccountStatusType.withdrawn.by.applicant"), // + REJECTED(500, "savingsAccountStatusType.rejected"), // CLOSED(600, "savingsAccountStatusType.closed"); private final Integer value; @@ -23,11 +26,20 @@ public enum SavingsAccountStatusType { SavingsAccountStatusType enumeration = SavingsAccountStatusType.INVALID; switch (type) { case 100: - enumeration = SavingsAccountStatusType.UNACTIVATED; + enumeration = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + break; + case 200: + enumeration = SavingsAccountStatusType.APPROVED; break; case 300: enumeration = SavingsAccountStatusType.ACTIVE; break; + case 400: + enumeration = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT; + break; + case 500: + enumeration = SavingsAccountStatusType.REJECTED; + break; case 600: enumeration = SavingsAccountStatusType.CLOSED; break; @@ -52,8 +64,20 @@ public enum SavingsAccountStatusType { return code; } - public boolean isUnactivated() { - return this.value.equals(SavingsAccountStatusType.UNACTIVATED.getValue()); + public boolean isSubmittedAndPendingApproval() { + return this.value.equals(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue()); + } + + public boolean isApproved() { + return this.value.equals(SavingsAccountStatusType.APPROVED.getValue()); + } + + public boolean isRejected() { + return this.value.equals(SavingsAccountStatusType.REJECTED.getValue()); + } + + public boolean isApplicationWithdrawnByApplicant() { + return this.value.equals(SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue()); } public boolean isActive() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index a7fcabb84..c04174883 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java @@ -13,6 +13,7 @@ import javax.persistence.Embeddable; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; /** * {@link SavingsAccountSummary} encapsulates all the summary details of a @@ -60,19 +61,15 @@ public final class SavingsAccountSummary { .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).getAmount(); } - public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, - final List compoundingPeriods) { + public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List allPostingPeriods) { - // get the max compounded interest - final BigDecimal compoundedInterestToDate = compoundingPeriods.get(compoundingPeriods.size() - 1).compoundedInterest(); - final Money maxIntrestCompoundedToDate = Money.of(currency, compoundedInterestToDate); + Money totalEarned = Money.zero(currency); - BigDecimal totalInterestUnrounded = BigDecimal.ZERO; - for (InterestCompoundingPeriodSummary interestPeriodSummary : compoundingPeriods) { - totalInterestUnrounded = totalInterestUnrounded.add(interestPeriodSummary.interestUnrounded()); + for (PostingPeriod period : allPostingPeriods) { + totalEarned = totalEarned.plus(period.interest()); } - this.totalInterestEarned = maxIntrestCompoundedToDate.getAmountDefaultedToNullIfZero(); + this.totalInterestEarned = totalEarned.getAmount(); } public boolean isLessThanOrEqualToAccountBalance(final Money amount) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 4851d5c8a..687014b9c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -24,7 +24,9 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.portfolio.savings.SavingsAccountTransactionType; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; +import org.mifosplatform.portfolio.savings.domain.interest.EndOfDayBalance; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -205,27 +207,6 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return this.balanceNumberOfDays != null && this.balanceNumberOfDays > 0; } - public SavingsAccountDailyBalance toDailyBalance(final LocalDate periodEnd) { - - LocalDate balanceValidTo = getEndOfBalanceLocalDate(); - if (periodEnd.isBefore(getEndOfBalanceLocalDate())) { - balanceValidTo = periodEnd; - } - - final LocalDateInterval interestPeriodInterval = LocalDateInterval.create(getTransactionLocalDate(), balanceValidTo); - return SavingsAccountDailyBalance.createFrom(interestPeriodInterval, this.runningBalance, BigDecimal.ZERO); - } - - public SavingsAccountDailyBalance toCompoundedDailyBalance(final LocalDate periodEnd, final BigDecimal compoundedInterestToDate) { - LocalDate balanceValidTo = getEndOfBalanceLocalDate(); - if (periodEnd.isBefore(getEndOfBalanceLocalDate())) { - balanceValidTo = periodEnd; - } - - final LocalDateInterval interestPeriodInterval = LocalDateInterval.create(getTransactionLocalDate(), balanceValidTo); - return SavingsAccountDailyBalance.createFrom(interestPeriodInterval, this.runningBalance, compoundedInterestToDate); - } - public boolean hasNotAmount(final Money amountToCheck) { final Money transactionAmount = getAmount(amountToCheck.getCurrency()); return transactionAmount.isNotEqualTo(amountToCheck); @@ -249,4 +230,99 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return thisTransactionData; } + + public boolean isAfter(final LocalDate transactionDate) { + return this.getTransactionLocalDate().isAfter(transactionDate); + } + + public EndOfDayBalance toEndOfDayBalance(final LocalDateInterval periodInterval, final MonetaryCurrency currency) { + + Money endOfDayBalance = Money.of(currency, this.runningBalance); + Money openingBalance = endOfDayBalance; + + LocalDate balanceDate = periodInterval.startDate(); + + int numberOfDays = periodInterval.daysInPeriodInclusiveOfEndDate(); + if (periodInterval.contains(getTransactionLocalDate())) { + balanceDate = getTransactionLocalDate(); + LocalDateInterval newInterval = LocalDateInterval.create(getTransactionLocalDate(), periodInterval.endDate()); + numberOfDays = newInterval.daysInPeriodInclusiveOfEndDate(); + } + + return EndOfDayBalance.from(balanceDate, openingBalance, endOfDayBalance, numberOfDays); + } + + public EndOfDayBalance toEndOfDayBalance(final Money openingBalance, final LocalDate nextTransactionDate) { + + MonetaryCurrency currency = openingBalance.getCurrency(); + Money endOfDayBalance = openingBalance.copy(); + if (isDeposit()) { + endOfDayBalance = openingBalance.plus(getAmount(currency)); + } else if (isWithdrawal() || isWithdrawalFee()) { + endOfDayBalance = openingBalance.minus(getAmount(currency)); + } + + int numberOfDays = LocalDateInterval.create(getTransactionLocalDate(), nextTransactionDate).daysInPeriodInclusiveOfEndDate(); + if (!openingBalance.isEqualTo(endOfDayBalance) && numberOfDays > 1) { + numberOfDays = numberOfDays - 1; + } + return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, numberOfDays); + } + + public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) { + MonetaryCurrency currency = openingBalance.getCurrency(); + Money endOfDayBalance = openingBalance.copy(); + if (isDeposit()) { + endOfDayBalance = openingBalance.plus(getAmount(currency)); + } else if (isWithdrawal() || isWithdrawalFee()) { + endOfDayBalance = openingBalance.minus(getAmount(currency)); + } + + return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays); + } + + public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) { + + MonetaryCurrency currency = openingBalance.getCurrency(); + Money endOfDayBalance = openingBalance.copy(); + + int numberOfDaysOfBalance = this.balanceNumberOfDays; + + LocalDate balanceStartDate = getTransactionLocalDate(); + LocalDate balanceEndDate = getEndOfBalanceLocalDate(); + + if (boundedBy.startDate().isAfter(balanceStartDate)) { + balanceStartDate = boundedBy.startDate(); + LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); + numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); + } else { + if (isDeposit()) { + endOfDayBalance = openingBalance.plus(getAmount(currency)); + } else if (isWithdrawal() || isWithdrawalFee()) { + endOfDayBalance = openingBalance.minus(getAmount(currency)); + } + } + + if (balanceEndDate.isAfter(boundedBy.endDate())) { + balanceEndDate = boundedBy.endDate(); + LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate); + numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate(); + } + + return EndOfDayBalance.from(balanceStartDate, openingBalance, endOfDayBalance, numberOfDaysOfBalance); + } + + public boolean isBalanceInExistencesForOneDayOrMore() { + return this.balanceNumberOfDays != null && this.balanceNumberOfDays.intValue() >= 1; + } + + public boolean fallsWithin(final LocalDateInterval periodInterval) { + LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + return periodInterval.contains(balanceInterval); + } + + public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) { + LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate()); + return balanceInterval.containsPortionOf(periodInterval); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculator.java deleted file mode 100644 index 7e1cf2f0e..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculator.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; -import java.util.List; - -public interface SavingsCompoundInterestCalculator { - - BigDecimal calculate(List dailyBalances, Integer numberOfDays); - -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForAverageBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForAverageBalance.java deleted file mode 100644 index 66d18353f..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForAverageBalance.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.List; - -/** - * Calculates interest on a savings account using the Average Balance - * method. - */ -public class SavingsCompoundInterestCalculatorForAverageBalance implements SavingsCompoundInterestCalculator { - - private final BigDecimal periodsInOneYearAsFraction; - private final BigDecimal annualInterestRateAsFraction; - - public SavingsCompoundInterestCalculatorForAverageBalance(final BigDecimal periodsInOneYearAsFraction, - final BigDecimal annualInterestRateAsFraction) { - this.periodsInOneYearAsFraction = periodsInOneYearAsFraction; - this.annualInterestRateAsFraction = annualInterestRateAsFraction; - } - - @Override - public BigDecimal calculate(final List dailyBalances, final Integer numberOfDays) { - - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final BigDecimal dailyInterestRate = annualInterestRateAsFraction.multiply(periodsInOneYearAsFraction, mc); - final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays.longValue())); - - final BigDecimal numberOfDaysBigDecimal = BigDecimal.valueOf(numberOfDays.longValue()); - - BigDecimal totalCumulativeBalanceForPeriod = BigDecimal.ZERO; - for (SavingsAccountDailyBalance balance : dailyBalances) { - if (balance.isGreaterThanZero()) { - final BigDecimal cumulativeBalance = balance.cumulativeBalance(); - totalCumulativeBalanceForPeriod = totalCumulativeBalanceForPeriod.add(cumulativeBalance); - } - } - - final BigDecimal averageDailyBalance = totalCumulativeBalanceForPeriod.divide(numberOfDaysBigDecimal, mc); - - return averageDailyBalance.multiply(periodicInterestRate, mc); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForDailyBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForDailyBalance.java deleted file mode 100644 index 3fbb82642..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsCompoundInterestCalculatorForDailyBalance.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.List; - -/** - * Calculates interest on a savings account using the Daily Balance - * method. - */ -public class SavingsCompoundInterestCalculatorForDailyBalance implements SavingsCompoundInterestCalculator { - - private final BigDecimal periodsInOneYearAsFraction; - private final BigDecimal annualInterestRateAsFraction; - - public SavingsCompoundInterestCalculatorForDailyBalance(final BigDecimal periodsInOneYearAsFraction, - final BigDecimal annualInterestRateAsFraction) { - this.periodsInOneYearAsFraction = periodsInOneYearAsFraction; - this.annualInterestRateAsFraction = annualInterestRateAsFraction; - } - - @Override - public BigDecimal calculate(final List dailyBalances, final Integer numberOfDays) { - final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); - final BigDecimal dailyInterestRate = annualInterestRateAsFraction.multiply(periodsInOneYearAsFraction, mc); - - BigDecimal interestCalculated = BigDecimal.ZERO; - - for (SavingsAccountDailyBalance balance : dailyBalances) { - if (balance.isGreaterThanZero()) { - final BigDecimal interestForBalance = balance.interestUsingDailyBalanceMethod(dailyInterestRate, balance.numberOfDays()); - interestCalculated = interestCalculated.add(interestForBalance, mc); - } - } - - return interestCalculated; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java new file mode 100644 index 000000000..f87d72e4d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java @@ -0,0 +1,88 @@ +package org.mifosplatform.portfolio.savings.domain; + +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.domain.interest.CompoundInterestHelper; +import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; + +public final class SavingsHelper { + + private final CompoundInterestHelper compoundInterestHelper = new CompoundInterestHelper(); + + public List determineInterestPostingPeriods(final LocalDate activationLocalDate, + final LocalDate interestPostingUpToDate, final SavingsPostingInterestPeriodType postingPeriodType) { + + List postingPeriods = new ArrayList(); + + LocalDate periodStartDate = activationLocalDate; + LocalDate periodEndDate = periodStartDate; + + while (!periodStartDate.isAfter(interestPostingUpToDate) && !periodEndDate.isAfter(interestPostingUpToDate)) { + + final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType); + periodEndDate = interestPostingLocalDate.minusDays(1); + + if (!interestPostingLocalDate.isAfter(DateUtils.getLocalDateOfTenant())) { + postingPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate)); + } else { + postingPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate)); + } + + periodEndDate = interestPostingLocalDate; + periodStartDate = interestPostingLocalDate; + } + + return postingPeriods; + } + + private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate periodStartDate, + final SavingsPostingInterestPeriodType interestPostingPeriodType) { + + LocalDate periodEndDate = DateUtils.getLocalDateOfTenant(); + + switch (interestPostingPeriodType) { + case INVALID: + 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 ANNUAL: + periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + break; + } + + // interest posting always occurs on next day after the period end date. + periodEndDate = periodEndDate.plusDays(1); + + return periodEndDate; + } + + public Money calculateInterestForAllPostingPeriods(final MonetaryCurrency currency, final List allPeriods) { + return this.compoundInterestHelper.calculateInterestForAllPostingPeriods(currency, allPeriods); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java deleted file mode 100644 index e196abb79..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestCalculatorFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -import java.math.BigDecimal; - -public class SavingsInterestCalculatorFactory { - - public SavingsCompoundInterestCalculator createFrom(final SavingsInterestCalculationType interestCalculationType, - final BigDecimal periodsInOneYearAsFraction, final BigDecimal annualInterestRateAsFraction) { - - SavingsCompoundInterestCalculator savingsInterestCalculator = new SavingsCompoundInterestCalculatorForDailyBalance(periodsInOneYearAsFraction, - annualInterestRateAsFraction); - switch (interestCalculationType) { - case AVERAGE_DAILY_BALANCE: - savingsInterestCalculator = new SavingsCompoundInterestCalculatorForAverageBalance(periodsInOneYearAsFraction, - annualInterestRateAsFraction); - break; - case DAILY_BALANCE: - savingsInterestCalculator = new SavingsCompoundInterestCalculatorForDailyBalance(periodsInOneYearAsFraction, - annualInterestRateAsFraction); - break; - case INVALID: - break; - } - - return savingsInterestCalculator; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPostingPeriodType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPostingPeriodType.java deleted file mode 100644 index ce11e933b..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsInterestPostingPeriodType.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.mifosplatform.portfolio.savings.domain; - -/** - * The interest posting period is the span of time at the end of which savings - * earned but not yet credited/posted in a client's account is credited/posted. - */ -public enum SavingsInterestPostingPeriodType { - - INVALID(0, "savingsInterestPostingPeriodType.invalid"), // - MONTHLY(4, "savingsInterestPostingPeriodType.monthly"), // - QUATERLY(5, "savingsInterestPostingPeriodType.quarterly"), // - BI_ANNUAL(6, "savingsInterestPostingPeriodType.biannual"), // - ANNUAL(7, "savingsInterestPostingPeriodType.annual"); - - private final Integer value; - private final String code; - - private SavingsInterestPostingPeriodType(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 SavingsInterestPostingPeriodType fromInt(final Integer type) { - SavingsInterestPostingPeriodType repaymentFrequencyType = SavingsInterestPostingPeriodType.INVALID; - if (type != null) { - switch (type) { - case 4: - repaymentFrequencyType = SavingsInterestPostingPeriodType.MONTHLY; - break; - case 5: - repaymentFrequencyType = SavingsInterestPostingPeriodType.QUATERLY; - break; - case 6: - repaymentFrequencyType = SavingsInterestPostingPeriodType.BI_ANNUAL; - break; - case 7: - repaymentFrequencyType = SavingsInterestPostingPeriodType.ANNUAL; - break; - } - } - return repaymentFrequencyType; - } -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index cf19db5b0..68eaaae63 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -5,27 +5,30 @@ */ package org.mifosplatform.portfolio.savings.domain; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.localeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nameParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountingRuleParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import javax.persistence.Column; @@ -36,9 +39,17 @@ import javax.persistence.Table; import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; -import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -68,7 +79,7 @@ public class SavingsProduct extends AbstractPersistable { private Integer interestCompoundingPeriodType; /** - * A value from the {@link SavingsInterestPostingPeriodType} enumeration. + * A value from the {@link SavingsPostingInterestPeriodType} enumeration. */ @Column(name = "interest_posting_period_enum", nullable = false) private Integer interestPostingPeriodType; @@ -118,7 +129,7 @@ public class SavingsProduct extends AbstractPersistable { public static SavingsProduct createNew(final String name, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, @@ -136,7 +147,7 @@ public class SavingsProduct extends AbstractPersistable { private SavingsProduct(final String name, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsInterestPostingPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, @@ -157,16 +168,16 @@ public class SavingsProduct extends AbstractPersistable { } this.lockinPeriodFrequency = lockinPeriodFrequency; - if (lockinPeriodFrequencyType != null) { + if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) { this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue(); } this.withdrawalFeeAmount = withdrawalFeeAmount; - if (withdrawalFeeType != null) { + if (withdrawalFeeAmount != null && withdrawalFeeType != null) { this.withdrawalFeeType = withdrawalFeeType.getValue(); } this.annualFeeAmount = annualFeeAmount; - if (annualFeeOnMonthDay != null) { + if (annualFeeAmount != null && annualFeeOnMonthDay != null) { this.annualFeeOnMonth = annualFeeOnMonthDay.getMonthOfYear(); this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); } @@ -192,8 +203,8 @@ public class SavingsProduct extends AbstractPersistable { return SavingsCompoundingInterestPeriodType.fromInt(this.interestCompoundingPeriodType); } - public SavingsInterestPostingPeriodType interestPostingPeriodType() { - return SavingsInterestPostingPeriodType.fromInt(this.interestPostingPeriodType); + public SavingsPostingInterestPeriodType interestPostingPeriodType() { + return SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); } public SavingsInterestCalculationType interestCalculationType() { @@ -295,7 +306,7 @@ public class SavingsProduct extends AbstractPersistable { if (command.isChangeInIntegerParameterNamed(interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) { final Integer newValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName); actualChanges.put(interestPostingPeriodTypeParamName, newValue); - this.interestPostingPeriodType = SavingsInterestPostingPeriodType.fromInt(newValue).getValue(); + this.interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(newValue).getValue(); } if (command.isChangeInIntegerParameterNamed(interestCalculationTypeParamName, this.interestCalculationType)) { @@ -310,15 +321,16 @@ public class SavingsProduct extends AbstractPersistable { this.interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(newValue).getValue(); } - if (command.isChangeInBigDecimalParameterNamed(minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredOpeningBalanceParamName, + this.minRequiredOpeningBalance)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName); actualChanges.put(minRequiredOpeningBalanceParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.minRequiredOpeningBalance = newValue; } - if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) { - final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) { + final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName); actualChanges.put(lockinPeriodFrequencyParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.lockinPeriodFrequency = newValue; @@ -327,47 +339,63 @@ public class SavingsProduct extends AbstractPersistable { if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName); actualChanges.put(lockinPeriodFrequencyTypeParamName, newValue); - this.lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(newValue).getValue(); + this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue; } - if (command.isChangeInBigDecimalParameterNamed(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName); + // set period type to null if frequency is null + if (this.lockinPeriodFrequency == null) { + this.lockinPeriodFrequencyType = null; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(withdrawalFeeAmountParamName, this.withdrawalFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); actualChanges.put(withdrawalFeeAmountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.withdrawalFeeAmount = newValue; } - if (command.isChangeInIntegerParameterNamed(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { - final Integer newValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(withdrawalFeeTypeParamName, this.withdrawalFeeType)) { + final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeTypeParamName); actualChanges.put(withdrawalFeeTypeParamName, newValue); - this.withdrawalFeeType = newValue != null? SavingsWithdrawalFeesType.fromInt(newValue).getValue(): newValue; + this.withdrawalFeeType = newValue != null ? SavingsWithdrawalFeesType.fromInt(newValue).getValue() : newValue; } - if (command.isChangeInBigDecimalParameterNamed(annualFeeAmountParamName, this.annualFeeAmount)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + // set period type to null if frequency is null + if (this.withdrawalFeeAmount == null) { + this.withdrawalFeeType = null; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(annualFeeAmountParamName, this.annualFeeAmount)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); actualChanges.put(annualFeeAmountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.annualFeeAmount = newValue; } - if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnDay)) { final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); - final Integer newValue = monthDay != null? monthDay.getDayOfMonth(): null; + final Integer newValue = monthDay != null ? monthDay.getDayOfMonth() : null; actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnDay = newValue; } - if (command.isChangeInIntegerParameterNamed(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { + if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(annualFeeOnMonthDayParamName, this.annualFeeOnMonth)) { final MonthDay monthDay = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final String actualValueEntered = command.stringValueOfParameterNamed(annualFeeOnMonthDayParamName); - final Integer newValue = monthDay != null? monthDay.getMonthOfYear(): null; + final Integer newValue = monthDay != null ? monthDay.getMonthOfYear() : null; actualChanges.put(annualFeeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.annualFeeOnMonth = newValue; } + // set period type to null if frequency is null + if (this.annualFeeAmount == null) { + this.annualFeeOnDay = null; + this.annualFeeOnMonth = null; + } + if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) { final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName); actualChanges.put(accountingRuleParamName, newValue); @@ -382,42 +410,72 @@ public class SavingsProduct extends AbstractPersistable { } private void validateAnnualFeeDetails() { - if (isInvalidConfigurationOfAnnualFeeSettings()) { - Object[] defaultUserMessageArgs = new Object[] { annualFeeAmountParamName }; - throw new GeneralPlatformDomainRuleException("error.msg.product.savings.invalid.annualfee.settings", - "Invalid configuration of annual fee settings.", defaultUserMessageArgs); - } - } - private boolean isInvalidConfigurationOfAnnualFeeSettings() { - return (this.annualFeeAmount == null && (this.annualFeeOnDay != null || this.annualFeeOnMonth != null)) - || ((this.annualFeeOnDay == null || this.annualFeeOnMonth == null) && this.annualFeeAmount != null); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); + + if (this.annualFeeAmount == null) { + + if (this.annualFeeOnMonth != null || this.annualFeeOnDay != null) { + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).notNull(); + } + } else { + + if (this.annualFeeOnMonth == null || this.annualFeeOnDay == null) { + baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(this.annualFeeOnMonth).notNull(); + } + + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(this.annualFeeAmount).zeroOrPositiveAmount(); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } private void validateWithdrawalFeeDetails() { - if (isInvalidConfigurationOfWithdrawalFeeSettings()) { - Object[] defaultUserMessageArgs = new Object[] { withdrawalFeeAmountParamName }; - throw new GeneralPlatformDomainRuleException("error.msg.product.savings.invalid.withdrawalfee.settings", - "Invalid configuration of withdrawal fee settings.", defaultUserMessageArgs); - } - } - private boolean isInvalidConfigurationOfWithdrawalFeeSettings() { - return (this.withdrawalFeeAmount == null && this.withdrawalFeeType != null) - || (this.withdrawalFeeType == null && this.withdrawalFeeAmount != null); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); + + if (this.withdrawalFeeAmount == null) { + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).ignoreIfNull() + .isOneOfTheseValues(1, 2); + + if (this.withdrawalFeeType != null) { + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).notNull(); + } + } else { + baseDataValidator.reset().parameter(withdrawalFeeAmountParamName).value(this.withdrawalFeeAmount).zeroOrPositiveAmount(); + baseDataValidator.reset().parameter(withdrawalFeeTypeParamName).value(this.withdrawalFeeType).notNull() + .isOneOfTheseValues(1, 2); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } private void validateLockinDetails() { - if (isInvalidConfigurationOfLockinSettings()) { - Object[] defaultUserMessageArgs = new Object[] { lockinPeriodFrequencyParamName }; - throw new GeneralPlatformDomainRuleException("error.msg.product.savings.invalid.lockin.settings", - "Invalid configuration of lock in settings.", defaultUserMessageArgs); - } - } - private boolean isInvalidConfigurationOfLockinSettings() { - return (this.lockinPeriodFrequency == null && this.lockinPeriodFrequencyType != null) - || (this.lockinPeriodFrequencyType == null && this.lockinPeriodFrequency != null); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); + + if (this.lockinPeriodFrequency == null) { + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).ignoreIfNull() + .inMinMaxRange(0, 3); + + if (this.lockinPeriodFrequencyType != null) { + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull() + .integerZeroOrGreater(); + } + } else { + baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequencyType).integerZeroOrGreater(); + baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull() + .inMinMaxRange(0, 3); + } + + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } public boolean isCashBasedAccountingEnabled() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index b6ad15a25..0a01047c2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -5,22 +5,22 @@ */ package org.mifosplatform.portfolio.savings.domain; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.annualFeeOnMonthDayParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.currencyCodeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.descriptionParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.digitsAfterDecimalParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCalculationTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nameParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nameParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeAmountParamName; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; @@ -28,6 +28,12 @@ import org.joda.time.MonthDay; import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.stereotype.Component; @Component @@ -50,10 +56,10 @@ public class SavingsProductAssembler { interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue); } - SavingsInterestPostingPeriodType interestPostingPeriodType = null; + SavingsPostingInterestPeriodType interestPostingPeriodType = null; final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName); if (interestPostingPeriodTypeValue != null) { - interestPostingPeriodType = SavingsInterestPostingPeriodType.fromInt(interestPostingPeriodTypeValue); + interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue); } SavingsInterestCalculationType interestCalculationType = null; @@ -69,23 +75,24 @@ public class SavingsProductAssembler { interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue); } - final BigDecimal minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); + final BigDecimal minRequiredOpeningBalance = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName); - final Integer lockinPeriodFrequency = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName); + final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName); SavingsPeriodFrequencyType lockinPeriodFrequencyType = null; final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName); if (lockinPeriodFrequencyTypeValue != null) { lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); } - final BigDecimal withdrawalFeeAmount = command.bigDecimalValueOfParameterNamed(withdrawalFeeAmountParamName); + final BigDecimal withdrawalFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(withdrawalFeeAmountParamName); SavingsWithdrawalFeesType withdrawalFeeType = null; final Integer withdrawalFeeTypeValue = command.integerValueOfParameterNamed(withdrawalFeeTypeParamName); if (withdrawalFeeTypeValue != null) { withdrawalFeeType = SavingsWithdrawalFeesType.fromInt(withdrawalFeeTypeValue); } - final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); + final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName); final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundInterestHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundInterestHelper.java new file mode 100644 index 000000000..f13b0d1fb --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundInterestHelper.java @@ -0,0 +1,31 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.util.List; + +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; + +public class CompoundInterestHelper { + + public Money calculateInterestForAllPostingPeriods(final MonetaryCurrency currency, final List allPeriods) { + + // sum up the 'rounded' values that are posted each posting period + Money interestEarned = Money.zero(currency); + + // total interest earned in previous periods but not yet recognised + BigDecimal interestEarnedButNotPosted = BigDecimal.ZERO; + for (PostingPeriod postingPeriod : allPeriods) { + + final BigDecimal interestEarnedThisPeriod = postingPeriod.calculateInterest(interestEarnedButNotPosted); + + Money moneyToBePostedForPeriod = Money.of(currency, interestEarnedThisPeriod); + + interestEarned = interestEarned.plus(moneyToBePostedForPeriod); + + interestEarnedButNotPosted = interestEarnedButNotPosted.add(moneyToBePostedForPeriod.getAmount()); + } + + return interestEarned; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundingPeriod.java new file mode 100644 index 000000000..a14bba075 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/CompoundingPeriod.java @@ -0,0 +1,15 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; + +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public interface CompoundingPeriod { + + BigDecimal calculateInterest(BigDecimal interestRateAsFraction, long daysInYear); + + BigDecimal calculateInterest(SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + SavingsInterestCalculationType interestCalculationType, BigDecimal interestFromPreviousPostingPeriod, + BigDecimal interestRateAsFraction, long daysInYear); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/DailyCompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/DailyCompoundingPeriod.java new file mode 100644 index 000000000..7be30730f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/DailyCompoundingPeriod.java @@ -0,0 +1,90 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.ArrayList; +import java.util.List; + +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public class DailyCompoundingPeriod implements CompoundingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final List endOfDayBalances; + + public static DailyCompoundingPeriod create(final LocalDateInterval periodInterval, final List allEndOfDayBalances) { + + final List endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances); + + return new DailyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod); + } + + private static List endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval, + final List allEndOfDayBalances) { + + List endOfDayBalancesForPeriodInterval = new ArrayList(); + + EndOfDayBalance cappedToPeriodEndDate = null; + + for (EndOfDayBalance endOfDayBalance : allEndOfDayBalances) { + + if (compoundingPeriodInterval.contains(endOfDayBalance.date())) { + cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval); + } else if (endOfDayBalance.contains(compoundingPeriodInterval)) { + cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval); + } else { + LocalDateInterval latestPeriod = LocalDateInterval.create(compoundingPeriodInterval.startDate(), + DateUtils.getLocalDateOfTenant()); + cappedToPeriodEndDate = endOfDayBalance.upTo(latestPeriod); + } + + if (cappedToPeriodEndDate != null) { + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } + } + + return endOfDayBalancesForPeriodInterval; + } + + private DailyCompoundingPeriod(final LocalDateInterval periodInterval, final List endOfDayBalances) { + this.periodInterval = periodInterval; + this.endOfDayBalances = endOfDayBalances; + } + + @Override + public BigDecimal calculateInterest(final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + for (EndOfDayBalance balance : this.endOfDayBalances) { + BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalance(BigDecimal.ZERO, interestRateAsFraction, daysInYear); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } + + @Override + public BigDecimal calculateInterest( + @SuppressWarnings("unused") final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + @SuppressWarnings("unused") final SavingsInterestCalculationType interestCalculationType, + final BigDecimal interestFromPreviousPostingPeriod, final BigDecimal interestRateAsFraction, final long daysInYear) { + BigDecimal interestEarned = BigDecimal.ZERO; + + // for daily compounding - each interest calculated from previous daily + // calculations is 'compounded' + BigDecimal interestToCompound = interestFromPreviousPostingPeriod; + for (EndOfDayBalance balance : this.endOfDayBalances) { + BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, + interestRateAsFraction, daysInYear); + interestToCompound = interestToCompound.add(interestOnBalanceUnrounded, MathContext.DECIMAL64).setScale(9); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java new file mode 100644 index 000000000..078bca10b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/EndOfDayBalance.java @@ -0,0 +1,118 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.Money; + +public class EndOfDayBalance { + + private final LocalDate date; + private final Money openingBalance; + private final Money endOfDayBalance; + private final int numberOfDays; + + public static EndOfDayBalance from(final LocalDate date, final Money openingBalance, final Money endOfDayBalance, final int numberOfDays) { + return new EndOfDayBalance(date, openingBalance, endOfDayBalance, numberOfDays); + } + + public EndOfDayBalance(final LocalDate date, final Money openingBalance, final Money endOfDayBalance, final int numberOfDays) { + this.date = date; + this.openingBalance = openingBalance; + this.endOfDayBalance = endOfDayBalance; + this.numberOfDays = numberOfDays; + } + + public LocalDate date() { + return this.date; + } + + public Money closingBalance() { + return this.endOfDayBalance; + } + + public BigDecimal cumulativeBalance(final BigDecimal interestToCompound) { + final BigDecimal daysAsBigDecimal = BigDecimal.valueOf(this.numberOfDays); + final BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount().add(interestToCompound); + return realBalanceForInterestCalculation.multiply(daysAsBigDecimal, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + public BigDecimal calculateInterestOnBalance(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, + final long daysInYear) { + + BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64); + BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(this.numberOfDays), MathContext.DECIMAL64); + + BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount().add(interestToCompound); + + return realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + /** + * Future Value (FV) = PV x (1+r)^n + * + * Interest = FV - PV PV = Principal or the Account Balance r = rate per + * compounding period (so for daily, r = nominalInterestRateAsFraction x + * 1/365 n = number of periods rate is compounded + */ + public BigDecimal calculateInterestOnBalanceAndInterest(final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, + final long daysInYear) { + BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + + BigDecimal presentValue = endOfDayBalance.getAmount().add(interestToCompound); + + BigDecimal r = interestRateAsFraction.multiply(multiplicand); + + BigDecimal interestRateForCompoundingPeriodPlusOne = BigDecimal.ONE.add(r); + + double interestRateForCompoundingPeriodPowered = Math.pow(interestRateForCompoundingPeriodPlusOne.doubleValue(), + Integer.valueOf(this.numberOfDays).doubleValue()); + + BigDecimal futureValue = presentValue.multiply(BigDecimal.valueOf(interestRateForCompoundingPeriodPowered), MathContext.DECIMAL64) + .setScale(9, RoundingMode.HALF_EVEN); + return futureValue.subtract(presentValue); + } + + public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval) { + + Money startingBalance = openingBalance; + LocalDate balanceStartDate = this.date; + + if (this.date.isBefore(compoundingPeriodInterval.startDate())) { + balanceStartDate = compoundingPeriodInterval.startDate(); + startingBalance = endOfDayBalance; + } + + int daysOfBalance = this.numberOfDays; + LocalDate balanceEndDate = balanceStartDate.plusDays(this.numberOfDays); + if (balanceEndDate.isAfter(compoundingPeriodInterval.endDate())) { + balanceEndDate = compoundingPeriodInterval.endDate(); + LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate); + daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate(); + } + if (balanceEndDate.isAfter(DateUtils.getLocalDateOfTenant())) { + balanceEndDate = DateUtils.getLocalDateOfTenant(); + LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate, balanceEndDate); + daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate(); + } + + return new EndOfDayBalance(balanceStartDate, startingBalance, this.endOfDayBalance, daysOfBalance); + } + + public boolean contains(final LocalDateInterval compoundingPeriodInterval) { + + final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays); + + final LocalDateInterval balanceInterval = LocalDateInterval.create(this.date, balanceUpToDate); + return balanceInterval.containsPortionOf(compoundingPeriodInterval); + } + + public Integer getNumberOfDays() { + return Integer.valueOf(this.numberOfDays); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java new file mode 100644 index 000000000..60646a339 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/MonthlyCompoundingPeriod.java @@ -0,0 +1,149 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; + +public class MonthlyCompoundingPeriod implements CompoundingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final List endOfDayBalances; + + public static MonthlyCompoundingPeriod create(final LocalDateInterval periodInterval, final List allEndOfDayBalances) { + + final List endOfDayBalancesWithinPeriod = endOfDayBalancesWithinPeriodInterval(periodInterval, allEndOfDayBalances); + + return new MonthlyCompoundingPeriod(periodInterval, endOfDayBalancesWithinPeriod); + } + + @Override + public BigDecimal calculateInterest(final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + for (EndOfDayBalance balance : endOfDayBalances) { + BigDecimal interestOnBalanceUnrounded = balance.calculateInterestOnBalance(BigDecimal.ZERO, interestRateAsFraction, daysInYear); + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + + return interestEarned; + } + + @Override + public BigDecimal calculateInterest(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + + switch (interestCalculationType) { + case DAILY_BALANCE: + interestEarned = calculateUsingDailyBalanceMethod(compoundingInterestPeriodType, interestToCompound, + interestRateAsFraction, daysInYear); + break; + case AVERAGE_DAILY_BALANCE: + interestEarned = calculateUsingAverageDailyBalanceMethod(interestToCompound, interestRateAsFraction, daysInYear); + break; + case INVALID: + break; + } + + return interestEarned; + } + + private BigDecimal calculateUsingAverageDailyBalanceMethod(final BigDecimal interestToCompound, + final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal cumulativeBalance = BigDecimal.ZERO; + Integer numberOfDays = Integer.valueOf(0); + + for (EndOfDayBalance balance : endOfDayBalances) { + BigDecimal endOfDayCumulativeBalance = balance.cumulativeBalance(interestToCompound); + cumulativeBalance = cumulativeBalance.add(endOfDayCumulativeBalance); + + Integer balanceExistsForNumberOfDays = balance.getNumberOfDays(); + numberOfDays = numberOfDays + balanceExistsForNumberOfDays; + } + + BigDecimal interestEarned = BigDecimal.ZERO; + if (cumulativeBalance.compareTo(BigDecimal.ZERO) != 0 && numberOfDays > 0) { + BigDecimal averageDailyBalance = cumulativeBalance.divide(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64).setScale(9, + RoundingMode.HALF_EVEN); + + BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear), MathContext.DECIMAL64); + BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64); + BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(numberOfDays), MathContext.DECIMAL64); + + interestEarned = averageDailyBalance.multiply(periodicInterestRate, MathContext.DECIMAL64).setScale(9, RoundingMode.HALF_EVEN); + } + + return interestEarned; + } + + private BigDecimal calculateUsingDailyBalanceMethod(final SavingsCompoundingInterestPeriodType compoundingInterestPeriodType, + final BigDecimal interestToCompound, final BigDecimal interestRateAsFraction, final long daysInYear) { + + BigDecimal interestEarned = BigDecimal.ZERO; + BigDecimal interestOnBalanceUnrounded = BigDecimal.ZERO; + for (EndOfDayBalance balance : endOfDayBalances) { + + switch (compoundingInterestPeriodType) { + case DAILY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalanceAndInterest(interestToCompound, interestRateAsFraction, + daysInYear); + break; + case MONTHLY: + interestOnBalanceUnrounded = balance.calculateInterestOnBalance(interestToCompound, interestRateAsFraction, daysInYear); + break; + // case QUATERLY: + // break; + // case WEEKLY: + // break; + // case BIWEEKLY: + // break; + // case BI_ANNUAL: + // break; + // case ANNUAL: + // break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; + case INVALID: + break; + } + + interestEarned = interestEarned.add(interestOnBalanceUnrounded); + } + return interestEarned; + } + + private static List endOfDayBalancesWithinPeriodInterval(final LocalDateInterval compoundingPeriodInterval, + final List allEndOfDayBalances) { + + List endOfDayBalancesForPeriodInterval = new ArrayList(); + + for (EndOfDayBalance endOfDayBalance : allEndOfDayBalances) { + + if (compoundingPeriodInterval.contains(endOfDayBalance.date())) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } else if (endOfDayBalance.contains(compoundingPeriodInterval)) { + final EndOfDayBalance cappedToPeriodEndDate = endOfDayBalance.upTo(compoundingPeriodInterval); + endOfDayBalancesForPeriodInterval.add(cappedToPeriodEndDate); + } + } + + return endOfDayBalancesForPeriodInterval; + } + + private MonthlyCompoundingPeriod(final LocalDateInterval periodInterval, final List endOfDayBalances) { + this.periodInterval = periodInterval; + this.endOfDayBalances = endOfDayBalances; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java new file mode 100644 index 000000000..3da8b4300 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/interest/PostingPeriod.java @@ -0,0 +1,248 @@ +package org.mifosplatform.portfolio.savings.domain.interest; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.domain.LocalDateInterval; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; + +public class PostingPeriod { + + @SuppressWarnings("unused") + private final LocalDateInterval periodInterval; + private final MonetaryCurrency currency; + private final SavingsCompoundingInterestPeriodType interestCompoundingType; + private final BigDecimal interestRateAsFraction; + private final long daysInYear; + private final List compoundingPeriods; + + // interest posting details + private final LocalDate dateOfPostingTransaction; + private BigDecimal interestEarnedUnrounded; + private Money interestEarnedRounded; + + // opening/closing details + @SuppressWarnings("unused") + private final Money openingBalance; + private final Money closingBalance; + private final SavingsInterestCalculationType interestCalculationType; + + public static PostingPeriod createFrom(final LocalDateInterval periodInterval, final Money periodStartingBalance, + final List orderedListOfTransactions, final MonetaryCurrency currency, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear) { + + List accountEndOfDayBalances = new ArrayList(); + + Money openingDayBalance = periodStartingBalance; + Money closeOfDayBalance = openingDayBalance; + for (SavingsAccountTransaction transaction : orderedListOfTransactions) { + + if (transaction.fallsWithin(periodInterval)) { + // the balance of the transaction falls entirely within this + // period so no need to do any cropping/bounding + EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalance(openingDayBalance); + accountEndOfDayBalances.add(endOfDayBalance); + + openingDayBalance = endOfDayBalance.closingBalance(); + } else if (transaction.spansAnyPortionOf(periodInterval)) { + EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalanceBoundedBy(openingDayBalance, periodInterval); + accountEndOfDayBalances.add(endOfDayBalance); + + closeOfDayBalance = endOfDayBalance.closingBalance(); + openingDayBalance = closeOfDayBalance; + } + } + + if (accountEndOfDayBalances.isEmpty()) { + EndOfDayBalance endOfDayBalance = EndOfDayBalance.from(periodInterval.startDate(), openingDayBalance, closeOfDayBalance, + periodInterval.daysInPeriodInclusiveOfEndDate()); + accountEndOfDayBalances.add(endOfDayBalance); + + closeOfDayBalance = endOfDayBalance.closingBalance(); + openingDayBalance = closeOfDayBalance; + } + + final List compoundingPeriods = compoundingPeriodsInPostingPeriod(periodInterval, interestCompoundingPeriodType, + accountEndOfDayBalances); + + return new PostingPeriod(periodInterval, currency, periodStartingBalance, openingDayBalance, interestCompoundingPeriodType, + interestCalculationType, interestRateAsFraction, daysInYear, compoundingPeriods); + } + + private PostingPeriod(final LocalDateInterval periodInterval, final MonetaryCurrency currency, final Money openingBalance, + final Money closingBalance, final SavingsCompoundingInterestPeriodType interestCompoundingType, + final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear, + final List compoundingPeriods) { + this.periodInterval = periodInterval; + this.currency = currency; + this.openingBalance = openingBalance; + this.closingBalance = closingBalance; + this.interestCompoundingType = interestCompoundingType; + this.interestCalculationType = interestCalculationType; + this.interestRateAsFraction = interestRateAsFraction; + this.daysInYear = daysInYear; + this.compoundingPeriods = compoundingPeriods; + + this.dateOfPostingTransaction = periodInterval.endDate().plusDays(1); + } + + public Money interest() { + return this.interestEarnedRounded; + } + + public LocalDate dateOfPostingTransaction() { + return this.dateOfPostingTransaction; + } + + public Money closingBalance() { + return this.closingBalance; + } + + public BigDecimal calculateInterest(final BigDecimal interestFromPreviousPostingPeriod) { + BigDecimal interestEarned = BigDecimal.ZERO; + + // for each compounding period accumulate the amount of interest + // to be applied to the balanced for interest calculation + BigDecimal interestCompounded = interestFromPreviousPostingPeriod; + for (CompoundingPeriod compoundingPeriod : this.compoundingPeriods) { + + BigDecimal interestUnrounded = compoundingPeriod.calculateInterest(this.interestCompoundingType, this.interestCalculationType, + interestCompounded, this.interestRateAsFraction, this.daysInYear); + interestCompounded = interestCompounded.add(interestUnrounded); + interestEarned = interestEarned.add(interestUnrounded); + } + + this.interestEarnedUnrounded = interestEarned; + this.interestEarnedRounded = Money.of(this.currency, this.interestEarnedUnrounded); + + return interestEarned; + } + + public Money getInterestEarned() { + return this.interestEarnedRounded; + } + + private static List compoundingPeriodsInPostingPeriod(final LocalDateInterval postingPeriodInterval, + final SavingsCompoundingInterestPeriodType interestPeriodType, final List allEndOfDayBalances) { + + List compoundingPeriods = new ArrayList(); + + CompoundingPeriod compoundingPeriod = null; + switch (interestPeriodType) { + case INVALID: + break; + case DAILY: + compoundingPeriod = DailyCompoundingPeriod.create(postingPeriodInterval, allEndOfDayBalances); + compoundingPeriods.add(compoundingPeriod); + break; + case MONTHLY: + + final LocalDate postingPeriodEndDate = postingPeriodInterval.endDate(); + + LocalDate periodStartDate = postingPeriodInterval.startDate(); + LocalDate periodEndDate = periodStartDate; + + while (!periodStartDate.isAfter(postingPeriodEndDate) && !periodEndDate.isAfter(postingPeriodEndDate)) { + + periodEndDate = determineInterestPeriodEndDateFrom(periodStartDate, interestPeriodType); + if (periodEndDate.isAfter(postingPeriodEndDate)) { + periodEndDate = postingPeriodEndDate; + } + + final LocalDateInterval compoundingPeriodInterval = LocalDateInterval.create(periodStartDate, periodEndDate); + if (postingPeriodInterval.contains(compoundingPeriodInterval)) { + + compoundingPeriod = MonthlyCompoundingPeriod.create(compoundingPeriodInterval, allEndOfDayBalances); + compoundingPeriods.add(compoundingPeriod); + } + + // move periodStartDate forward to day after this period + periodStartDate = periodEndDate.plusDays(1); + } + break; + // case WEEKLY: + // break; + // case BIWEEKLY: + // break; + // case QUATERLY: + // break; + // case BI_ANNUAL: + // break; + // case ANNUAL: + // break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // break; + } + + return compoundingPeriods; + } + + private static LocalDate determineInterestPeriodEndDateFrom(final LocalDate periodStartDate, + final SavingsCompoundingInterestPeriodType 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 BI_ANNUAL: + // // jan 1st to 30, jul 1st to dec 31 + // year = periodStartDate.getYearOfEra(); + // monthofYear = periodStartDate.getMonthOfYear(); + // if (monthofYear <= 6) { + // periodEndDate = new DateTime().withDate(year, 6, 30).toLocalDate(); + // } else if (monthofYear <= 12) { + // periodEndDate = new DateTime().withDate(year, 12, 31).toLocalDate(); + // } + // break; + // case ANNUAL: + // periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + // break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); + // periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + // break; + } + + return periodEndDate; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InsufficientAccountBalanceException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InsufficientAccountBalanceException.java index 321be2827..3a580d7a2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InsufficientAccountBalanceException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InsufficientAccountBalanceException.java @@ -15,8 +15,10 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul */ public class InsufficientAccountBalanceException extends AbstractPlatformDomainRuleException { - public InsufficientAccountBalanceException(final String paramName, final BigDecimal accountBalance, final BigDecimal withdrawalFee, final BigDecimal transactionAmount) { - super(withdrawalFee != null? "error.msg.savingsaccount.transaction.insufficient.account.balance.withdraw" : "error.msg.savingsaccount.transaction.insufficient.account.balance", - "Insufficient account balance.", paramName, accountBalance, withdrawalFee, transactionAmount); + public InsufficientAccountBalanceException(final String paramName, final BigDecimal accountBalance, final BigDecimal withdrawalFee, + final BigDecimal transactionAmount) { + super(withdrawalFee != null ? "error.msg.savingsaccount.transaction.insufficient.account.balance.withdraw" + : "error.msg.savingsaccount.transaction.insufficient.account.balance", "Insufficient account balance.", paramName, + accountBalance, withdrawalFee, transactionAmount); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java index 1d3ec78e4..d586f4a3c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java @@ -16,16 +16,16 @@ import org.springframework.transaction.annotation.Transactional; @Service public class ActivateSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired public ActivateSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { - this.savingAccountWritePlatformService = savingAccountWritePlatformService; + this.writePlatformService = savingAccountWritePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.activate(command.getSavingsId(), command); + return this.writePlatformService.activate(command.getSavingsId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java index a7fa216ba..f12498d12 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CalculateInterestSavingsAccountCommandHandler.java @@ -16,16 +16,17 @@ import org.springframework.transaction.annotation.Transactional; @Service public class CalculateInterestSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired - public CalculateInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { - this.savingAccountWritePlatformService = savingAccountWritePlatformService; + public CalculateInterestSavingsAccountCommandHandler( + final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.calculateInterest(command.getSavingsId(), command); + return this.writePlatformService.calculateInterest(command.getSavingsId()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java index b7d9cc611..133f8f2fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DepositSavingsAccountCommandHandler.java @@ -16,16 +16,16 @@ import org.springframework.transaction.annotation.Transactional; @Service public class DepositSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired - public DepositSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { - this.savingAccountWritePlatformService = savingAccountWritePlatformService; + public DepositSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.deposit(command.getSavingsId(), command); + return this.writePlatformService.deposit(command.getSavingsId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java index 603957f8b..a901133c0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java @@ -16,16 +16,16 @@ import org.springframework.transaction.annotation.Transactional; @Service public class PostInterestSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired - public PostInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { - this.savingAccountWritePlatformService = savingAccountWritePlatformService; + public PostInterestSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.postInterest(command.getSavingsId(), command); + return this.writePlatformService.postInterest(command.getSavingsId()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.java new file mode 100644 index 000000000..ce28cf4e3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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.SavingsApplicationProcessWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountApplicationApprovalCommandHandler implements NewCommandSourceHandler { + + private final SavingsApplicationProcessWritePlatformService writePlatformService; + + @Autowired + public SavingsAccountApplicationApprovalCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.approveApplication(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.java new file mode 100644 index 000000000..694dbc77c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationApprovalUndoCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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.SavingsApplicationProcessWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountApplicationApprovalUndoCommandHandler implements NewCommandSourceHandler { + + private final SavingsApplicationProcessWritePlatformService writePlatformService; + + @Autowired + public SavingsAccountApplicationApprovalUndoCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.undoApplicationApproval(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.java similarity index 62% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountCommandHandler.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.java index 0ba49a677..f0d835872 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/DeleteSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationDeletionCommandHandler.java @@ -8,25 +8,25 @@ 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.mifosplatform.portfolio.savings.service.SavingsApplicationProcessWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -public class DeleteSavingsAccountCommandHandler implements NewCommandSourceHandler { +public class SavingsAccountApplicationDeletionCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService; @Autowired - public DeleteSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + public SavingsAccountApplicationDeletionCommandHandler(final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) { this.savingAccountWritePlatformService = savingAccountWritePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.deleteSavingAccount(command.entityId()); + return this.savingAccountWritePlatformService.deleteApplication(command.entityId()); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.java similarity index 61% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountCommandHandler.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.java index 2857b2e74..1afe21684 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/UpdateSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationModificationCommandHandler.java @@ -8,25 +8,24 @@ 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.mifosplatform.portfolio.savings.service.SavingsApplicationProcessWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -public class UpdateSavingsAccountCommandHandler implements NewCommandSourceHandler { +public class SavingsAccountApplicationModificationCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService; @Autowired - public UpdateSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + public SavingsAccountApplicationModificationCommandHandler(final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) { this.savingAccountWritePlatformService = savingAccountWritePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.updateSavingAccount(command.entityId(), command); + return this.savingAccountWritePlatformService.modifyApplication(command.entityId(), command); } - -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.java new file mode 100644 index 000000000..4cef7bfb3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationRejectedCommandHandler.java @@ -0,0 +1,32 @@ +/** + * 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.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.SavingsApplicationProcessWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountApplicationRejectedCommandHandler implements NewCommandSourceHandler { + + private final SavingsApplicationProcessWritePlatformService writePlatformService; + + @Autowired + public SavingsAccountApplicationRejectedCommandHandler(final SavingsApplicationProcessWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.rejectApplication(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CreateSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.java similarity index 62% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CreateSavingsAccountCommandHandler.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.java index 85e073d13..f746185dc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/CreateSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationSubmittalCommandHandler.java @@ -8,24 +8,24 @@ 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.mifosplatform.portfolio.savings.service.SavingsApplicationProcessWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -public class CreateSavingsAccountCommandHandler implements NewCommandSourceHandler { +public class SavingsAccountApplicationSubmittalCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService; @Autowired - public CreateSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + public SavingsAccountApplicationSubmittalCommandHandler(final SavingsApplicationProcessWritePlatformService savingAccountWritePlatformService) { this.savingAccountWritePlatformService = savingAccountWritePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.createSavingAccount(command); + return this.savingAccountWritePlatformService.submitApplication(command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.java new file mode 100644 index 000000000..c1411e6ec --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/SavingsAccountApplicationWithdrawnByApplicantCommandHandler.java @@ -0,0 +1,33 @@ +/** + * 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.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.SavingsApplicationProcessWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountApplicationWithdrawnByApplicantCommandHandler implements NewCommandSourceHandler { + + private final SavingsApplicationProcessWritePlatformService writePlatformService; + + @Autowired + public SavingsAccountApplicationWithdrawnByApplicantCommandHandler( + final SavingsApplicationProcessWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.applicantWithdrawsFromApplication(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java index 73ff12bc8..2d625c7fd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/WithdrawSavingsAccountCommandHandler.java @@ -16,16 +16,16 @@ import org.springframework.transaction.annotation.Transactional; @Service public class WithdrawSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired - public WithdrawSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { - this.savingAccountWritePlatformService = savingAccountWritePlatformService; + public WithdrawSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.withdrawal(command.getSavingsId(), command); + return this.writePlatformService.withdrawal(command.getSavingsId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java new file mode 100644 index 000000000..243f13435 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountApplicationTransitionApiJsonValidator.java @@ -0,0 +1,129 @@ +/** + * 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.service; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Component +public final class SavingsAccountApplicationTransitionApiJsonValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public SavingsAccountApplicationTransitionApiJsonValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + public void validateApproval(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Set disbursementParameters = new HashSet(Arrays.asList("approvedOnDate", "note", "locale", "dateFormat")); + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication"); + + final JsonElement element = fromApiJsonHelper.parse(json); + final LocalDate approvedOnDate = fromApiJsonHelper.extractLocalDateNamed("approvedOnDate", element); + baseDataValidator.reset().parameter("approvedOnDate").value(approvedOnDate).notNull(); + + final String note = fromApiJsonHelper.extractStringNamed("note", element); + baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateRejection(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Set disbursementParameters = new HashSet(Arrays.asList("rejectedOnDate", "note", "locale", "dateFormat")); + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication"); + + final JsonElement element = fromApiJsonHelper.parse(json); + final LocalDate rejectedOnDate = fromApiJsonHelper.extractLocalDateNamed("rejectedOnDate", element); + baseDataValidator.reset().parameter("rejectedOnDate").value(rejectedOnDate).notNull(); + + final String note = fromApiJsonHelper.extractStringNamed("note", element); + baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateApplicantWithdrawal(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Set disbursementParameters = new HashSet(Arrays.asList("withdrawnOnDate", "note", "locale", "dateFormat")); + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("savingsaccountapplication"); + + final JsonElement element = fromApiJsonHelper.parse(json); + final LocalDate withdrawnOnDate = fromApiJsonHelper.extractLocalDateNamed("withdrawnOnDate", element); + baseDataValidator.reset().parameter("withdrawnOnDate").value(withdrawnOnDate).notNull(); + + final String note = fromApiJsonHelper.extractStringNamed("note", element); + baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForUndo(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Set undoSupportedParameters = new HashSet(Arrays.asList("note")); + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource("savingsaccountapplication.undo"); + final JsonElement element = fromApiJsonHelper.parse(json); + + final String note = "note"; + if (fromApiJsonHelper.parameterExists(note, element)) { + final String noteText = fromApiJsonHelper.extractStringNamed(note, element); + baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java index e8a9a6e14..240acf10c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformService.java @@ -18,7 +18,7 @@ public interface SavingsAccountReadPlatformService { SavingsAccountData retrieveOne(Long savingsId); - SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId); + SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly); SavingsAccountTransactionData retrieveDepositTransactionTemplate(Long savingsId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index ebea988ba..481794968 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.service; import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -23,23 +24,26 @@ import org.mifosplatform.infrastructure.core.service.PaginationHelper; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.organisation.staff.data.StaffData; +import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.SearchParameters; import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData; 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.SavingsCompoundingInterestPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestPostingPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; import org.mifosplatform.portfolio.savings.exception.SavingsAccountNotFoundException; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; @@ -47,6 +51,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; @Service public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountReadPlatformService { @@ -56,10 +61,11 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead private final ClientReadPlatformService clientReadPlatformService; private final GroupReadPlatformService groupReadPlatformService; private final SavingsProductReadPlatformService savingsProductReadPlatformService; + private final StaffReadPlatformService staffReadPlatformService; private final SavingsDropdownReadPlatformService dropdownReadPlatformService; private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; private final SavingsAccountTransactionsMapper transactionsMapper; - + private final PaginationHelper paginationHelper = new PaginationHelper(); private final SavingAccountMapper savingAccountMapper = new SavingAccountMapper(); @@ -67,15 +73,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService, final SavingsProductReadPlatformService savingProductReadPlatformService, - final SavingsDropdownReadPlatformService dropdownReadPlatformService) { + final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService) { this.context = context; this.jdbcTemplate = new JdbcTemplate(dataSource); this.clientReadPlatformService = clientReadPlatformService; this.groupReadPlatformService = groupReadPlatformService; this.savingsProductReadPlatformService = savingProductReadPlatformService; + this.staffReadPlatformService = staffReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; - transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); - transactionsMapper = new SavingsAccountTransactionsMapper(); + this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); + this.transactionsMapper = new SavingsAccountTransactionsMapper(); } @Override @@ -151,13 +158,40 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead public SavingAccountMapper() { final StringBuilder sqlBuilder = new StringBuilder(400); sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); - sqlBuilder.append("sa.status_enum as statusEnum, sa.activation_date as activationDate, "); sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); sqlBuilder.append("sp.id as productId, sp.name as productName, "); + sqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, "); + sqlBuilder.append("sa.status_enum as statusEnum, "); + + sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); + sqlBuilder.append("sbu.username as submittedByUsername,"); + sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); + + sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); + sqlBuilder.append("rbu.username as rejectedByUsername,"); + sqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); + + sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); + sqlBuilder.append("wbu.username as withdrawnByUsername,"); + sqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); + + sqlBuilder.append("sa.approvedon_date as approvedOnDate,"); + sqlBuilder.append("abu.username as approvedByUsername,"); + sqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); + + sqlBuilder.append("sa.activatedon_date as activatedOnDate,"); + sqlBuilder.append("avbu.username as activatedByUsername,"); + sqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); + + sqlBuilder.append("sa.closedon_date as closedOnDate,"); + sqlBuilder.append("cbu.username as closedByUsername,"); + sqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); + sqlBuilder.append("sa.currency_code as currencyCode, sa.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("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); @@ -180,10 +214,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); sqlBuilder.append("sa.account_balance_derived as accountBalance "); sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("left outer join m_client c ON c.id = sa.client_id "); - sqlBuilder.append("left outer join m_group g ON g.id = sa.group_id "); sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("left join m_client c ON c.id = sa.client_id "); + sqlBuilder.append("left join m_group g ON g.id = sa.group_id "); + sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id "); + sqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); + sqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); + sqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); + sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); + sqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid "); + sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); this.schemaSql = sqlBuilder.toString(); } @@ -199,11 +240,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String accountNo = rs.getString("accountNo"); final String externalId = rs.getString("externalId"); - final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); - - final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate"); - final Long groupId = JdbcSupport.getLong(rs, "groupId"); final String groupName = rs.getString("groupName"); final Long clientId = JdbcSupport.getLong(rs, "clientId"); @@ -212,6 +248,49 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Long productId = rs.getLong("productId"); final String productName = rs.getString("productName"); + final Long fieldOfficerId = rs.getLong("fieldOfficerId"); + final String fieldOfficerName = rs.getString("fieldOfficerName"); + + final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final String withdrawnByUsername = rs.getString("withdrawnByUsername"); + final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); + final String withdrawnByLastname = rs.getString("withdrawnByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, + rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, + withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, + activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, + closedByLastname); + final String currencyCode = rs.getString("currencyCode"); final String currencyName = rs.getString("currencyName"); final String currencyNameCode = rs.getString("currencyNameCode"); @@ -226,7 +305,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType @@ -277,27 +356,31 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance); - return SavingsAccountData.instance(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, - annualFeeNextDueDate, summary); + return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay, annualFeeNextDueDate, summary); } } @Override - public SavingsAccountData retrieveTemplate(final Long clientId, final Long groupId, final Long productId) { + public SavingsAccountData retrieveTemplate(final Long clientId, final Long groupId, final Long productId, + final boolean staffInSelectedOfficeOnly) { - context.authenticatedUser(); + AppUser loggedInUser = context.authenticatedUser(); + Long officeId = loggedInUser.getOffice().getId(); ClientData client = null; if (clientId != null) { client = this.clientReadPlatformService.retrieveOne(clientId); + officeId = client.officeId(); } GroupGeneralData group = null; if (groupId != null) { group = this.groupReadPlatformService.retrieveOne(groupId); + officeId = group.officeId(); } final Collection productOptions = this.savingsProductReadPlatformService.retrieveAllForLookup(); @@ -328,9 +411,35 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection transactions = null; - template = SavingsAccountData.withTemplateOptions(template, productOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + Collection fieldOfficerOptions = null; + + if (officeId != null) { + + if (staffInSelectedOfficeOnly) { + // only bring back loan officers in selected branch/office + Collection fieldOfficersInBranch = this.staffReadPlatformService + .retrieveAllLoanOfficersInOfficeById(officeId); + + if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) { + fieldOfficerOptions = new ArrayList(fieldOfficersInBranch); + } + } else { + // by default bring back all officers in selected + // branch/office as well as officers in office above + // this office + final boolean restrictToLoanOfficersOnly = true; + Collection loanOfficersInHierarchy = this.staffReadPlatformService + .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, restrictToLoanOfficersOnly); + + if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) { + fieldOfficerOptions = new ArrayList(loanOfficersInHierarchy); + } + } + } + + template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); } else { String clientName = null; @@ -345,6 +454,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead template = SavingsAccountData.withClientTemplate(clientId, clientName, groupId, groupName); + final Collection fieldOfficerOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; final Collection interestPostingPeriodTypeOptions = null; final Collection interestCalculationTypeOptions = null; @@ -354,9 +464,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection transactions = null; - template = SavingsAccountData.withTemplateOptions(template, productOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); + template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions); } return template; @@ -553,7 +663,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType .fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(SavingsInterestCalculationType @@ -604,16 +714,19 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead groupName = group.getName(); } + final Long fieldOfficerId = null; + final String fieldOfficerName = null; final SavingsAccountStatusEnumData status = null; - final LocalDate activationDate = null; final LocalDate annualFeeNextDueDate = null; final SavingsAccountSummaryData summary = null; - return SavingsAccountData.instance(null, null, null, status, activationDate, groupId, groupName, clientId, clientName, - productId, productName, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, - annualFeeNextDueDate, summary); + final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); + + return SavingsAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, + annualFeeOnMonthDay, annualFeeNextDueDate, summary); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index 9f09846f2..d6b3b50d6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -10,21 +10,15 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; public interface SavingsAccountWritePlatformService { - CommandProcessingResult createSavingAccount(JsonCommand command); - - CommandProcessingResult updateSavingAccount(Long accountId, JsonCommand command); - - CommandProcessingResult deleteSavingAccount(Long accountId); - CommandProcessingResult activate(Long savingsId, JsonCommand command); CommandProcessingResult deposit(Long savingsId, JsonCommand command); CommandProcessingResult withdrawal(Long savingsId, JsonCommand command); - + CommandProcessingResult applyAnnualFee(Long savingsId); - CommandProcessingResult calculateInterest(Long savingsId, JsonCommand command); + CommandProcessingResult calculateInterest(Long savingsId); - CommandProcessingResult postInterest(Long savingsId, JsonCommand command); + CommandProcessingResult postInterest(Long savingsId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 0b9adcf74..dac0e508b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -6,6 +6,8 @@ package org.mifosplatform.portfolio.savings.service; import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -19,66 +21,45 @@ import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatfo import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; -import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; -import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; -import org.mifosplatform.portfolio.client.domain.Client; -import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; -import org.mifosplatform.portfolio.group.domain.Group; -import org.mifosplatform.portfolio.group.domain.GroupRepository; -import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; -import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; -import org.mifosplatform.portfolio.savings.data.SavingsAccountDataValidator; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator; import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionRepository; -import org.mifosplatform.portfolio.savings.domain.SavingsProduct; -import org.mifosplatform.portfolio.savings.domain.SavingsProductRepository; -import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.mifosplatform.portfolio.savings.domain.SavingsHelper; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService { - private final static Logger logger = LoggerFactory.getLogger(SavingsAccountWritePlatformServiceJpaRepositoryImpl.class); - private final PlatformSecurityContext context; private final SavingsAccountRepositoryWrapper savingAccountRepository; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; private final SavingsAccountAssembler savingAccountAssembler; - private final SavingsAccountDataValidator savingsAccountDataValidator; private final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator; - private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; - private final ClientRepositoryWrapper clientRepository; - private final GroupRepository groupRepository; - private final SavingsProductRepository savingsProductRepository; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final SavingsHelper savingsHelper = new SavingsHelper(); + @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountTransactionRepository savingsAccountTransactionRepository, - final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountDataValidator savingsAccountDataValidator, + final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, - final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepositoryWrapper clientRepository, - final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository, final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService) { @@ -87,184 +68,29 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.savingAccountAssembler = savingAccountAssembler; this.savingsAccountTransactionDataValidator = savingsAccountTransactionDataValidator; - this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; - this.savingsAccountDataValidator = savingsAccountDataValidator; - this.clientRepository = clientRepository; - this.groupRepository = groupRepository; - this.savingsProductRepository = savingsProductRepository; this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; } - /* - * Guaranteed to throw an exception no matter what the data integrity issue - * is. - */ - private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dve) { - - final StringBuilder errorCodeBuilder = new StringBuilder("error.msg.").append(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - - final Throwable realCause = dve.getMostSpecificCause(); - if (realCause.getMessage().contains("sa_account_no_UNIQUE")) { - final String accountNo = command.stringValueOfParameterNamed("accountNo"); - errorCodeBuilder.append(".duplicate.accountNo"); - throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with accountNo " + accountNo - + " already exists", "accountNo", accountNo); - - } else if (realCause.getMessage().contains("sa_external_id_UNIQUE")) { - - final String externalId = command.stringValueOfParameterNamed("externalId"); - errorCodeBuilder.append(".duplicate.externalId"); - throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with externalId " + externalId - + " already exists", "externalId", externalId); - } - - errorCodeBuilder.append(".unknown.data.integrity.issue"); - logger.error(dve.getMessage(), dve); - throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Unknown data integrity issue with savings account."); - } - - @Transactional - @Override - public CommandProcessingResult createSavingAccount(final JsonCommand command) { - try { - this.context.authenticatedUser(); - this.savingsAccountDataValidator.validateForCreate(command.json()); - - final List existingTransactionIds = new ArrayList(); - final List existingReversedTransactionIds = new ArrayList(); - final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command, existingTransactionIds, - existingReversedTransactionIds); - this.savingAccountRepository.save(account); - - if (account.isAccountNumberRequiresAutoGeneration()) { - final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory - .determineLoanAccountNoGenerator(account.getId()); - account.updateAccountNo(accountNoGenerator.generate()); - - this.savingAccountRepository.save(account); - } - - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); - - final Long savingsId = account.getId(); - return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .build(); - } catch (DataAccessException dve) { - handleDataIntegrityIssues(command, dve); - return CommandProcessingResult.empty(); - } - } - - @Transactional - @Override - public CommandProcessingResult updateSavingAccount(final Long savingsId, final JsonCommand command) { - try { - this.context.authenticatedUser(); - this.savingsAccountDataValidator.validateForUpdate(command.json()); - - final Map changes = new LinkedHashMap(20); - - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); - - account.update(command, changes); - - if (!changes.isEmpty()) { - - if (changes.containsKey(SavingsApiConstants.clientIdParamName)) { - final Long clientId = command.longValueOfParameterNamed(SavingsApiConstants.clientIdParamName); - if (clientId != null) { - final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); - account.update(client); - } else { - final Client client = null; - account.update(client); - } - } - - if (changes.containsKey(SavingsApiConstants.groupIdParamName)) { - final Long groupId = command.longValueOfParameterNamed(SavingsApiConstants.groupIdParamName); - if (groupId != null) { - final Group group = this.groupRepository.findOne(groupId); - if (group == null) { throw new GroupNotFoundException(groupId); } - account.update(group); - } else { - final Group group = null; - account.update(group); - } - } - - if (changes.containsKey(SavingsApiConstants.productIdParamName)) { - final Long productId = command.longValueOfParameterNamed(SavingsApiConstants.productIdParamName); - final SavingsProduct product = this.savingsProductRepository.findOne(productId); - if (product == null) { throw new SavingsProductNotFoundException(productId); } - - account.update(product); - } - - this.savingAccountRepository.saveAndFlush(account); - } - - return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .with(changes) // - .build(); - } catch (DataAccessException dve) { - handleDataIntegrityIssues(command, dve); - return new CommandProcessingResult(Long.valueOf(-1)); - } - } - - @Transactional - @Override - public CommandProcessingResult deleteSavingAccount(final Long savingsId) { - - this.context.authenticatedUser(); - - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); - - this.savingAccountRepository.delete(account); - - return new CommandProcessingResultBuilder() // - .withEntityId(savingsId) // - .withOfficeId(account.officeId()) // - .withClientId(account.clientId()) // - .withGroupId(account.groupId()) // - .withSavingsId(savingsId) // - .build(); - } - @Transactional @Override public CommandProcessingResult activate(final Long savingsId, final JsonCommand command) { - this.context.authenticatedUser(); + final AppUser user = this.context.authenticatedUser(); this.savingsAccountTransactionDataValidator.validateActivation(command); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - account.activate(fmt, activationDate, existingReversedTransactionIds, existingReversedTransactionIds); - - this.savingAccountRepository.save(account); + final Map changes = account.activate(user, command, DateUtils.getLocalDateOfTenant(), + existingReversedTransactionIds, existingReversedTransactionIds); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(account); + } postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -274,6 +100,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withClientId(account.clientId()) // .withGroupId(account.groupId()) // .withSavingsId(savingsId) // + .with(changes) // .build(); } @@ -285,7 +112,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsAccountTransactionDataValidator.validate(command); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -298,8 +125,18 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Map changes = new LinkedHashMap(); PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds, existingReversedTransactionIds, paymentDetail); + + if (account.isBeforeLastPostingPeriod(transactionDate)) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(this.savingsHelper, mc, today); + } + final Long transactionId = saveTransactionToGenerateTransactionId(deposit); this.savingAccountRepository.save(account); @@ -329,7 +166,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsAccountTransactionDataValidator.validate(command); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); @@ -342,8 +179,18 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Map changes = new LinkedHashMap(); PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds, existingReversedTransactionIds, paymentDetail); + + if (account.isBeforeLastPostingPeriod(transactionDate)) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); + } else { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + account.calculateInterestUsing(this.savingsHelper, mc, today); + } + final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal); this.savingAccountRepository.save(account); @@ -363,7 +210,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult applyAnnualFee(final Long savingsId) { - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final LocalDate transactionDate = LocalDate.now(); @@ -372,8 +219,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - final SavingsAccountTransaction annualFee = account.addAnnualFee(fmt, transactionDate, existingTransactionIds, - existingReversedTransactionIds); + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + final SavingsAccountTransaction annualFee = account.addAnnualFee(this.savingsHelper, mc, fmt, transactionDate, + existingTransactionIds, existingReversedTransactionIds); final Long transactionId = saveTransactionToGenerateTransactionId(annualFee); this.savingAccountRepository.save(account); @@ -388,14 +236,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .build(); } + @Transactional @Override - public CommandProcessingResult calculateInterest(final Long savingsId, final JsonCommand command) { + public CommandProcessingResult calculateInterest(final Long savingsId) { this.context.authenticatedUser(); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.calculateInterest(today); + + final MathContext mc = new MathContext(15, RoundingMode.HALF_EVEN); + + account.calculateInterestUsing(this.savingsHelper, mc, today); + this.savingAccountRepository.save(account); return new CommandProcessingResultBuilder() // @@ -409,15 +262,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult postInterest(final Long savingsId, final JsonCommand command) { + public CommandProcessingResult postInterest(final Long savingsId) { this.context.authenticatedUser(); final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(today, existingTransactionIds, existingReversedTransactionIds); + final MathContext mc = new MathContext(10, RoundingMode.HALF_EVEN); + + account.postInterest(this.savingsHelper, mc, today, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java new file mode 100644 index 000000000..c68919cc5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformService.java @@ -0,0 +1,26 @@ +/** + * 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.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface SavingsApplicationProcessWritePlatformService { + + CommandProcessingResult submitApplication(JsonCommand command); + + CommandProcessingResult modifyApplication(Long savingsId, JsonCommand command); + + CommandProcessingResult deleteApplication(Long savingsId); + + CommandProcessingResult approveApplication(Long savingsId, JsonCommand command); + + CommandProcessingResult undoApplicationApproval(Long savingsId, JsonCommand command); + + CommandProcessingResult rejectApplication(Long savingsId, JsonCommand command); + + CommandProcessingResult applicantWithdrawsFromApplication(Long savingsId, JsonCommand command); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..f079a10fa --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,382 @@ +/** + * 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.service; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; +import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.portfolio.group.domain.GroupRepository; +import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; +import org.mifosplatform.portfolio.note.domain.Note; +import org.mifosplatform.portfolio.note.domain.NoteRepository; +import org.mifosplatform.portfolio.savings.SavingsApiConstants; +import org.mifosplatform.portfolio.savings.data.SavingsAccountDataValidator; +import org.mifosplatform.portfolio.savings.domain.SavingsAccount; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.mifosplatform.portfolio.savings.domain.SavingsProduct; +import org.mifosplatform.portfolio.savings.domain.SavingsProductRepository; +import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException; +import org.mifosplatform.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl implements SavingsApplicationProcessWritePlatformService { + + private final static Logger logger = LoggerFactory.getLogger(SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.class); + + private final PlatformSecurityContext context; + private final SavingsAccountRepositoryWrapper savingAccountRepository; + private final SavingsAccountAssembler savingAccountAssembler; + private final SavingsAccountDataValidator savingsAccountDataValidator; + private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; + private final ClientRepositoryWrapper clientRepository; + private final GroupRepository groupRepository; + private final SavingsProductRepository savingsProductRepository; + private final NoteRepository noteRepository; + private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; + + @Autowired + public SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, + final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountAssembler savingAccountAssembler, + final SavingsAccountDataValidator savingsAccountDataValidator, + final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepositoryWrapper clientRepository, + final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository, + final NoteRepository noteRepository, + final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + final JournalEntryWritePlatformService journalEntryWritePlatformService) { + this.context = context; + this.savingAccountRepository = savingAccountRepository; + this.savingAccountAssembler = savingAccountAssembler; + this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; + this.savingsAccountDataValidator = savingsAccountDataValidator; + this.clientRepository = clientRepository; + this.groupRepository = groupRepository; + this.savingsProductRepository = savingsProductRepository; + this.noteRepository = noteRepository; + this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator; + this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; + } + + /* + * Guaranteed to throw an exception no matter what the data integrity issue + * is. + */ + private void handleDataIntegrityIssues(final JsonCommand command, final DataAccessException dve) { + + final StringBuilder errorCodeBuilder = new StringBuilder("error.msg.").append(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + + final Throwable realCause = dve.getMostSpecificCause(); + if (realCause.getMessage().contains("sa_account_no_UNIQUE")) { + final String accountNo = command.stringValueOfParameterNamed("accountNo"); + errorCodeBuilder.append(".duplicate.accountNo"); + throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with accountNo " + accountNo + + " already exists", "accountNo", accountNo); + + } else if (realCause.getMessage().contains("sa_external_id_UNIQUE")) { + + final String externalId = command.stringValueOfParameterNamed("externalId"); + errorCodeBuilder.append(".duplicate.externalId"); + throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Savings account with externalId " + externalId + + " already exists", "externalId", externalId); + } + + errorCodeBuilder.append(".unknown.data.integrity.issue"); + logger.error(dve.getMessage(), dve); + throw new PlatformDataIntegrityException(errorCodeBuilder.toString(), "Unknown data integrity issue with savings account."); + } + + @Transactional + @Override + public CommandProcessingResult submitApplication(final JsonCommand command) { + try { + this.savingsAccountDataValidator.validateForSubmit(command.json()); + + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command); + this.savingAccountRepository.save(account); + + if (account.isAccountNumberRequiresAutoGeneration()) { + final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory + .determineSavingsAccountNoGenerator(account.getId()); + account.updateAccountNo(accountNoGenerator.generate()); + + this.savingAccountRepository.save(account); + } + + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + + final Long savingsId = account.getId(); + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); + } catch (DataAccessException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + @Transactional + @Override + public CommandProcessingResult modifyApplication(final Long savingsId, final JsonCommand command) { + try { + this.savingsAccountDataValidator.validateForUpdate(command.json()); + + final Map changes = new LinkedHashMap(20); + + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + + account.update(command, changes); + + if (!changes.isEmpty()) { + + if (changes.containsKey(SavingsApiConstants.clientIdParamName)) { + final Long clientId = command.longValueOfParameterNamed(SavingsApiConstants.clientIdParamName); + if (clientId != null) { + final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + account.update(client); + } else { + final Client client = null; + account.update(client); + } + } + + if (changes.containsKey(SavingsApiConstants.groupIdParamName)) { + final Long groupId = command.longValueOfParameterNamed(SavingsApiConstants.groupIdParamName); + if (groupId != null) { + final Group group = this.groupRepository.findOne(groupId); + if (group == null) { throw new GroupNotFoundException(groupId); } + account.update(group); + } else { + final Group group = null; + account.update(group); + } + } + + if (changes.containsKey(SavingsApiConstants.productIdParamName)) { + final Long productId = command.longValueOfParameterNamed(SavingsApiConstants.productIdParamName); + final SavingsProduct product = this.savingsProductRepository.findOne(productId); + if (product == null) { throw new SavingsProductNotFoundException(productId); } + + account.update(product); + } + + this.savingAccountRepository.saveAndFlush(account); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } catch (DataAccessException dve) { + handleDataIntegrityIssues(command, dve); + return new CommandProcessingResult(Long.valueOf(-1)); + } + } + + @Transactional + @Override + public CommandProcessingResult deleteApplication(final Long savingsId) { + + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + + List relatedNotes = this.noteRepository.findBySavingsAccountId(savingsId); + this.noteRepository.deleteInBatch(relatedNotes); + + this.savingAccountRepository.delete(account); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult approveApplication(final Long savingsId, final JsonCommand command) { + + final AppUser currentUser = context.authenticatedUser(); + + this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json()); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); + + final Map changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getLocalDateOfTenant()); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(savingsAccount); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.savingNote(savingsAccount, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult undoApplicationApproval(final Long savingsId, final JsonCommand command) { + + context.authenticatedUser(); + + this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json()); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); + + final Map changes = savingsAccount.undoApplicationApproval(); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(savingsAccount); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.savingNote(savingsAccount, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult rejectApplication(final Long savingsId, final JsonCommand command) { + + final AppUser currentUser = context.authenticatedUser(); + + this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json()); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); + + final Map changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getLocalDateOfTenant()); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(savingsAccount); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.savingNote(savingsAccount, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult applicantWithdrawsFromApplication(final Long savingsId, final JsonCommand command) { + final AppUser currentUser = context.authenticatedUser(); + + this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json()); + + final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); + + final Map changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command, + DateUtils.getLocalDateOfTenant()); + if (!changes.isEmpty()) { + this.savingAccountRepository.save(savingsAccount); + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + Note note = Note.savingNote(savingsAccount, noteText); + changes.put("note", noteText); + this.noteRepository.save(note); + } + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsId) // + .withOfficeId(savingsAccount.officeId()) // + .withClientId(savingsAccount.clientId()) // + .withGroupId(savingsAccount.groupId()) // + .withSavingsId(savingsId) // + .with(changes) // + .build(); + } + + private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final MonetaryCurrency currency = savingsAccount.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); + + final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds); + journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java index 2ab3f1fca..8e884d64c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java @@ -10,12 +10,12 @@ import java.util.Collection; import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; -import org.mifosplatform.portfolio.savings.domain.SavingsCompoundingInterestPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestPostingPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; -import org.mifosplatform.portfolio.savings.domain.SavingsWithdrawalFeesType; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.springframework.stereotype.Service; @Service @@ -47,13 +47,13 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe public Collection retrieveCompoundingInterestPeriodTypeOptions() { List allowedOptions = Arrays.asList( // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.WEEKLY), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BIWEEKLY), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL), // - SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST) // +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.WEEKLY), // +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BIWEEKLY), // + SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY) +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY), // +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL), // +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL), // +// SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST) // ); return allowedOptions; @@ -62,10 +62,9 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe @Override public Collection retrieveInterestPostingPeriodTypeOptions() { List allowedOptions = Arrays.asList( // - SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType.MONTHLY), // - SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType.QUATERLY), // - SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType.BI_ANNUAL), // - SavingsEnumerations.interestPostingPeriodType(SavingsInterestPostingPeriodType.ANNUAL) // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.QUATERLY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.ANNUAL) // ); return allowedOptions; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java index 57ee1e872..aa87c8286 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsEnumerations.java @@ -6,16 +6,16 @@ package org.mifosplatform.portfolio.savings.service; import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.savings.SavingsAccountTransactionType; +import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType; +import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType; +import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; +import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; 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.SavingsCompoundingInterestPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationType; -import org.mifosplatform.portfolio.savings.domain.SavingsInterestPostingPeriodType; -import org.mifosplatform.portfolio.savings.domain.SavingsPeriodFrequencyType; -import org.mifosplatform.portfolio.savings.domain.SavingsWithdrawalFeesType; public class SavingsEnumerations { @@ -94,59 +94,81 @@ public class SavingsEnumerations { public static SavingsAccountStatusEnumData status(final SavingsAccountStatusType type) { + final boolean submittedAndPendingApproval = type.isSubmittedAndPendingApproval(); + final boolean isApproved = type.isApproved(); + final boolean isRejected = type.isRejected(); + final boolean isWithdrawnByApplicant = type.isApplicationWithdrawnByApplicant(); + final boolean isActive = type.isActive(); + final boolean isClosed = type.isClosed(); + SavingsAccountStatusEnumData optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), - SavingsAccountStatusType.INVALID.getCode(), "Invalid", false, false, false); + SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); switch (type) { case INVALID: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), - SavingsAccountStatusType.INVALID.getCode(), "Invalid", type.isUnactivated(), type.isActive(), type.isClosed()); + SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); break; - case UNACTIVATED: - optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.UNACTIVATED.getValue().longValue(), - SavingsAccountStatusType.UNACTIVATED.getCode(), "Unactivated", type.isUnactivated(), type.isActive(), - type.isClosed()); + case SUBMITTED_AND_PENDING_APPROVAL: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue() + .longValue(), SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval", + submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed); + break; + case REJECTED: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.REJECTED.getValue().longValue(), + SavingsAccountStatusType.REJECTED.getCode(), "Rejected", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); + break; + case WITHDRAWN_BY_APPLICANT: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue().longValue(), + SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getCode(), "Withdrawn by applicant", submittedAndPendingApproval, + isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed); + break; + case APPROVED: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.APPROVED.getValue().longValue(), + SavingsAccountStatusType.APPROVED.getCode(), "Approved", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); break; case ACTIVE: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.ACTIVE.getValue().longValue(), - SavingsAccountStatusType.ACTIVE.getCode(), "Active", type.isUnactivated(), type.isActive(), type.isClosed()); + SavingsAccountStatusType.ACTIVE.getCode(), "Active", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); break; case CLOSED: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.CLOSED.getValue().longValue(), - SavingsAccountStatusType.CLOSED.getCode(), "Closed", type.isUnactivated(), type.isActive(), type.isClosed()); + SavingsAccountStatusType.CLOSED.getCode(), "Closed", submittedAndPendingApproval, isApproved, isRejected, + isWithdrawnByApplicant, isActive, isClosed); break; } return optionData; } public static EnumOptionData interestPostingPeriodType(final Integer type) { - return interestPostingPeriodType(SavingsInterestPostingPeriodType.fromInt(type)); + return interestPostingPeriodType(SavingsPostingInterestPeriodType.fromInt(type)); } - public static EnumOptionData interestPostingPeriodType(final SavingsInterestPostingPeriodType type) { + public static EnumOptionData interestPostingPeriodType(final SavingsPostingInterestPeriodType type) { final String codePrefix = "savings.interest.posting.period."; - EnumOptionData optionData = new EnumOptionData(SavingsInterestPostingPeriodType.INVALID.getValue().longValue(), - SavingsInterestPostingPeriodType.INVALID.getCode(), "Invalid"); + EnumOptionData optionData = new EnumOptionData(SavingsPostingInterestPeriodType.INVALID.getValue().longValue(), + SavingsPostingInterestPeriodType.INVALID.getCode(), "Invalid"); switch (type) { case INVALID: break; case MONTHLY: - optionData = new EnumOptionData(SavingsInterestPostingPeriodType.MONTHLY.getValue().longValue(), codePrefix - + SavingsInterestPostingPeriodType.MONTHLY.getCode(), "Monthly"); + optionData = new EnumOptionData(SavingsPostingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix + + SavingsPostingInterestPeriodType.MONTHLY.getCode(), "Monthly"); break; case QUATERLY: - optionData = new EnumOptionData(SavingsInterestPostingPeriodType.QUATERLY.getValue().longValue(), codePrefix - + SavingsInterestPostingPeriodType.QUATERLY.getCode(), "Quarterly"); - break; - case BI_ANNUAL: - optionData = new EnumOptionData(SavingsInterestPostingPeriodType.BI_ANNUAL.getValue().longValue(), codePrefix - + SavingsInterestPostingPeriodType.BI_ANNUAL.getCode(), "Semi-Annual"); + optionData = new EnumOptionData(SavingsPostingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix + + SavingsPostingInterestPeriodType.QUATERLY.getCode(), "Quarterly"); break; case ANNUAL: - optionData = new EnumOptionData(SavingsInterestPostingPeriodType.ANNUAL.getValue().longValue(), codePrefix - + SavingsInterestPostingPeriodType.ANNUAL.getCode(), "Annually"); + optionData = new EnumOptionData(SavingsPostingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix + + SavingsPostingInterestPeriodType.ANNUAL.getCode(), "Annually"); break; } @@ -170,35 +192,51 @@ public class SavingsEnumerations { optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.DAILY.getValue().longValue(), codePrefix + SavingsCompoundingInterestPeriodType.DAILY.getCode(), "Daily"); break; - case WEEKLY: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.WEEKLY.getValue().longValue(), codePrefix - + SavingsCompoundingInterestPeriodType.WEEKLY.getCode(), "Weekly"); - break; - case BIWEEKLY: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.BIWEEKLY.getValue().longValue(), codePrefix - + SavingsCompoundingInterestPeriodType.BIWEEKLY.getCode(), "Bi-Weekly"); - break; + // case WEEKLY: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.WEEKLY.getValue().longValue(), + // codePrefix + // + SavingsCompoundingInterestPeriodType.WEEKLY.getCode(), + // "Weekly"); + // break; + // case BIWEEKLY: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.BIWEEKLY.getValue().longValue(), + // codePrefix + // + SavingsCompoundingInterestPeriodType.BIWEEKLY.getCode(), + // "Bi-Weekly"); + // break; case MONTHLY: optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix + SavingsCompoundingInterestPeriodType.MONTHLY.getCode(), "Monthly"); break; - case QUATERLY: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.QUATERLY.getValue().longValue(), codePrefix - + SavingsCompoundingInterestPeriodType.QUATERLY.getCode(), "Quarterly"); - break; - case BI_ANNUAL: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.BI_ANNUAL.getValue().longValue(), codePrefix - + SavingsCompoundingInterestPeriodType.BI_ANNUAL.getCode(), "Semi-Annual"); - break; - case ANNUAL: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.ANNUAL.getValue().longValue(), codePrefix - + SavingsCompoundingInterestPeriodType.ANNUAL.getCode(), "Annually"); - break; - case NO_COMPOUNDING_SIMPLE_INTEREST: - optionData = new EnumOptionData(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getValue().longValue(), - codePrefix + SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getCode(), - "No Compounding - Simple Interest"); - break; + // case QUATERLY: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.QUATERLY.getValue().longValue(), + // codePrefix + // + SavingsCompoundingInterestPeriodType.QUATERLY.getCode(), + // "Quarterly"); + // break; + // case BI_ANNUAL: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.BI_ANNUAL.getValue().longValue(), + // codePrefix + // + SavingsCompoundingInterestPeriodType.BI_ANNUAL.getCode(), + // "Semi-Annual"); + // break; + // case ANNUAL: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.ANNUAL.getValue().longValue(), + // codePrefix + // + SavingsCompoundingInterestPeriodType.ANNUAL.getCode(), "Annually"); + // break; + // case NO_COMPOUNDING_SIMPLE_INTEREST: + // optionData = new + // EnumOptionData(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getValue().longValue(), + // codePrefix + + // SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST.getCode(), + // "No Compounding - Simple Interest"); + // break; } return optionData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java index 43740ddaa..087c799c1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java @@ -24,7 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountingRuleParamName; + +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; @Service public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements SavingsProductWritePlatformService { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java index ea0a210bc..29caa53b3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/scheduledjobs/service/ScheduledJobRunnerServiceImpl.java @@ -205,5 +205,4 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by update: " + savingsIds.size()); } - } \ No newline at end of file diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java index 52897458f..1a4a26e5b 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MonetaryCurrencyBuilder.java @@ -4,20 +4,20 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; public class MonetaryCurrencyBuilder { - private String code = "XOF"; - private int digitsAfterDecimal = 0; + private String code = "XOF"; + private int digitsAfterDecimal = 0; - public MonetaryCurrency build() { - return new MonetaryCurrency(code, digitsAfterDecimal); - } + public MonetaryCurrency build() { + return new MonetaryCurrency(code, digitsAfterDecimal); + } - public MonetaryCurrencyBuilder withCode(String withCode) { - this.code = withCode; - return this; - } + public MonetaryCurrencyBuilder withCode(final String withCode) { + this.code = withCode; + return this; + } - public MonetaryCurrencyBuilder withDigitsAfterDecimal(int withDigitsAfterDecimal) { - this.digitsAfterDecimal = withDigitsAfterDecimal; - return this; - } + public MonetaryCurrencyBuilder withDigitsAfterDecimal(final int withDigitsAfterDecimal) { + this.digitsAfterDecimal = withDigitsAfterDecimal; + return this; + } } diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MoneyBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MoneyBuilder.java index c05798033..cc762ec87 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MoneyBuilder.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/MoneyBuilder.java @@ -7,20 +7,20 @@ import org.mifosplatform.organisation.monetary.domain.Money; public class MoneyBuilder { - private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build(); - private BigDecimal newAmount = BigDecimal.ZERO; - - public Money build() { - return Money.of(currencyDetail, newAmount); - } + private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build(); + private BigDecimal newAmount = BigDecimal.ZERO; - public MoneyBuilder with(MonetaryCurrency withDetail) { - this.currencyDetail = withDetail; - return this; - } - - public MoneyBuilder with(String withAmount) { - this.newAmount = BigDecimal.valueOf(Double.valueOf(withAmount)); - return this; - } + public Money build() { + return Money.of(currencyDetail, newAmount); + } + + public MoneyBuilder with(final MonetaryCurrency withDetail) { + this.currencyDetail = withDetail; + return this; + } + + public MoneyBuilder with(final String withAmount) { + this.newAmount = BigDecimal.valueOf(Double.valueOf(withAmount)); + return this; + } } \ No newline at end of file