From 63eba40115da53a198777de4c4b23f3776423e6a Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Mon, 25 Mar 2013 17:39:01 +0000 Subject: [PATCH] MIFOSX-247: add basic lifecycle state to savings account and support some constrains around deposits and withdrawals. --- api-docs/apiLive.htm | 77 +++++- .../0001a-mifosplatform-core-ddl-latest.sql | 4 + .../0002-mifosx-base-reference-data-utf8.sql | 44 ++- ...osx-permissions-and-authorisation-utf8.sql | 4 +- .../1-add-status-to-savings-account.sql | 16 ++ .../java/ClientLoanIntegrationTest.java | 44 +-- .../commands/domain/CommandSource.java | 7 +- .../commands/domain/CommandWrapper.java | 30 +- .../service/CommandWrapperBuilder.java | 13 +- .../SynchronousCommandProcessingService.java | 5 +- .../infrastructure/core/api/JsonCommand.java | 5 + .../core/data/ApiGlobalErrorResponse.java | 22 -- .../PlatformApiDataValidationException.java | 13 +- ...tformApiDataValidationExceptionMapper.java | 6 +- ...pdateOfficeWithParentOfficeSameAsSelf.java | 9 +- .../RootOfficeParentCannotBeUpdated.java | 9 +- .../domain/PeriodFrequencyType.java | 32 +-- ...SavingsAccountTransactionsApiResource.java | 5 +- .../api/SavingsAccountsApiResource.java | 29 ++ .../savings/api/SavingsApiConstants.java | 21 +- .../savings/data/SavingsAccountData.java | 50 ++-- .../data/SavingsAccountDataValidator.java | 12 + .../data/SavingsAccountStatusEnumData.java | 35 +++ ...avingsAccountTransactionDataValidator.java | 24 +- .../savings/domain/SavingsAccount.java | 257 ++++++++++++++++-- .../domain/SavingsAccountAssembler.java | 21 +- .../domain/SavingsAccountStatusType.java | 66 +++++ .../savings/domain/SavingsAccountSummary.java | 2 +- ...lidSavingsAccountTransactionException.java | 15 - .../ActivateSavingsAccountCommandHandler.java | 26 ++ .../DepositSavingsAccountCommandHandler.java | 2 +- .../WithdrawSavingsAccountCommandHandler.java | 2 +- ...SavingsAccountReadPlatformServiceImpl.java | 25 +- .../SavingsAccountWritePlatformService.java | 2 + ...WritePlatformServiceJpaRepositoryImpl.java | 63 ++++- .../savings/service/SavingsEnumerations.java | 33 +++ 36 files changed, 809 insertions(+), 221 deletions(-) create mode 100644 mifosng-db/patches/1-add-status-to-savings-account.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountTransactionException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 6bacd8f0f..dac8e30e2 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -7118,6 +7118,12 @@ Content-Type: application/json externalId A place to put an external reference for this savings account e.g. The ID another system uses. If provided, it must be unique. + active + Indicates whether this savings account should go into active state or not automatically + + activationDate + If active is true the activationDate must be provided. If the savings account has an entry for minRequiredOpeningBalance then a deposit for that amount with activationDate as the date is automatically entered. + interestRate The default interest rate set when creating savings accounts of this type of product. e.g. 5% Per year @@ -7257,7 +7263,7 @@ Content-Type: application/json
- +
Mandatory Fields
clientId or groupId, productId, accountNo, externalId
clientId or groupId, productId, accountNo, externalId, active, (activationDate if active is true)

@@ -7273,27 +7279,29 @@ Content-Type: application/json

Minimal request: accountNo auto generated, remaining details inherited from savings product.

POST https://Domain Name/api/v1/savingsaccounts - POST savingaccount -Content-Type: application/json -Request Body: -{ - "clientId": 1, - "productId": 1 -} - -

Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.

- POST savingaccount + POST savingsaccount Content-Type: application/json Request Body: { "clientId": 1, "productId": 1, + "active": false +} + +

Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.

+ POST savingsaccount +Content-Type: application/json +Request Body: +{ + "clientId": 1, + "productId": 1, + "active": false, "accountNo": "SA000023", "externalId": "SYS-23" }

Full request: accountNo provided (must be unique), remaining details override details from savings product (except currency).

- POST savingaccount + POST savingsaccount Content-Type: application/json Request Body: { @@ -7301,7 +7309,10 @@ Request Body: "productId": 1, "accountNo": "SA000023", "externalId": "SYS-23", - "locale": "en", + "active": true, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "activationDate": "01 March 2011", "interestRate": "5.65", "interestRatePeriodFrequencyType": 2, "minRequiredOpeningBalance": "1,000", @@ -7313,6 +7324,35 @@ Request Body: { "officeId": 1, "clientId": 1, + "savingsId": 1, + "resourceId": 1 +} + +
+ + +   +
+
+

Activate a Savings Account

+

Activates a savings account.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=activate + POST savingsaccount/1?command=activate +Content-Type: application/json +Request Body: +{ + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "activationDate": "01 March 2011" +} + + +{ + "officeId": 1, + "clientId": 1, + "savingsId": 1, "resourceId": 1 } @@ -11436,6 +11476,14 @@ Content-Type: application/json
+ + + + + + + + @@ -11443,7 +11491,7 @@ Content-Type: application/json - + @@ -11467,7 +11515,6 @@ Content-Type: application/json - diff --git a/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql b/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql index 4a144c76a..da3731065 100644 --- a/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql +++ b/mifosng-db/0001a-mifosplatform-core-ddl-latest.sql @@ -729,6 +729,7 @@ CREATE TABLE `m_savings_product` ( UNIQUE KEY `sp_unq_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `m_savings_account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `account_no` varchar(20) NOT NULL, @@ -736,6 +737,8 @@ CREATE TABLE `m_savings_account` ( `client_id` bigint(20) DEFAULT NULL, `group_id` bigint(20) DEFAULT NULL, `product_id` bigint(20) DEFAULT NULL, + `status_enum` SMALLINT(5) NOT NULL DEFAULT 300, + `activation_date` DATE DEFAULT NULL, `currency_code` varchar(3) NOT NULL, `currency_digits` smallint(5) NOT NULL, `nominal_interest_rate_per_period` decimal(19,6) NOT NULL, @@ -744,6 +747,7 @@ CREATE TABLE `m_savings_account` ( `min_required_opening_balance` decimal(19,6) DEFAULT NULL, `lockin_period_frequency` decimal(19,6) DEFAULT NULL, `lockin_period_frequency_enum` smallint(5) DEFAULT NULL, + `lockedin_until_date_derived` DATE DEFAULT NULL, `total_deposits_derived` decimal(19,6) DEFAULT NULL, `total_withdrawals_derived` decimal(19,6) DEFAULT NULL, `total_interest_posted_derived` decimal(19,6) DEFAULT NULL, diff --git a/mifosng-db/0002-mifosx-base-reference-data-utf8.sql b/mifosng-db/0002-mifosx-base-reference-data-utf8.sql index f6e7d9b0b..23819b5d5 100644 --- a/mifosng-db/0002-mifosx-base-reference-data-utf8.sql +++ b/mifosng-db/0002-mifosx-base-reference-data-utf8.sql @@ -17,19 +17,36 @@ INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'), ('amortization_method_enum',1,'Equal installments','Equal installments'), -('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'), -('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'), -('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'), -('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'), -('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'), -('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'), -('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'), -('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'), -('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'), -('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'), -('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'), -('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'), -('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'); +('interest_calculated_in_period_enum',0,'Daily','Daily'), +('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'), +('interest_method_enum',0,'Declining Balance','Declining Balance'), +('interest_method_enum',1,'Flat','Flat'), +('interest_period_frequency_enum',2,'Per month','Per month'), +('interest_period_frequency_enum',3,'Per year','Per year'), +('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'), +('loan_status_id',200,'Approved','Approved'), +('loan_status_id',300,'Active','Active'), +('loan_status_id',400,'Withdrawn by client','Withdrawn by client'), +('loan_status_id',500,'Rejected','Rejected'), +('loan_status_id',600,'Closed','Closed'), +('loan_status_id',601,'Written-Off','Written-Off'), +('loan_status_id',602,'Rescheduled','Rescheduled'), +('loan_status_id',700,'Overpaid','Overpaid'), +('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'), +('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'), +('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'), +('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'), +('processing_result_enum',0,'invalid','Invalid'), +('processing_result_enum',1,'processed','Processed'), +('processing_result_enum',2,'awaiting.approval','Awaiting Approval'), +('processing_result_enum',3,'rejected','Rejected'), +('repayment_period_frequency_enum',0,'Days','Days'), +('repayment_period_frequency_enum',1,'Weeks','Weeks'), +('repayment_period_frequency_enum',2,'Months','Months'), +('term_period_frequency_enum',0,'Days','Days'), +('term_period_frequency_enum',1,'Weeks','Weeks'), +('term_period_frequency_enum',2,'Months','Months'), +('term_period_frequency_enum',3,'Years','Years'); INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) VALUES ('transaction_type_enum', '1', 'Disbursement', 'Disbursement'); @@ -64,7 +81,6 @@ VALUES ('transaction_type_enum', '10', 'Apply Charges', 'Apply Charges'); INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`) VALUES ('transaction_type_enum', '11', 'Apply Interest', 'Apply Interest'); - INSERT INTO `m_currency` (`id`,`code`,`decimal_places`,`display_symbol`,`name`, `internationalized_name_code`) VALUES diff --git a/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql b/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql index 652fa17d6..6836d79a6 100644 --- a/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql +++ b/mifosng-db/0003-mifosx-permissions-and-authorisation-utf8.sql @@ -263,7 +263,9 @@ INSERT INTO `m_permission` ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'), ('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'), -('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL', '0'); +('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'WITHDRAWAL', '0'), +('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'), +('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'); -- == accounting related permissions INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES diff --git a/mifosng-db/patches/1-add-status-to-savings-account.sql b/mifosng-db/patches/1-add-status-to-savings-account.sql new file mode 100644 index 000000000..282789e08 --- /dev/null +++ b/mifosng-db/patches/1-add-status-to-savings-account.sql @@ -0,0 +1,16 @@ +ALTER TABLE `m_savings_account` +ADD COLUMN `status_enum` SMALLINT(5) NOT NULL DEFAULT 300 AFTER `product_id` ; + +ALTER TABLE `m_savings_account` +ADD COLUMN `activation_date` DATE DEFAULT NULL AFTER `status_enum`; + +ALTER TABLE `m_savings_account` +ADD COLUMN `lockedin_until_date_derived` DATE DEFAULT NULL AFTER `lockin_period_frequency_enum`; + +ALTER TABLE `m_portfolio_command_source` +ADD COLUMN `savings_id` BIGINT(20) NULL DEFAULT NULL AFTER `loan_id` ; + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES +('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ACTIVATE', '1'), +('transaction_savings', 'ACTIVATE_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'ACTIVATE', '0'); \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/ClientLoanIntegrationTest.java index 649873eb9..9709cd442 100644 --- a/mifosng-provider/src/integrationTest/java/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/ClientLoanIntegrationTest.java @@ -1,4 +1,17 @@ +import static com.jayway.restassured.RestAssured.given; +import static com.jayway.restassured.RestAssured.post; +import static com.jayway.restassured.path.json.JsonPath.from; +import static junit.framework.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Random; + +import org.junit.Before; +import org.junit.Test; + import com.google.gson.Gson; import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.builder.ResponseSpecBuilder; @@ -6,22 +19,11 @@ import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.specification.RequestSpecification; import com.jayway.restassured.specification.ResponseSpecification; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Random; - -import static com.jayway.restassured.RestAssured.given; -import static com.jayway.restassured.RestAssured.post; -import static com.jayway.restassured.path.json.JsonPath.from; -import static junit.framework.Assert.assertEquals; /** * Client Loan Integration Test for checking Loan Application Repayment Schedule. */ +@SuppressWarnings({ "rawtypes", "unchecked" }) public class ClientLoanIntegrationTest { String basicAuthKey; ResponseSpecification responseSpec; @@ -70,7 +72,7 @@ public class ClientLoanIntegrationTest { return from(json).get("clientId"); } - private void checkClientCreatedOnServer(Integer generatedClientID) { + private void checkClientCreatedOnServer(final Integer generatedClientID) { System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n"); given().spec(requestSpec) .expect().spec(responseSpec) @@ -86,14 +88,14 @@ public class ClientLoanIntegrationTest { return from(json).get("resourceId"); } - private void applyForLoanApplication(Integer clientID, Integer loanProductID) { + private void applyForLoanApplication(final Integer clientID, final Integer loanProductID) { System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); given().spec(requestSpec).body(getLoanApplicationBodyAsJSON(clientID.toString(), loanProductID.toString())) .expect().spec(responseSpec) .when().post("/mifosng-provider/api/v1/loans?tenantIdentifier=default"); } - private ArrayList getLoanRepaymentSchedule(Integer productID) + private ArrayList getLoanRepaymentSchedule(final Integer productID) { System.out.println("---------------------------GETTING LOAN REPAYMENT SCHEDULE--------------------------------"); String json = given().spec(requestSpec).body(getLoanCalculationBodyAsJSON(productID.toString())) @@ -102,7 +104,7 @@ public class ClientLoanIntegrationTest { return from(json).get("periods"); } - private void verifyLoanRepaymentSchedule(ArrayList loanSchedule) { + private void verifyLoanRepaymentSchedule(final ArrayList loanSchedule) { System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------"); assertEquals("Checking for Due Date for 1st Month",new ArrayList(Arrays.asList(2011, 10, 20)),loanSchedule.get(1).get("dueDate")); @@ -156,7 +158,7 @@ public class ClientLoanIntegrationTest { return new Gson().toJson(map); } - private String getLoanApplicationBodyAsJSON(String clientID,String productID){ + private String getLoanApplicationBodyAsJSON(final String clientID,final String productID){ HashMap map = new HashMap(); map.put("dateFormat", "dd MMMM yyyy"); map.put("locale", "en_GB"); @@ -179,7 +181,7 @@ public class ClientLoanIntegrationTest { return new Gson().toJson(map); } - private String getLoanCalculationBodyAsJSON(String productID){ + private String getLoanCalculationBodyAsJSON(final String productID){ HashMap map = new HashMap(); map.put("dateFormat", "dd MMMM yyyy"); map.put("locale", "en_GB"); @@ -200,15 +202,15 @@ public class ClientLoanIntegrationTest { return new Gson().toJson(map); } - public String randomIDGenerator(String prefix, int lenOfRandomSuffix) { + public String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) { return randomStringGenerator(prefix,lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); } - public String randomNameGenerator(String prefix, int lenOfRandomSuffix) { + public String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) { return randomStringGenerator(prefix,lenOfRandomSuffix, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); } - private String randomStringGenerator(String prefix,int len, String sourceSetString) { + private String randomStringGenerator(final String prefix,final int len, final String sourceSetString) { int lengthOfSource = sourceSetString.length(); Random rnd = new Random(); StringBuilder sb = new StringBuilder(len); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java index 7261580fa..fe2c48cc6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java @@ -47,6 +47,10 @@ public class CommandSource extends AbstractPersistable { @Column(name = "loan_id") private Long loanId; + @SuppressWarnings("unused") + @Column(name = "savings_id") + private Long savingsId; + @Column(name = "api_get_url", length = 100) private String resourceGetUrl; @@ -165,11 +169,12 @@ public class CommandSource extends AbstractPersistable { this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); } - public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId) { + public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savignsId) { this.officeId = officeId; this.groupId = groupId; this.clientId = clientId; this.loanId = loanId; + this.savingsId = savignsId; } public String getResourceGetUrl() { 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 0c773fc24..da98c5f3d 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 @@ -31,12 +31,12 @@ public class CommandWrapper { } public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName, - final Long resourceId, final Long subresourceId, final String resourceGetUrl) { + final Long resourceId, final Long subresourceId, final String resourceGetUrl) { return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl); } - private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, final Long subresourceId, - final String resourceGetUrl) { + private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, + final Long subresourceId, final String resourceGetUrl) { this.commandId = commandId; this.officeId = null; this.groupId = null; @@ -54,13 +54,12 @@ public class CommandWrapper { this.href = resourceGetUrl; this.json = null; this.transactionId = null; - + } public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, - final String actionName, final String entityName, final Long entityId, final Long subentityId, - final Long codeId, final String supportedEntityType, final Long supportedEntityId, final String href, final String json, - final String transactionId) { + final String actionName, final String entityName, final Long entityId, final Long subentityId, final Long codeId, + final String supportedEntityType, final Long supportedEntityId, final String href, final String json, final String transactionId) { this.commandId = null; this.officeId = officeId; this.groupId = groupId; @@ -95,7 +94,7 @@ public class CommandWrapper { public Long resourceId() { return this.entityId; } - + public Long subresourceId() { return this.subentityId; } @@ -135,7 +134,7 @@ public class CommandWrapper { public Long getEntityId() { return this.entityId; } - + public Long getSubentityId() { return this.subentityId; } @@ -354,12 +353,12 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("BULKREASSIGN") && this.entityName.equalsIgnoreCase("LOAN"); } - public boolean isDatatableResource() { + public boolean isDatatableResource() { return this.href.startsWith("/datatables/"); } public boolean isDeleteOneToOne() { - /* also covers case of deleting all of a one to many*/ + /* also covers case of deleting all of a one to many */ return isDatatableResource() && isDeleteOperation() && this.subentityId == null; } @@ -431,6 +430,10 @@ public class CommandWrapper { return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } + public boolean isSavingsAccountActivation() { + return this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); + } + public boolean isSavingsAccountDeposit() { return this.actionName.equalsIgnoreCase("DEPOSIT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } @@ -446,8 +449,8 @@ public class CommandWrapper { public boolean isNoteResource() { boolean isnoteResource = false; if (this.entityName.equalsIgnoreCase("CLIENTNOTE") || this.entityName.equalsIgnoreCase("LOANNOTE") - || this.entityName.equalsIgnoreCase("LOANTRANSACTIONNOTE") || this.entityName.equalsIgnoreCase("DEPOSITNOTE") - || this.entityName.equalsIgnoreCase("SAVINGNOTE") || this.entityName.equalsIgnoreCase("GROUPNOTE")) { + || this.entityName.equalsIgnoreCase("LOANTRANSACTIONNOTE") || this.entityName.equalsIgnoreCase("SAVINGNOTE") + || this.entityName.equalsIgnoreCase("GROUPNOTE")) { isnoteResource = true; } return isnoteResource; @@ -456,5 +459,4 @@ public class CommandWrapper { public boolean isGroupResource() { return this.entityName.equalsIgnoreCase("GROUP"); } - } \ No newline at end of file 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 86890da1e..564a1ca53 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 @@ -686,10 +686,19 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder savingsAccountActivation(final Long accountId) { + this.actionName = "ACTIVATE"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=activate"; + return this; + } + public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) { this.actionName = "DEPOSIT"; this.entityName = "SAVINGSACCOUNT"; - this.loanId = accountId; + this.savingsId = accountId; this.entityId = null; this.href = "/savingsaccounts/" + accountId + "/transactions"; return this; @@ -698,7 +707,7 @@ public class CommandWrapperBuilder { public CommandWrapperBuilder savingsAccountWithdrawal(final Long accountId) { this.actionName = "WITHDRAWAL"; this.entityName = "SAVINGSACCOUNT"; - this.loanId = accountId; + this.savingsId = accountId; this.entityId = null; this.href = "/savingsaccounts/" + accountId + "/transactions"; return this; 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 e3d4af282..1549942cc 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 @@ -69,7 +69,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker); } commandSourceResult.updateResourceId(result.resourceId()); - commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId()); + commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), + result.getSavingsId()); String changesOnlyJson = null; if (result.hasChanges()) { @@ -360,6 +361,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = applicationContext.getBean("depositSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isSavingsAccountWithdrawal()) { handler = applicationContext.getBean("withdrawSavingsAccountCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isSavingsAccountActivation()) { + handler = applicationContext.getBean("activateSavingsAccountCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } 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 4c83e8d65..c5aa63a90 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 @@ -8,6 +8,7 @@ package org.mifosplatform.infrastructure.core.api; import java.math.BigDecimal; import java.util.Arrays; import java.util.Date; +import java.util.Locale; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; @@ -377,4 +378,8 @@ public final class JsonCommand { final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText); return platformPasswordEncoder.encode(dummyPlatformUser); } + + public Locale extractLocale() { + return this.fromApiJsonHelper.extractLocaleParameter(this.parsedCommand.getAsJsonObject()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java index 64e814cb5..c68648b46 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -22,12 +22,6 @@ public class ApiGlobalErrorResponse { */ private String developerMessage; - /** - * A link to a URL which provides more information on the details of this - * type of error. - */ - private String developerDocLink; - /** * The HTTP status code of the response. */ @@ -57,7 +51,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("401"); globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authenticated"); globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login."); @@ -69,7 +62,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("401"); globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier"); globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request."); @@ -81,7 +73,6 @@ public class ApiGlobalErrorResponse { globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse .setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("error.msg.not.authorized"); globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action."); @@ -97,7 +88,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation"); globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation."); @@ -114,7 +104,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("404"); globalErrorResponse.setDeveloperMessage("The requested resource is not available."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("error.msg.resource.not.found"); globalErrorResponse.setDefaultUserMessage("The requested resource is not available."); @@ -131,7 +120,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode); globalErrorResponse.setDefaultUserMessage(defaultUserMessage); @@ -149,7 +137,6 @@ public class ApiGlobalErrorResponse { globalErrorResponse.setHttpStatusCode("400"); globalErrorResponse .setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode); globalErrorResponse.setDefaultUserMessage(defaultUserMessage); @@ -164,7 +151,6 @@ public class ApiGlobalErrorResponse { ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); globalErrorResponse.setHttpStatusCode("500"); globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server."); - globalErrorResponse.setDeveloperDocLink("https://github.com/keithwoodlock/mifosx/wiki/HTTP-API-Error-codes"); globalErrorResponse.setUserMessageGlobalisationCode("error.msg.platform.server.side.error"); globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server."); @@ -224,12 +210,4 @@ public class ApiGlobalErrorResponse { public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) { this.userMessageGlobalisationCode = userMessageGlobalisationCode; } - - public String getDeveloperDocLink() { - return developerDocLink; - } - - public void setDeveloperDocLink(final String developerDocLink) { - this.developerDocLink = developerDocLink; - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformApiDataValidationException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformApiDataValidationException.java index b5d8ee6c5..7eecc0f81 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformApiDataValidationException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformApiDataValidationException.java @@ -9,14 +9,23 @@ import java.util.List; import org.mifosplatform.infrastructure.core.data.ApiParameterError; +/** + * Exception thrown when problem with an API request to the platform. + */ public class PlatformApiDataValidationException extends RuntimeException { private final String globalisationMessageCode; private final String defaultUserMessage; private final List errors; + public PlatformApiDataValidationException(final List errors) { + this.globalisationMessageCode = "validation.msg.validation.errors.exist"; + this.defaultUserMessage = "Validation errors exist."; + this.errors = errors; + } + public PlatformApiDataValidationException(final String globalisationMessageCode, final String defaultUserMessage, - List errors) { + final List errors) { this.globalisationMessageCode = globalisationMessageCode; this.defaultUserMessage = defaultUserMessage; this.errors = errors; @@ -33,4 +42,4 @@ public class PlatformApiDataValidationException extends RuntimeException { public List getErrors() { return errors; } -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java index 6e274482c..17cf81e50 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exceptionmapper/PlatformApiDataValidationExceptionMapper.java @@ -29,11 +29,11 @@ import org.springframework.stereotype.Component; public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper { @Override - public Response toResponse(PlatformApiDataValidationException exception) { + public Response toResponse(final PlatformApiDataValidationException exception) { - ApiGlobalErrorResponse dataIntegrityError = ApiGlobalErrorResponse.badClientRequest(exception.getGlobalisationMessageCode(), + final ApiGlobalErrorResponse dataValidationErrorResponse = ApiGlobalErrorResponse.badClientRequest(exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getErrors()); - return Response.status(Status.BAD_REQUEST).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build(); + return Response.status(Status.BAD_REQUEST).entity(dataValidationErrorResponse).type(MediaType.APPLICATION_JSON).build(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java index a6df4071a..bbc92273b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/CannotUpdateOfficeWithParentOfficeSameAsSelf.java @@ -10,10 +10,9 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul /** * Exception thrown when an attempt is made update the parent of a root office. */ -public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends - AbstractPlatformDomainRuleException { +public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends AbstractPlatformDomainRuleException { - public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long parentId) { - super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId); - } + public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long parentId) { + super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/RootOfficeParentCannotBeUpdated.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/RootOfficeParentCannotBeUpdated.java index 11d490c40..779eb3ba0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/RootOfficeParentCannotBeUpdated.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/exception/RootOfficeParentCannotBeUpdated.java @@ -10,10 +10,9 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul /** * Exception thrown when an attempt is made update the parent of a root office. */ -public class RootOfficeParentCannotBeUpdated extends - AbstractPlatformDomainRuleException { +public class RootOfficeParentCannotBeUpdated extends AbstractPlatformDomainRuleException { - public RootOfficeParentCannotBeUpdated() { - super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office."); - } + public RootOfficeParentCannotBeUpdated() { + super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office."); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/PeriodFrequencyType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/PeriodFrequencyType.java index c63507c67..83bb83ea1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/PeriodFrequencyType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/PeriodFrequencyType.java @@ -29,24 +29,22 @@ public enum PeriodFrequencyType { } public static PeriodFrequencyType fromInt(final Integer frequency) { - PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.INVALID; - switch (frequency) { - case 0: - repaymentFrequencyType = PeriodFrequencyType.DAYS; - break; - case 1: - repaymentFrequencyType = PeriodFrequencyType.WEEKS; - break; - case 2: - repaymentFrequencyType = PeriodFrequencyType.MONTHS; - break; - case 3: - repaymentFrequencyType = PeriodFrequencyType.YEARS; - break; - default: - repaymentFrequencyType = PeriodFrequencyType.INVALID; - break; + if (frequency != null) { + switch (frequency) { + case 0: + repaymentFrequencyType = PeriodFrequencyType.DAYS; + break; + case 1: + repaymentFrequencyType = PeriodFrequencyType.WEEKS; + break; + case 2: + repaymentFrequencyType = PeriodFrequencyType.MONTHS; + break; + case 3: + repaymentFrequencyType = PeriodFrequencyType.YEARS; + break; + } } return repaymentFrequencyType; } 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 3c899d02a..bc682f004 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 @@ -96,7 +96,10 @@ public class SavingsAccountTransactionsApiResource { result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } - if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); } + if (result == null) { + // FIXME - KW - handle blank command param use case + throw new UnrecognizedQueryParamException("command", commandParam); + } return this.toApiJsonSerializer.serialize(result); } 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 e08bc23f7..c6b225eeb 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 @@ -23,6 +23,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang.StringUtils; import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; @@ -30,6 +31,7 @@ import org.mifosplatform.infrastructure.core.api.ApiParameterHelper; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; @@ -183,6 +185,33 @@ public class SavingsAccountsApiResource { return this.toApiJsonSerializer.serialize(result); } + @POST + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String activate(@PathParam("accountId") final Long accountId, @QueryParam("command") final String commandParam, + final String apiRequestBodyAsJson) { + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + + CommandProcessingResult result = null; + if (is(commandParam, "activate")) { + final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + if (result == null) { + // FIXME - KW - handle blank command param use case + throw new UnrecognizedQueryParamException("command", commandParam); + } + + return this.toApiJsonSerializer.serialize(result); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + @DELETE @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java index 23ea132f1..92fa39d42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java @@ -21,9 +21,12 @@ public class SavingsApiConstants { public static final String idParamName = "id"; public static final String accountNoParamName = "accountNo"; public static final String externalIdParamName = "externalId"; + public static final String statusParamName = "status"; public static final String clientIdParamName = "clientId"; public static final String groupIdParamName = "groupId"; public static final String productIdParamName = "productId"; + public static final String activeParamName = "active"; + public static final String activationDateParamName = "activationDate"; public static final String nameParamName = "name"; public static final String descriptionParamName = "description"; public static final String currencyCodeParamName = "currencyCode"; @@ -55,9 +58,9 @@ public class SavingsApiConstants { "lockinPeriodFrequencyTypeOptions")); public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, - accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, interestRateParamName, - interestRatePeriodFrequencyTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName)); + dateFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, + activeParamName, activationDateParamName, interestRateParamName, interestRatePeriodFrequencyTypeParamName, + minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName)); /** * These parameters will match the class level parameters of @@ -65,14 +68,18 @@ public class SavingsApiConstants { * parameters to match those of request parameters. */ public static final Set SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, - accountNoParamName, externalIdParamName, clientIdParamName, "clientName", groupIdParamName, "groupName", "savingsProductId", - "savingsProductName", "currency", interestRateParamName, interestRatePeriodFrequencyTypeParamName, "annualInterestRate", - minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "summary", - "transactions", "productOptions", "interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions")); + accountNoParamName, externalIdParamName, statusParamName, activationDateParamName, clientIdParamName, "clientName", + groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", interestRateParamName, + interestRatePeriodFrequencyTypeParamName, "annualInterestRate", minRequiredOpeningBalanceParamName, + lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "summary", "transactions", "productOptions", + "interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions")); public static final Set SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName)); public static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount")); + + public static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, activationDateParamName)); } \ 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 9471cbaa7..97474ec20 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 @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; import java.util.Collection; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; @@ -20,6 +21,8 @@ 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; @@ -43,10 +46,11 @@ public class SavingsAccountData { private final Collection interestRatePeriodFrequencyTypeOptions; private final Collection lockinPeriodFrequencyTypeOptions; - 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 CurrencyData currency, final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, - final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, + public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, + final SavingsAccountStatusEnumData status, final LocalDate activationDate, final Long groupId, final String groupName, + final Long clientId, final String clientName, final Long productId, final String productName, final CurrencyData currency, + final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, + final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary) { final Collection productOptions = null; @@ -54,21 +58,22 @@ public class SavingsAccountData { final Collection lockinPeriodFrequencyTypeOptions = null; final Collection transactions = null; - return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, - lockinPeriodFrequencyTypeOptions, transactions); + return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, + interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final Collection productOptions, final Collection interestRatePeriodFrequencyTypeOptions, final Collection lockinPeriodFrequencyTypeOptions, final Collection transactions) { - return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, - account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.currency, - account.interestRate, account.interestRatePeriodFrequencyType, account.annualInterestRate, - account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.summary, - productOptions, interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); + return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.status, account.activationDate, + account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, + account.savingsProductName, account.currency, account.interestRate, account.interestRatePeriodFrequencyType, + account.annualInterestRate, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, + account.lockinPeriodFrequencyType, account.summary, productOptions, interestRatePeriodFrequencyTypeOptions, + lockinPeriodFrequencyTypeOptions, transactions); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -77,6 +82,8 @@ 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 CurrencyData currency = null; @@ -93,15 +100,16 @@ public class SavingsAccountData { final Collection lockinPeriodFrequencyTypeOptions = null; final Collection transactions = null; - return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, - lockinPeriodFrequencyTypeOptions, transactions); + return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, + interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); } - 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 CurrencyData currency, - final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, + private SavingsAccountData(final Long id, final String accountNo, final String externalId, final SavingsAccountStatusEnumData status, + final LocalDate activationDate, final Long groupId, final String groupName, final Long clientId, final String clientName, + final Long productId, final String productName, final CurrencyData currency, final BigDecimal interestRate, + final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary, final Collection productOptions, final Collection interestRatePeriodFrequencyTypeOptions, @@ -109,6 +117,8 @@ 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; 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 e6525ab33..6312cd5ba 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 @@ -1,6 +1,8 @@ package org.mifosplatform.portfolio.savings.data; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNoParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.externalIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.groupIdParamName; @@ -18,6 +20,7 @@ import java.util.List; import java.util.Map; 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; @@ -69,6 +72,15 @@ 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(); + } + if (fromApiJsonHelper.parameterExists(accountNoParamName, element)) { final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element); baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20); 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 new file mode 100644 index 000000000..3a3479079 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountStatusEnumData.java @@ -0,0 +1,35 @@ +/** + * 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; + +/** + * Immutable data object represent savings account status enumerations. + */ +public class SavingsAccountStatusEnumData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final String code; + @SuppressWarnings("unused") + private final String value; + @SuppressWarnings("unused") + private final boolean unactivated; + @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) { + this.id = id; + this.code = code; + this.value = value; + this.unactivated = unactivated; + this.active = active; + this.closed = closed; + } +} \ No newline at end of file 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 d5c955f9f..aa53e092a 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 @@ -1,5 +1,6 @@ package org.mifosplatform.portfolio.savings.data; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionAmountParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionDateParamName; @@ -54,7 +55,28 @@ public class SavingsAccountTransactionDataValidator { baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull(); final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element); - baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().zeroOrPositiveAmount(); + baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateActivation(final JsonCommand command) { + final String json = command.json(); + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + SavingsApiConstants.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + + final JsonElement element = command.parsedJson(); + + final LocalDate activationDate = fromApiJsonHelper.extractLocalDateNamed(activationDateParamName, element); + baseDataValidator.reset().parameter(activationDateParamName).value(activationDate).notNull(); throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index a8025aef5..e3c721811 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 @@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; import java.util.List; import java.util.Map; @@ -19,6 +21,8 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; @@ -26,7 +30,10 @@ import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; +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.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -36,7 +43,6 @@ import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; -import org.mifosplatform.portfolio.savings.exception.InvalidSavingsAccountTransactionException; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -47,7 +53,7 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "account_no", length = 20, unique = true, nullable = false) private String accountNumber; - @Column(name = "external_id") + @Column(name = "external_id", nullable = true) private String externalId; @ManyToOne(optional = true) @@ -62,6 +68,13 @@ public class SavingsAccount extends AbstractPersistable { @JoinColumn(name = "product_id", nullable = false) private SavingsProduct product; + @Column(name = "status_enum", nullable = false) + private Integer status; + + @Temporal(TemporalType.DATE) + @Column(name = "activation_date", nullable = true) + private Date activationDate; + @Embedded private MonetaryCurrency currency; @@ -84,6 +97,15 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "lockin_period_frequency_enum", nullable = false) private Integer lockinPeriodFrequencyType; + /** + * When account is becomes active this field is derived if + * lockinPeriodFrequency and + * lockinPeriodFrequencyType details are present. + */ + @Temporal(TemporalType.DATE) + @Column(name = "lockedin_until_date_derived", nullable = true) + private Date lockedInUntilDate; + @Embedded private SavingsAccountSummary summary; @@ -102,18 +124,25 @@ public class SavingsAccount extends AbstractPersistable { } public static SavingsAccount createNewAccount(final Client client, final Group group, final SavingsProduct product, - final String accountNo, final String externalId, final BigDecimal interestRate, - final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, + final String accountNo, final String externalId, final boolean active, final LocalDate activationDate, + final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { - return new SavingsAccount(client, group, product, accountNo, externalId, interestRate, interestRatePeriodFrequencyType, - annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); + SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED; + if (active) { + status = SavingsAccountStatusType.ACTIVE; + } + + return new SavingsAccount(client, group, product, accountNo, externalId, status, activationDate, interestRate, + interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType); } private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final String accountNo, - final String externalId, final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, - final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, + final String externalId, final SavingsAccountStatusType status, final LocalDate activationDate, final BigDecimal interestRate, + final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, + final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final PeriodFrequencyType lockinPeriodFrequencyType) { this.client = client; this.group = group; @@ -126,6 +155,10 @@ public class SavingsAccount extends AbstractPersistable { } this.currency = product.currency(); this.externalId = externalId; + this.status = status.getValue(); + if (activationDate != null) { + this.activationDate = activationDate.toDate(); + } this.interestRate = interestRate; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType.getValue(); this.annualInterestRate = annualInterestRate; @@ -140,24 +173,121 @@ public class SavingsAccount extends AbstractPersistable { /** * Used after fetching/hydrating a {@link SavingsAccount} object to inject * helper services/components used for update summary details after - * events/transactions on a loan. + * events/transactions on a {@link SavingsAccount}. */ public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; } - public SavingsAccountTransaction deposit(final LocalDate transactionDate, final BigDecimal transactionAmount) { + public boolean isNotActive() { + return !isActive(); + } - final Money amount = Money.of(this.currency, transactionAmount); + public boolean isActive() { + return SavingsAccountStatusType.fromInt(this.status).isActive(); + } - // FIXME - kw - check transactionDate is not before savings account - // activation date. + public void activate(final DateTimeFormatter formatter, final LocalDate activationDate) { - if (isTransactionInTheFuture(transactionDate)) { - final String defaultUserMessage = "Transaction date cannot be in the future."; - throw new InvalidSavingsAccountTransactionException("in.the.future", defaultUserMessage, "transactionDate", transactionDate); + 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); + } + + 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()) { + deposit(formatter, activationDate, minRequiredOpeningBalance.getAmount()); + } + } + + 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 SavingsAccountTransaction deposit(final DateTimeFormatter formatter, final LocalDate transactionDate, + final BigDecimal transactionAmount) { + + if (isNotActive()) { + final String defaultUserMessage = "Transaction is not allowed. Account is not active."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.is.not.active", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isDateInTheFuture(transactionDate)) { + final String defaultUserMessage = "Transaction date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (transactionDate.isBefore(getActivationLocalDate())) { + final Object[] defaultUserArgs = Arrays.asList(transactionDate.toString(formatter), + getActivationLocalDate().toString(formatter)).toArray(); + final String defaultUserMessage = "Transaction date cannot be before accounts activation date."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.before.activation.date", + defaultUserMessage, "transactionDate", defaultUserArgs); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + final Money amount = Money.of(this.currency, transactionAmount); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, transactionDate, amount); this.transactions.add(transaction); @@ -166,19 +296,69 @@ public class SavingsAccount extends AbstractPersistable { return transaction; } - public SavingsAccountTransaction withdrawal(final LocalDate transactionDate, final BigDecimal transactionAmount) { + private LocalDate getActivationLocalDate() { + LocalDate activationLocalDate = null; + if (this.activationDate != null) { + activationLocalDate = new LocalDate(this.activationDate); + } + return activationLocalDate; + } + + public SavingsAccountTransaction withdraw(final DateTimeFormatter formatter, final LocalDate transactionDate, + final BigDecimal transactionAmount) { + + if (isNotActive()) { + + final String defaultUserMessage = "Transaction is not allowed. Account is not active."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.is.not.active", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isDateInTheFuture(transactionDate)) { + final String defaultUserMessage = "Transaction date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.in.the.future", + defaultUserMessage, "transactionDate", transactionDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (transactionDate.isBefore(getActivationLocalDate())) { + final Object[] defaultUserArgs = Arrays.asList(transactionDate.toString(formatter), + getActivationLocalDate().toString(formatter)).toArray(); + final String defaultUserMessage = "Transaction date cannot be before accounts activation date."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.before.activation.date", + defaultUserMessage, "transactionDate", defaultUserArgs); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isAccountLocked(transactionDate)) { + final String defaultUserMessage = "Withdrawal is not allowed. No withdrawals are allowed until after " + + getLockedInUntilLocalDate().toString(formatter); + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.savingsaccount.transaction.withdrawals.blocked.during.lockin.period", defaultUserMessage, "transactionDate", + transactionDate.toString(formatter), getLockedInUntilLocalDate().toString(formatter)); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } final Money amount = Money.of(this.currency, transactionAmount); - // FIXME - kw - check transactionDate is not before savings account - // activation date. - - if (isTransactionInTheFuture(transactionDate)) { - final String defaultUserMessage = "Transaction date cannot be in the future."; - throw new InvalidSavingsAccountTransactionException("in.the.future", defaultUserMessage, "transactionDate", transactionDate); - } - - if (isNotAllowedToWithdraw(amount)) { + if (isNotEnoughFundsToWithdraw(amount)) { // throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), transactionAmount); } @@ -191,7 +371,24 @@ public class SavingsAccount extends AbstractPersistable { return transaction; } - private boolean isTransactionInTheFuture(final LocalDate transactionDate) { + private boolean isAccountLocked(final LocalDate transactionDate) { + boolean isLocked = false; + final boolean accountHasLockedInSetting = this.lockedInUntilDate != null; + if (accountHasLockedInSetting) { + isLocked = getLockedInUntilLocalDate().isAfter(transactionDate); + } + return isLocked; + } + + private LocalDate getLockedInUntilLocalDate() { + LocalDate lockedInUntilLocalDate = null; + if (this.lockedInUntilDate != null) { + lockedInUntilLocalDate = new LocalDate(this.lockedInUntilDate); + } + return lockedInUntilLocalDate; + } + + private boolean isDateInTheFuture(final LocalDate transactionDate) { return transactionDate.isAfter(DateUtils.getLocalDateOfTenant()); } @@ -199,7 +396,11 @@ public class SavingsAccount extends AbstractPersistable { return this.summary.getAccountBalance(this.currency).getAmount(); } - private boolean isNotAllowedToWithdraw(final Money amount) { + private boolean isNotEnoughFundsToWithdraw(final Money amount) { + return !isEnoughFundsToWithdraw(amount); + } + + private boolean isEnoughFundsToWithdraw(final Money amount) { return this.summary.isLessThanOrEqualToAccountBalance(amount); } 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 f018ad745..784a3cd17 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,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.domain; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; @@ -12,7 +14,11 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockin import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName; import java.math.BigDecimal; +import java.util.Locale; +import org.joda.time.LocalDate; +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.portfolio.client.domain.Client; @@ -90,6 +96,9 @@ public class SavingsAccountAssembler { interestRate = product.interestRate(); } + final boolean active = fromApiJsonHelper.extractBooleanNamed(activeParamName, element); + final LocalDate activationDate = fromApiJsonHelper.extractLocalDateNamed(activationDateParamName, element); + PeriodFrequencyType interestRatePeriodFrequencyType = null; Integer interestRatePeriodFrequencyTypeValue = null; if (command.parameterExists(interestRatePeriodFrequencyTypeParamName)) { @@ -128,11 +137,17 @@ public class SavingsAccountAssembler { lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); } - final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, interestRate, - interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType); + final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, active, + activationDate, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType); account.setHelpers(this.savingsAccountTransactionSummaryWrapper); + if (account.isActive()) { + final Locale locale = command.extractLocale(); + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + account.activate(formatter, activationDate); + } + return account; } } \ 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 new file mode 100644 index 000000000..8f49ceb67 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java @@ -0,0 +1,66 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.savings.domain; + +/** + * Enum representation of {@link SavingsAccount} status states. + */ +public enum SavingsAccountStatusType { + + INVALID(0, "savingsAccountStatusType.invalid"), // + UNACTIVATED(100, "savingsAccountStatusType.unactivated"), // + ACTIVE(300, "savingsAccountStatusType.active"), // + CLOSED(600, "savingsAccountStatusType.closed"); + + private final Integer value; + private final String code; + + public static SavingsAccountStatusType fromInt(final Integer type) { + + SavingsAccountStatusType enumeration = SavingsAccountStatusType.INVALID; + switch (type) { + case 100: + enumeration = SavingsAccountStatusType.UNACTIVATED; + break; + case 300: + enumeration = SavingsAccountStatusType.ACTIVE; + break; + case 600: + enumeration = SavingsAccountStatusType.CLOSED; + break; + } + return enumeration; + } + + private SavingsAccountStatusType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public boolean hasStateOf(final SavingsAccountStatusType state) { + return this.value.equals(state.getValue()); + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return code; + } + + public boolean isUnactivated() { + return this.value.equals(SavingsAccountStatusType.UNACTIVATED.getValue()); + } + + public boolean isActive() { + return this.value.equals(SavingsAccountStatusType.ACTIVE.getValue()); + } + + public boolean isClosed() { + return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountSummary.java index 1c57b6448..9ba842b05 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 @@ -45,7 +45,7 @@ public final class SavingsAccountSummary { public boolean isLessThanOrEqualToAccountBalance(final Money amount) { final Money accountBalance = getAccountBalance(amount.getCurrency()); - return accountBalance.isGreaterThanOrEqualTo(accountBalance); + return accountBalance.isGreaterThanOrEqualTo(amount); } public Money getAccountBalance(final MonetaryCurrency currency) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountTransactionException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountTransactionException.java deleted file mode 100644 index 5a193be56..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountTransactionException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.mifosplatform.portfolio.savings.exception; - -import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; - -/** - * Thrown when an attempt is made to withdraw money that is greater than the - * account balance. - */ -public class InvalidSavingsAccountTransactionException extends AbstractPlatformDomainRuleException { - - public InvalidSavingsAccountTransactionException(final String errorCode, final String defaultErrorMessage, final String paramName, - final Object... defaultUserMessageArgs) { - super("error.msg.savingsaccount.transaction." + errorCode, defaultErrorMessage, paramName, defaultUserMessageArgs); - } -} \ 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 new file mode 100644 index 000000000..b2832e84b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/handler/ActivateSavingsAccountCommandHandler.java @@ -0,0 +1,26 @@ +package org.mifosplatform.portfolio.savings.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ActivateSavingsAccountCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService savingAccountWritePlatformService; + + @Autowired + public ActivateSavingsAccountCommandHandler(final SavingsAccountWritePlatformService savingAccountWritePlatformService) { + this.savingAccountWritePlatformService = savingAccountWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.savingAccountWritePlatformService.activate(command.getSavingsId(), command); + } +} \ 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 31664f3cc..a9e2ce0c3 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 @@ -21,6 +21,6 @@ public class DepositSavingsAccountCommandHandler implements NewCommandSourceHand @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.deposit(command.getLoanId(), command); + return this.savingAccountWritePlatformService.deposit(command.getSavingsId(), 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 8707f7a04..3df0cf36c 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 @@ -21,6 +21,6 @@ public class WithdrawSavingsAccountCommandHandler implements NewCommandSourceHan @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.savingAccountWritePlatformService.withdrawal(command.getLoanId(), command); + return this.savingAccountWritePlatformService.withdrawal(command.getSavingsId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index fc2deee8b..5e9cfa81e 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 @@ -23,6 +23,7 @@ import org.mifosplatform.portfolio.group.data.GroupData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; +import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountSummaryData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; @@ -92,6 +93,7 @@ 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.name as groupName, "); sqlBuilder.append("sp.id as productId, sp.name as productName, "); @@ -128,9 +130,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String accountNo = rs.getString("accountNo"); final String externalId = rs.getString("externalId"); - final Long groupId = rs.getLong("groupId"); + 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 = rs.getLong("clientId"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); final String clientName = rs.getString("clientName"); final Long productId = rs.getLong("productId"); @@ -167,9 +174,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, totalInterestPosted, accountBalance); - return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, summary); + return SavingsAccountData.instance(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName, + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary); } } @@ -413,10 +420,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead groupName = group.getName(); } + final SavingsAccountStatusEnumData status = null; + final LocalDate activationDate = null; final SavingsAccountSummaryData summary = null; - return SavingsAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, - currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, summary); + return SavingsAccountData.instance(null, null, null, status, activationDate, groupId, groupName, clientId, clientName, + productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary); } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformService.java index d0483eb94..2f8240d96 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 @@ -16,6 +16,8 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult deleteSavingAccount(Long accountId); + CommandProcessingResult activate(Long savingsId, JsonCommand command); + CommandProcessingResult deposit(Long savingsId, JsonCommand command); CommandProcessingResult withdrawal(Long savingsId, JsonCommand command); 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 61a34e9ad..678116cda 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 @@ -7,9 +7,12 @@ package org.mifosplatform.portfolio.savings.service; import java.math.BigDecimal; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import org.joda.time.LocalDate; +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.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -61,8 +64,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountTransactionRepository savingsAccountTransactionRepository, - final SavingsAccountAssembler savingAccountAssembler, - final SavingsAccountDataValidator savingsAccountDataValidator, + final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountDataValidator savingsAccountDataValidator, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepository clientRepository, final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository) { @@ -124,30 +126,31 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.save(account); } + final Long savingsId = account.getId(); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // - .withEntityId(account.getId()) // + .withEntityId(savingsId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // .build(); } catch (DataAccessException dve) { handleDataIntegrityIssues(command, dve); return new CommandProcessingResult(Long.valueOf(-1)); } - } @Transactional @Override - public CommandProcessingResult updateSavingAccount(final Long accountId, final JsonCommand command) { + 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(accountId); + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); account.update(command, changes); @@ -190,10 +193,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // - .withEntityId(account.getId()) // + .withEntityId(savingsId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // .with(changes) // .build(); } catch (DataAccessException dve) { @@ -204,19 +208,46 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult deleteSavingAccount(final Long accountId) { + public CommandProcessingResult deleteSavingAccount(final Long savingsId) { this.context.authenticatedUser(); - final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(accountId); + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); this.savingAccountRepository.delete(account); return new CommandProcessingResultBuilder() // - .withEntityId(accountId) // + .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(); + + this.savingsAccountTransactionDataValidator.validateActivation(command); + + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); + account.activate(fmt, activationDate); + + this.savingAccountRepository.save(account); + + return new CommandProcessingResultBuilder() // + .withEntityId(savingsId) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // .build(); } @@ -230,10 +261,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); - final SavingsAccountTransaction deposit = account.deposit(transactionDate, transactionAmount); + final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount); final Long transactionId = saveTransactionToGenerateTransactionId(deposit); this.savingAccountRepository.save(account); @@ -258,12 +292,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.context.authenticatedUser(); + this.savingsAccountTransactionDataValidator.validate(command); + final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); - final SavingsAccountTransaction withdrawal = account.withdrawal(transactionDate, transactionAmount); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + + final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount); final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal); this.savingAccountRepository.save(account); 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 5188c9b82..0d4fb415e 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 @@ -7,7 +7,9 @@ package org.mifosplatform.portfolio.savings.service; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType; public class SavingsEnumerations { @@ -103,4 +105,35 @@ public class SavingsEnumerations { } return optionData; } + + public static SavingsAccountStatusEnumData status(final Integer statusEnum) { + return status(SavingsAccountStatusType.fromInt(statusEnum)); + } + + public static SavingsAccountStatusEnumData status(final SavingsAccountStatusType type) { + + SavingsAccountStatusEnumData optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), + SavingsAccountStatusType.INVALID.getCode(), "Invalid", false, false, false); + + switch (type) { + case INVALID: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), + SavingsAccountStatusType.INVALID.getCode(), "Invalid", type.isUnactivated(), type.isActive(), type.isClosed()); + break; + case UNACTIVATED: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.UNACTIVATED.getValue().longValue(), + SavingsAccountStatusType.UNACTIVATED.getCode(), "Unactivated", type.isUnactivated(), type.isActive(), + type.isClosed()); + break; + case ACTIVE: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.ACTIVE.getValue().longValue(), + SavingsAccountStatusType.ACTIVE.getCode(), "Active", type.isUnactivated(), type.isActive(), type.isClosed()); + break; + case CLOSED: + optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.CLOSED.getValue().longValue(), + SavingsAccountStatusType.CLOSED.getCode(), "Closed", type.isUnactivated(), type.isActive(), type.isClosed()); + break; + } + return optionData; + } } \ No newline at end of file
Delete a Savings Account
savingsaccounts/{accountId}?command=activateActivate a Savings Account
Savings Transactions savingsaccounts/{accountId}/transactions?command=deposit Make a Deposit
savingsaccounts/{accountId}/transactions?command=withdrawal Make a WithdrawalUpdate an Office
CURRENCY currencies