MIFOSX-247: add basic lifecycle state to savings account and support some constrains around deposits and withdrawals.

This commit is contained in:
Keith Woodlock
2013-03-25 17:39:01 +00:00
parent 3e36a8a3cb
commit 63eba40115
36 changed files with 809 additions and 221 deletions
+62 -15
View File
@@ -7118,6 +7118,12 @@ Content-Type: application/json
<tr class=alt><td>externalId</td></tr> <tr class=alt><td>externalId</td></tr>
<tr><td class=fielddesc>A place to put an external reference for this savings account e.g. The ID another system uses. If provided, it must be unique.</td></tr> <tr><td class=fielddesc>A place to put an external reference for this savings account e.g. The ID another system uses. If provided, it must be unique.</td></tr>
<tr class=alt><td>active</td></tr>
<tr><td class=fielddesc>Indicates whether this savings account should go into active state or not automatically</td></tr>
<tr class=alt><td>activationDate</td></tr>
<tr><td class=fielddesc>If <b>active</b> is true the <i>activationDate</i> must be provided. If the savings account has an entry for <i>minRequiredOpeningBalance</i> then a deposit for that amount with <i>activationDate</i> as the date is automatically entered.</td></tr>
<tr class=alt><td>interestRate</td></tr> <tr class=alt><td>interestRate</td></tr>
<tr><td class=fielddesc>The default interest rate set when creating savings accounts of this type of product. e.g. <b>5</b>% Per year</td></tr> <tr><td class=fielddesc>The default interest rate set when creating savings accounts of this type of product. e.g. <b>5</b>% Per year</td></tr>
@@ -7257,7 +7263,7 @@ Content-Type: application/json
<br/> <br/>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"><td><div class="mifosXHeading2">Mandatory Fields</div></td></tr> <tr class="matrixHeadingBG"><td><div class="mifosXHeading2">Mandatory Fields</div></td></tr>
<tr class=alt><td>clientId or groupId, productId, accountNo, externalId</td></tr> <tr class=alt><td>clientId or groupId, productId, accountNo, externalId, active, (activationDate if active is true)</td></tr>
</table> </table>
<br/> <br/>
<table class=matrixHeading> <table class=matrixHeading>
@@ -7273,27 +7279,29 @@ Content-Type: application/json
<div class="method-example"> <div class="method-example">
<p>Minimal request: accountNo auto generated, remaining details inherited from savings product.</p> <p>Minimal request: accountNo auto generated, remaining details inherited from savings product.</p>
<code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts</code> <code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts</code>
<code class="method-request">POST savingaccount <code class="method-request">POST savingsaccount
Content-Type: application/json
Request Body:
{
"clientId": 1,
"productId": 1
}
</code>
<p>Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.</p>
<code class="method-request">POST savingaccount
Content-Type: application/json Content-Type: application/json
Request Body: Request Body:
{ {
"clientId": 1, "clientId": 1,
"productId": 1, "productId": 1,
"active": false
}
</code>
<p>Minimal request: accountNo provided (must be unique), remaining details inherited from savings product.</p>
<code class="method-request">POST savingsaccount
Content-Type: application/json
Request Body:
{
"clientId": 1,
"productId": 1,
"active": false,
"accountNo": "SA000023", "accountNo": "SA000023",
"externalId": "SYS-23" "externalId": "SYS-23"
} }
</code> </code>
<p>Full request: accountNo provided (must be unique), remaining details override details from savings product (except currency).</p> <p>Full request: accountNo provided (must be unique), remaining details override details from savings product (except currency).</p>
<code class="method-request">POST savingaccount <code class="method-request">POST savingsaccount
Content-Type: application/json Content-Type: application/json
Request Body: Request Body:
{ {
@@ -7301,7 +7309,10 @@ Request Body:
"productId": 1, "productId": 1,
"accountNo": "SA000023", "accountNo": "SA000023",
"externalId": "SYS-23", "externalId": "SYS-23",
"locale": "en", "active": true,
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"activationDate": "01 March 2011",
"interestRate": "5.65", "interestRate": "5.65",
"interestRatePeriodFrequencyType": 2, "interestRatePeriodFrequencyType": 2,
"minRequiredOpeningBalance": "1,000", "minRequiredOpeningBalance": "1,000",
@@ -7313,6 +7324,35 @@ Request Body:
{ {
"officeId": 1, "officeId": 1,
"clientId": 1, "clientId": 1,
"savingsId": 1,
"resourceId": 1
}
</code>
</div>
</div>
<a id="savingsaccounts_activate" name="savingsaccounts_activate" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Activate a Savings Account</h2>
<p>Activates a savings account.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=activate</code>
<code class="method-request">POST savingsaccount/1?command=activate
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"activationDate": "01 March 2011"
}
</code>
<code class="method-response">
{
"officeId": 1,
"clientId": 1,
"savingsId": 1,
"resourceId": 1 "resourceId": 1
} }
</code> </code>
@@ -11436,6 +11476,14 @@ Content-Type: application/json
<td><a href="#savingsaccounts_delete">Delete a Savings Account</a></td> <td><a href="#savingsaccounts_delete">Delete a Savings Account</a></td>
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td></td>
<td>savingsaccounts/{accountId}?command=activate</td>
<td><a href="#savingsaccounts_activate">Activate a Savings Account</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#savingsaccounts_transactions">Savings Transactions</a></td> <td><a href="#savingsaccounts_transactions">Savings Transactions</a></td>
<td>savingsaccounts/{accountId}/transactions?command=deposit</td> <td>savingsaccounts/{accountId}/transactions?command=deposit</td>
<td><a href="#savingsaccounts_deposit">Make a Deposit</a></td> <td><a href="#savingsaccounts_deposit">Make a Deposit</a></td>
@@ -11443,7 +11491,7 @@ Content-Type: application/json
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr class="alt">
<td></td> <td></td>
<td>savingsaccounts/{accountId}/transactions?command=withdrawal</td> <td>savingsaccounts/{accountId}/transactions?command=withdrawal</td>
<td><a href="#savingsaccounts_withdrawal">Make a Withdrawal</a></td> <td><a href="#savingsaccounts_withdrawal">Make a Withdrawal</a></td>
@@ -11467,7 +11515,6 @@ Content-Type: application/json
<td><a href="#offices_update">Update an Office</a></td> <td><a href="#offices_update">Update an Office</a></td>
<td></td> <td></td>
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td><a href="#configs">CURRENCY</a></td> <td><a href="#configs">CURRENCY</a></td>
<td>currencies</td> <td>currencies</td>
@@ -729,6 +729,7 @@ CREATE TABLE `m_savings_product` (
UNIQUE KEY `sp_unq_name` (`name`) UNIQUE KEY `sp_unq_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `m_savings_account` ( CREATE TABLE `m_savings_account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_no` varchar(20) NOT NULL, `account_no` varchar(20) NOT NULL,
@@ -736,6 +737,8 @@ CREATE TABLE `m_savings_account` (
`client_id` bigint(20) DEFAULT NULL, `client_id` bigint(20) DEFAULT NULL,
`group_id` bigint(20) DEFAULT NULL, `group_id` bigint(20) DEFAULT NULL,
`product_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_code` varchar(3) NOT NULL,
`currency_digits` smallint(5) NOT NULL, `currency_digits` smallint(5) NOT NULL,
`nominal_interest_rate_per_period` decimal(19,6) 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, `min_required_opening_balance` decimal(19,6) DEFAULT NULL,
`lockin_period_frequency` decimal(19,6) DEFAULT NULL, `lockin_period_frequency` decimal(19,6) DEFAULT NULL,
`lockin_period_frequency_enum` smallint(5) 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_deposits_derived` decimal(19,6) DEFAULT NULL,
`total_withdrawals_derived` decimal(19,6) DEFAULT NULL, `total_withdrawals_derived` decimal(19,6) DEFAULT NULL,
`total_interest_posted_derived` decimal(19,6) DEFAULT NULL, `total_interest_posted_derived` decimal(19,6) DEFAULT NULL,
@@ -17,19 +17,36 @@ INSERT INTO `r_enum_value`
VALUES VALUES
('amortization_method_enum',0,'Equal principle payments','Equal principle payments'), ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),
('amortization_method_enum',1,'Equal installments','Equal installments'), ('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_calculated_in_period_enum',0,'Daily','Daily'),
('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'), ('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),
('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'), ('interest_method_enum',0,'Declining Balance','Declining Balance'),
('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'), ('interest_method_enum',1,'Flat','Flat'),
('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'), ('interest_period_frequency_enum',2,'Per month','Per month'),
('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'), ('interest_period_frequency_enum',3,'Per year','Per year'),
('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'), ('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),
('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'), ('loan_status_id',200,'Approved','Approved'),
('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'), ('loan_status_id',300,'Active','Active'),
('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'), ('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),
('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'), ('loan_status_id',500,'Rejected','Rejected'),
('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'), ('loan_status_id',600,'Closed','Closed'),
('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'); ('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`) INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
VALUES ('transaction_type_enum', '1', 'Disbursement', 'Disbursement'); 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`) INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`)
VALUES ('transaction_type_enum', '11', 'Apply Interest', 'Apply Interest'); VALUES ('transaction_type_enum', '11', 'Apply Interest', 'Apply Interest');
INSERT INTO `m_currency` INSERT INTO `m_currency`
(`id`,`code`,`decimal_places`,`display_symbol`,`name`, `internationalized_name_code`) (`id`,`code`,`decimal_places`,`display_symbol`,`name`, `internationalized_name_code`)
VALUES VALUES
@@ -263,7 +263,9 @@ INSERT INTO `m_permission`
('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'DEPOSIT', '1'),
('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'), ('transaction_savings', 'DEPOSIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'DEPOSIT', '0'),
('transaction_savings', 'WITHDRAWAL_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'WITHDRAWAL', '1'), ('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 -- == accounting related permissions
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
@@ -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');
@@ -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.google.gson.Gson;
import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder; 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.path.json.JsonPath;
import com.jayway.restassured.specification.RequestSpecification; import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification; 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. * Client Loan Integration Test for checking Loan Application Repayment Schedule.
*/ */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClientLoanIntegrationTest { public class ClientLoanIntegrationTest {
String basicAuthKey; String basicAuthKey;
ResponseSpecification responseSpec; ResponseSpecification responseSpec;
@@ -70,7 +72,7 @@ public class ClientLoanIntegrationTest {
return from(json).get("clientId"); return from(json).get("clientId");
} }
private void checkClientCreatedOnServer(Integer generatedClientID) { private void checkClientCreatedOnServer(final Integer generatedClientID) {
System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n"); System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
given().spec(requestSpec) given().spec(requestSpec)
.expect().spec(responseSpec) .expect().spec(responseSpec)
@@ -86,14 +88,14 @@ public class ClientLoanIntegrationTest {
return from(json).get("resourceId"); 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--------------------------------"); System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
given().spec(requestSpec).body(getLoanApplicationBodyAsJSON(clientID.toString(), loanProductID.toString())) given().spec(requestSpec).body(getLoanApplicationBodyAsJSON(clientID.toString(), loanProductID.toString()))
.expect().spec(responseSpec) .expect().spec(responseSpec)
.when().post("/mifosng-provider/api/v1/loans?tenantIdentifier=default"); .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--------------------------------"); System.out.println("---------------------------GETTING LOAN REPAYMENT SCHEDULE--------------------------------");
String json = given().spec(requestSpec).body(getLoanCalculationBodyAsJSON(productID.toString())) String json = given().spec(requestSpec).body(getLoanCalculationBodyAsJSON(productID.toString()))
@@ -102,7 +104,7 @@ public class ClientLoanIntegrationTest {
return from(json).get("periods"); return from(json).get("periods");
} }
private void verifyLoanRepaymentSchedule(ArrayList<HashMap> loanSchedule) { private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule) {
System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------"); System.out.println("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------");
assertEquals("Checking for Due Date for 1st Month",new ArrayList<Integer>(Arrays.asList(2011, 10, 20)),loanSchedule.get(1).get("dueDate")); assertEquals("Checking for Due Date for 1st Month",new ArrayList<Integer>(Arrays.asList(2011, 10, 20)),loanSchedule.get(1).get("dueDate"));
@@ -156,7 +158,7 @@ public class ClientLoanIntegrationTest {
return new Gson().toJson(map); return new Gson().toJson(map);
} }
private String getLoanApplicationBodyAsJSON(String clientID,String productID){ private String getLoanApplicationBodyAsJSON(final String clientID,final String productID){
HashMap<String, String> map = new HashMap<String, String>(); HashMap<String, String> map = new HashMap<String, String>();
map.put("dateFormat", "dd MMMM yyyy"); map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en_GB"); map.put("locale", "en_GB");
@@ -179,7 +181,7 @@ public class ClientLoanIntegrationTest {
return new Gson().toJson(map); return new Gson().toJson(map);
} }
private String getLoanCalculationBodyAsJSON(String productID){ private String getLoanCalculationBodyAsJSON(final String productID){
HashMap<String, String> map = new HashMap<String, String>(); HashMap<String, String> map = new HashMap<String, String>();
map.put("dateFormat", "dd MMMM yyyy"); map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en_GB"); map.put("locale", "en_GB");
@@ -200,15 +202,15 @@ public class ClientLoanIntegrationTest {
return new Gson().toJson(map); 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"); 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"); 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(); int lengthOfSource = sourceSetString.length();
Random rnd = new Random(); Random rnd = new Random();
StringBuilder sb = new StringBuilder(len); StringBuilder sb = new StringBuilder(len);
@@ -47,6 +47,10 @@ public class CommandSource extends AbstractPersistable<Long> {
@Column(name = "loan_id") @Column(name = "loan_id")
private Long loanId; private Long loanId;
@SuppressWarnings("unused")
@Column(name = "savings_id")
private Long savingsId;
@Column(name = "api_get_url", length = 100) @Column(name = "api_get_url", length = 100)
private String resourceGetUrl; private String resourceGetUrl;
@@ -165,11 +169,12 @@ public class CommandSource extends AbstractPersistable<Long> {
this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); 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.officeId = officeId;
this.groupId = groupId; this.groupId = groupId;
this.clientId = clientId; this.clientId = clientId;
this.loanId = loanId; this.loanId = loanId;
this.savingsId = savignsId;
} }
public String getResourceGetUrl() { public String getResourceGetUrl() {
@@ -31,12 +31,12 @@ public class CommandWrapper {
} }
public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName, 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); 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, private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
final String resourceGetUrl) { final Long subresourceId, final String resourceGetUrl) {
this.commandId = commandId; this.commandId = commandId;
this.officeId = null; this.officeId = null;
this.groupId = null; this.groupId = null;
@@ -54,13 +54,12 @@ public class CommandWrapper {
this.href = resourceGetUrl; this.href = resourceGetUrl;
this.json = null; this.json = null;
this.transactionId = null; this.transactionId = null;
} }
public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, 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 String actionName, final String entityName, final Long entityId, final Long subentityId, final Long codeId,
final Long codeId, final String supportedEntityType, final Long supportedEntityId, final String href, final String json, final String supportedEntityType, final Long supportedEntityId, final String href, final String json, final String transactionId) {
final String transactionId) {
this.commandId = null; this.commandId = null;
this.officeId = officeId; this.officeId = officeId;
this.groupId = groupId; this.groupId = groupId;
@@ -95,7 +94,7 @@ public class CommandWrapper {
public Long resourceId() { public Long resourceId() {
return this.entityId; return this.entityId;
} }
public Long subresourceId() { public Long subresourceId() {
return this.subentityId; return this.subentityId;
} }
@@ -135,7 +134,7 @@ public class CommandWrapper {
public Long getEntityId() { public Long getEntityId() {
return this.entityId; return this.entityId;
} }
public Long getSubentityId() { public Long getSubentityId() {
return this.subentityId; return this.subentityId;
} }
@@ -354,12 +353,12 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("BULKREASSIGN") && this.entityName.equalsIgnoreCase("LOAN"); return this.actionName.equalsIgnoreCase("BULKREASSIGN") && this.entityName.equalsIgnoreCase("LOAN");
} }
public boolean isDatatableResource() { public boolean isDatatableResource() {
return this.href.startsWith("/datatables/"); return this.href.startsWith("/datatables/");
} }
public boolean isDeleteOneToOne() { 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; return isDatatableResource() && isDeleteOperation() && this.subentityId == null;
} }
@@ -431,6 +430,10 @@ public class CommandWrapper {
return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
} }
public boolean isSavingsAccountActivation() {
return this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isSavingsAccountDeposit() { public boolean isSavingsAccountDeposit() {
return this.actionName.equalsIgnoreCase("DEPOSIT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); return this.actionName.equalsIgnoreCase("DEPOSIT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
} }
@@ -446,8 +449,8 @@ public class CommandWrapper {
public boolean isNoteResource() { public boolean isNoteResource() {
boolean isnoteResource = false; boolean isnoteResource = false;
if (this.entityName.equalsIgnoreCase("CLIENTNOTE") || this.entityName.equalsIgnoreCase("LOANNOTE") if (this.entityName.equalsIgnoreCase("CLIENTNOTE") || this.entityName.equalsIgnoreCase("LOANNOTE")
|| this.entityName.equalsIgnoreCase("LOANTRANSACTIONNOTE") || this.entityName.equalsIgnoreCase("DEPOSITNOTE") || this.entityName.equalsIgnoreCase("LOANTRANSACTIONNOTE") || this.entityName.equalsIgnoreCase("SAVINGNOTE")
|| this.entityName.equalsIgnoreCase("SAVINGNOTE") || this.entityName.equalsIgnoreCase("GROUPNOTE")) { || this.entityName.equalsIgnoreCase("GROUPNOTE")) {
isnoteResource = true; isnoteResource = true;
} }
return isnoteResource; return isnoteResource;
@@ -456,5 +459,4 @@ public class CommandWrapper {
public boolean isGroupResource() { public boolean isGroupResource() {
return this.entityName.equalsIgnoreCase("GROUP"); return this.entityName.equalsIgnoreCase("GROUP");
} }
} }
@@ -686,10 +686,19 @@ public class CommandWrapperBuilder {
return this; 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) { public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) {
this.actionName = "DEPOSIT"; this.actionName = "DEPOSIT";
this.entityName = "SAVINGSACCOUNT"; this.entityName = "SAVINGSACCOUNT";
this.loanId = accountId; this.savingsId = accountId;
this.entityId = null; this.entityId = null;
this.href = "/savingsaccounts/" + accountId + "/transactions"; this.href = "/savingsaccounts/" + accountId + "/transactions";
return this; return this;
@@ -698,7 +707,7 @@ public class CommandWrapperBuilder {
public CommandWrapperBuilder savingsAccountWithdrawal(final Long accountId) { public CommandWrapperBuilder savingsAccountWithdrawal(final Long accountId) {
this.actionName = "WITHDRAWAL"; this.actionName = "WITHDRAWAL";
this.entityName = "SAVINGSACCOUNT"; this.entityName = "SAVINGSACCOUNT";
this.loanId = accountId; this.savingsId = accountId;
this.entityId = null; this.entityId = null;
this.href = "/savingsaccounts/" + accountId + "/transactions"; this.href = "/savingsaccounts/" + accountId + "/transactions";
return this; return this;
@@ -69,7 +69,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker); commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker);
} }
commandSourceResult.updateResourceId(result.resourceId()); 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; String changesOnlyJson = null;
if (result.hasChanges()) { if (result.hasChanges()) {
@@ -360,6 +361,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = applicationContext.getBean("depositSavingsAccountCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("depositSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountWithdrawal()) { } else if (wrapper.isSavingsAccountWithdrawal()) {
handler = applicationContext.getBean("withdrawSavingsAccountCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("withdrawSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountActivation()) {
handler = applicationContext.getBean("activateSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -8,6 +8,7 @@ package org.mifosplatform.infrastructure.core.api;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -377,4 +378,8 @@ public final class JsonCommand {
final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText); final PlatformUser dummyPlatformUser = new BasicPasswordEncodablePlatformUser(saltValue, "", passwordPlainText);
return platformPasswordEncoder.encode(dummyPlatformUser); return platformPasswordEncoder.encode(dummyPlatformUser);
} }
public Locale extractLocale() {
return this.fromApiJsonHelper.extractLocaleParameter(this.parsedCommand.getAsJsonObject());
}
} }
@@ -22,12 +22,6 @@ public class ApiGlobalErrorResponse {
*/ */
private String developerMessage; 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. * The HTTP status code of the response.
*/ */
@@ -57,7 +51,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("401"); globalErrorResponse.setHttpStatusCode("401");
globalErrorResponse.setDeveloperMessage("Invalid authentication details were passed in api request."); 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.setUserMessageGlobalisationCode("error.msg.not.authenticated");
globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login."); globalErrorResponse.setDefaultUserMessage("Unauthenticated. Please login.");
@@ -69,7 +62,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("401"); globalErrorResponse.setHttpStatusCode("401");
globalErrorResponse.setDeveloperMessage("Invalid tenant details were passed in api request."); 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.setUserMessageGlobalisationCode("error.msg.invalid.tenant.identifier");
globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request."); globalErrorResponse.setDefaultUserMessage("Invalide tenant identifier provided with request.");
@@ -81,7 +73,6 @@ public class ApiGlobalErrorResponse {
globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse globalErrorResponse
.setDeveloperMessage("The user associated with credentials passed on this request does not have sufficient privileges to perform this action."); .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.setUserMessageGlobalisationCode("error.msg.not.authorized");
globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action."); globalErrorResponse.setDefaultUserMessage("Insufficient privileges to perform this action.");
@@ -97,7 +88,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("Request was understood but caused a domain rule violation."); 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.setUserMessageGlobalisationCode("validation.msg.domain.rule.violation");
globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation."); globalErrorResponse.setDefaultUserMessage("Errors contain reason for domain rule violation.");
@@ -114,7 +104,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("404"); globalErrorResponse.setHttpStatusCode("404");
globalErrorResponse.setDeveloperMessage("The requested resource is not available."); 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.setUserMessageGlobalisationCode("error.msg.resource.not.found");
globalErrorResponse.setDefaultUserMessage("The requested resource is not available."); globalErrorResponse.setDefaultUserMessage("The requested resource is not available.");
@@ -131,7 +120,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("403"); globalErrorResponse.setHttpStatusCode("403");
globalErrorResponse.setDeveloperMessage("The request caused a data integrity issue to be fired by the database."); 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.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage); globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
@@ -149,7 +137,6 @@ public class ApiGlobalErrorResponse {
globalErrorResponse.setHttpStatusCode("400"); globalErrorResponse.setHttpStatusCode("400");
globalErrorResponse globalErrorResponse
.setDeveloperMessage("The request was invalid. This typically will happen due to validation errors which are provided."); .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.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage); globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
@@ -164,7 +151,6 @@ public class ApiGlobalErrorResponse {
ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse(); ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode("500"); globalErrorResponse.setHttpStatusCode("500");
globalErrorResponse.setDeveloperMessage("An unexpected error occured on the platform server."); 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.setUserMessageGlobalisationCode("error.msg.platform.server.side.error");
globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server."); globalErrorResponse.setDefaultUserMessage("An unexpected error occured on the platform server.");
@@ -224,12 +210,4 @@ public class ApiGlobalErrorResponse {
public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) { public void setUserMessageGlobalisationCode(final String userMessageGlobalisationCode) {
this.userMessageGlobalisationCode = userMessageGlobalisationCode; this.userMessageGlobalisationCode = userMessageGlobalisationCode;
} }
public String getDeveloperDocLink() {
return developerDocLink;
}
public void setDeveloperDocLink(final String developerDocLink) {
this.developerDocLink = developerDocLink;
}
} }
@@ -9,14 +9,23 @@ import java.util.List;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
/**
* Exception thrown when problem with an API request to the platform.
*/
public class PlatformApiDataValidationException extends RuntimeException { public class PlatformApiDataValidationException extends RuntimeException {
private final String globalisationMessageCode; private final String globalisationMessageCode;
private final String defaultUserMessage; private final String defaultUserMessage;
private final List<ApiParameterError> errors; private final List<ApiParameterError> errors;
public PlatformApiDataValidationException(final List<ApiParameterError> errors) {
this.globalisationMessageCode = "validation.msg.validation.errors.exist";
this.defaultUserMessage = "Validation errors exist.";
this.errors = errors;
}
public PlatformApiDataValidationException(final String globalisationMessageCode, final String defaultUserMessage, public PlatformApiDataValidationException(final String globalisationMessageCode, final String defaultUserMessage,
List<ApiParameterError> errors) { final List<ApiParameterError> errors) {
this.globalisationMessageCode = globalisationMessageCode; this.globalisationMessageCode = globalisationMessageCode;
this.defaultUserMessage = defaultUserMessage; this.defaultUserMessage = defaultUserMessage;
this.errors = errors; this.errors = errors;
@@ -33,4 +42,4 @@ public class PlatformApiDataValidationException extends RuntimeException {
public List<ApiParameterError> getErrors() { public List<ApiParameterError> getErrors() {
return errors; return errors;
} }
} }
@@ -29,11 +29,11 @@ import org.springframework.stereotype.Component;
public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper<PlatformApiDataValidationException> { public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper<PlatformApiDataValidationException> {
@Override @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()); 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();
} }
} }
@@ -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. * Exception thrown when an attempt is made update the parent of a root office.
*/ */
public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends public class CannotUpdateOfficeWithParentOfficeSameAsSelf extends AbstractPlatformDomainRuleException {
AbstractPlatformDomainRuleException {
public CannotUpdateOfficeWithParentOfficeSameAsSelf(final Long officeId, final Long 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); super("error.msg.office.parentId.same.as.id", "Cannot update office with parent same as self.", officeId, parentId);
} }
} }
@@ -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. * Exception thrown when an attempt is made update the parent of a root office.
*/ */
public class RootOfficeParentCannotBeUpdated extends public class RootOfficeParentCannotBeUpdated extends AbstractPlatformDomainRuleException {
AbstractPlatformDomainRuleException {
public RootOfficeParentCannotBeUpdated() { public RootOfficeParentCannotBeUpdated() {
super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office."); super("error.msg.office.cannot.update.parent.office.of.root.office", "The root office must not be set with a parent office.");
} }
} }
@@ -29,24 +29,22 @@ public enum PeriodFrequencyType {
} }
public static PeriodFrequencyType fromInt(final Integer frequency) { public static PeriodFrequencyType fromInt(final Integer frequency) {
PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.INVALID; PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.INVALID;
switch (frequency) { if (frequency != null) {
case 0: switch (frequency) {
repaymentFrequencyType = PeriodFrequencyType.DAYS; case 0:
break; repaymentFrequencyType = PeriodFrequencyType.DAYS;
case 1: break;
repaymentFrequencyType = PeriodFrequencyType.WEEKS; case 1:
break; repaymentFrequencyType = PeriodFrequencyType.WEEKS;
case 2: break;
repaymentFrequencyType = PeriodFrequencyType.MONTHS; case 2:
break; repaymentFrequencyType = PeriodFrequencyType.MONTHS;
case 3: break;
repaymentFrequencyType = PeriodFrequencyType.YEARS; case 3:
break; repaymentFrequencyType = PeriodFrequencyType.YEARS;
default: break;
repaymentFrequencyType = PeriodFrequencyType.INVALID; }
break;
} }
return repaymentFrequencyType; return repaymentFrequencyType;
} }
@@ -96,7 +96,10 @@ public class SavingsAccountTransactionsApiResource {
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); 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); return this.toApiJsonSerializer.serialize(result);
} }
@@ -23,6 +23,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; 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.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; 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.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
@@ -183,6 +185,33 @@ public class SavingsAccountsApiResource {
return this.toApiJsonSerializer.serialize(result); 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 @DELETE
@Path("{accountId}") @Path("{accountId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -21,9 +21,12 @@ public class SavingsApiConstants {
public static final String idParamName = "id"; public static final String idParamName = "id";
public static final String accountNoParamName = "accountNo"; public static final String accountNoParamName = "accountNo";
public static final String externalIdParamName = "externalId"; public static final String externalIdParamName = "externalId";
public static final String statusParamName = "status";
public static final String clientIdParamName = "clientId"; public static final String clientIdParamName = "clientId";
public static final String groupIdParamName = "groupId"; public static final String groupIdParamName = "groupId";
public static final String productIdParamName = "productId"; 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 nameParamName = "name";
public static final String descriptionParamName = "description"; public static final String descriptionParamName = "description";
public static final String currencyCodeParamName = "currencyCode"; public static final String currencyCodeParamName = "currencyCode";
@@ -55,9 +58,9 @@ public class SavingsApiConstants {
"lockinPeriodFrequencyTypeOptions")); "lockinPeriodFrequencyTypeOptions"));
public static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, public static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, interestRateParamName, dateFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName,
interestRatePeriodFrequencyTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, activeParamName, activationDateParamName, interestRateParamName, interestRatePeriodFrequencyTypeParamName,
lockinPeriodFrequencyTypeParamName)); minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName));
/** /**
* These parameters will match the class level parameters of * These parameters will match the class level parameters of
@@ -65,14 +68,18 @@ public class SavingsApiConstants {
* parameters to match those of request parameters. * parameters to match those of request parameters.
*/ */
public static final Set<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, public static final Set<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName,
accountNoParamName, externalIdParamName, clientIdParamName, "clientName", groupIdParamName, "groupName", "savingsProductId", accountNoParamName, externalIdParamName, statusParamName, activationDateParamName, clientIdParamName, "clientName",
"savingsProductName", "currency", interestRateParamName, interestRatePeriodFrequencyTypeParamName, "annualInterestRate", groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", interestRateParamName,
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "summary", interestRatePeriodFrequencyTypeParamName, "annualInterestRate", minRequiredOpeningBalanceParamName,
"transactions", "productOptions", "interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions")); lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, "summary", "transactions", "productOptions",
"interestRatePeriodFrequencyTypeOptions", "lockinPeriodFrequencyTypeOptions"));
public static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList( public static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName)); localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName));
public static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, public static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName,
"accountId", accountNoParamName, "currency", "amount")); "accountId", accountNoParamName, "currency", "amount"));
public static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, activationDateParamName));
} }
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.savings.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.data.CurrencyData;
@@ -20,6 +21,8 @@ public class SavingsAccountData {
private final Long id; private final Long id;
private final String accountNo; private final String accountNo;
private final String externalId; private final String externalId;
private final SavingsAccountStatusEnumData status;
private final LocalDate activationDate;
private final Long groupId; private final Long groupId;
private final String groupName; private final String groupName;
private final Long clientId; private final Long clientId;
@@ -43,10 +46,11 @@ public class SavingsAccountData {
private final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions; private final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions;
private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions; private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId, final Long groupId, public static SavingsAccountData instance(final Long id, final String accountNo, final String externalId,
final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final SavingsAccountStatusEnumData status, final LocalDate activationDate, final Long groupId, final String groupName,
final CurrencyData currency, final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final Long clientId, final String clientName, final Long productId, final String productName, final CurrencyData currency,
final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary) { final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary) {
final Collection<SavingsProductData> productOptions = null; final Collection<SavingsProductData> productOptions = null;
@@ -54,21 +58,22 @@ public class SavingsAccountData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<SavingsAccountTransactionData> transactions = null; final Collection<SavingsAccountTransactionData> transactions = null;
return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName,
currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate,
lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions,
lockinPeriodFrequencyTypeOptions, transactions); interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions);
} }
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
final Collection<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions, final Collection<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<SavingsAccountTransactionData> transactions) { final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<SavingsAccountTransactionData> transactions) {
return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.status, account.activationDate,
account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.currency, account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId,
account.interestRate, account.interestRatePeriodFrequencyType, account.annualInterestRate, account.savingsProductName, account.currency, account.interestRate, account.interestRatePeriodFrequencyType,
account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.summary, account.annualInterestRate, account.minRequiredOpeningBalance, account.lockinPeriodFrequency,
productOptions, interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions); account.lockinPeriodFrequencyType, account.summary, productOptions, interestRatePeriodFrequencyTypeOptions,
lockinPeriodFrequencyTypeOptions, transactions);
} }
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, 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 Long id = null;
final String accountNo = null; final String accountNo = null;
final String externalId = null; final String externalId = null;
final SavingsAccountStatusEnumData status = null;
final LocalDate activationDate = null;
final Long productId = null; final Long productId = null;
final String productName = null; final String productName = null;
final CurrencyData currency = null; final CurrencyData currency = null;
@@ -93,15 +100,16 @@ public class SavingsAccountData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null; final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = null;
final Collection<SavingsAccountTransactionData> transactions = null; final Collection<SavingsAccountTransactionData> transactions = null;
return new SavingsAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, return new SavingsAccountData(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName,
currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate,
lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions, interestRatePeriodFrequencyTypeOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary, productOptions,
lockinPeriodFrequencyTypeOptions, transactions); interestRatePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, transactions);
} }
private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName, private SavingsAccountData(final Long id, final String accountNo, final String externalId, final SavingsAccountStatusEnumData status,
final Long clientId, final String clientName, final Long productId, final String productName, final CurrencyData currency, final LocalDate activationDate, final Long groupId, final String groupName, final Long clientId, final String clientName,
final BigDecimal interestRate, final EnumOptionData interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, 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 BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary, final EnumOptionData lockinPeriodFrequencyType, final SavingsAccountSummaryData summary,
final Collection<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions, final Collection<SavingsProductData> productOptions, final Collection<EnumOptionData> interestRatePeriodFrequencyTypeOptions,
@@ -109,6 +117,8 @@ public class SavingsAccountData {
this.id = id; this.id = id;
this.accountNo = accountNo; this.accountNo = accountNo;
this.externalId = externalId; this.externalId = externalId;
this.status = status;
this.activationDate = activationDate;
this.groupId = groupId; this.groupId = groupId;
this.groupName = groupName; this.groupName = groupName;
this.clientId = clientId; this.clientId = clientId;
@@ -1,6 +1,8 @@
package org.mifosplatform.portfolio.savings.data; package org.mifosplatform.portfolio.savings.data;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNoParamName; 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.clientIdParamName;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.externalIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.externalIdParamName;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.groupIdParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.groupIdParamName;
@@ -18,6 +20,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
@@ -69,6 +72,15 @@ public class SavingsAccountDataValidator {
final Long productId = fromApiJsonHelper.extractLongNamed(productIdParamName, element); final Long productId = fromApiJsonHelper.extractLongNamed(productIdParamName, element);
baseDataValidator.reset().parameter(productIdParamName).value(productId).notNull().integerGreaterThanZero(); 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)) { if (fromApiJsonHelper.parameterExists(accountNoParamName, element)) {
final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element); final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParamName, element);
baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20); baseDataValidator.reset().parameter(accountNoParamName).value(accountNo).notBlank().notExceedingLengthOf(20);
@@ -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;
}
}
@@ -1,5 +1,6 @@
package org.mifosplatform.portfolio.savings.data; 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.transactionAmountParamName;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionDateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionDateParamName;
@@ -54,7 +55,28 @@ public class SavingsAccountTransactionDataValidator {
baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull(); baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element); 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<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
SavingsApiConstants.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.savings.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -19,6 +21,8 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.OrderBy; import javax.persistence.OrderBy;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient; import javax.persistence.Transient;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
@@ -26,7 +30,10 @@ import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.api.JsonCommand; 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.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator; import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; 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.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; import org.mifosplatform.portfolio.savings.api.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
import org.mifosplatform.portfolio.savings.exception.InvalidSavingsAccountTransactionException;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity @Entity
@@ -47,7 +53,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@Column(name = "account_no", length = 20, unique = true, nullable = false) @Column(name = "account_no", length = 20, unique = true, nullable = false)
private String accountNumber; private String accountNumber;
@Column(name = "external_id") @Column(name = "external_id", nullable = true)
private String externalId; private String externalId;
@ManyToOne(optional = true) @ManyToOne(optional = true)
@@ -62,6 +68,13 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@JoinColumn(name = "product_id", nullable = false) @JoinColumn(name = "product_id", nullable = false)
private SavingsProduct product; 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 @Embedded
private MonetaryCurrency currency; private MonetaryCurrency currency;
@@ -84,6 +97,15 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@Column(name = "lockin_period_frequency_enum", nullable = false) @Column(name = "lockin_period_frequency_enum", nullable = false)
private Integer lockinPeriodFrequencyType; private Integer lockinPeriodFrequencyType;
/**
* When account is becomes <code>active</code> this field is derived if
* <code>lockinPeriodFrequency</code> and
* <code>lockinPeriodFrequencyType</code> details are present.
*/
@Temporal(TemporalType.DATE)
@Column(name = "lockedin_until_date_derived", nullable = true)
private Date lockedInUntilDate;
@Embedded @Embedded
private SavingsAccountSummary summary; private SavingsAccountSummary summary;
@@ -102,18 +124,25 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
public static SavingsAccount createNewAccount(final Client client, final Group group, final SavingsProduct product, public static SavingsAccount createNewAccount(final Client client, final Group group, final SavingsProduct product,
final String accountNo, final String externalId, final BigDecimal interestRate, final String accountNo, final String externalId, final boolean active, final LocalDate activationDate,
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate, final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final PeriodFrequencyType lockinPeriodFrequencyType) { final PeriodFrequencyType lockinPeriodFrequencyType) {
return new SavingsAccount(client, group, product, accountNo, externalId, interestRate, interestRatePeriodFrequencyType, SavingsAccountStatusType status = SavingsAccountStatusType.UNACTIVATED;
annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType); 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, private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final String accountNo,
final String externalId, final BigDecimal interestRate, final PeriodFrequencyType interestRatePeriodFrequencyType, final String externalId, final SavingsAccountStatusType status, final LocalDate activationDate, final BigDecimal interestRate,
final BigDecimal annualInterestRate, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualInterestRate,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final PeriodFrequencyType lockinPeriodFrequencyType) { final PeriodFrequencyType lockinPeriodFrequencyType) {
this.client = client; this.client = client;
this.group = group; this.group = group;
@@ -126,6 +155,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
this.currency = product.currency(); this.currency = product.currency();
this.externalId = externalId; this.externalId = externalId;
this.status = status.getValue();
if (activationDate != null) {
this.activationDate = activationDate.toDate();
}
this.interestRate = interestRate; this.interestRate = interestRate;
this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType.getValue(); this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType.getValue();
this.annualInterestRate = annualInterestRate; this.annualInterestRate = annualInterestRate;
@@ -140,24 +173,121 @@ public class SavingsAccount extends AbstractPersistable<Long> {
/** /**
* Used after fetching/hydrating a {@link SavingsAccount} object to inject * Used after fetching/hydrating a {@link SavingsAccount} object to inject
* helper services/components used for update summary details after * 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) { public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper) {
this.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 public void activate(final DateTimeFormatter formatter, final LocalDate activationDate) {
// activation date.
if (isTransactionInTheFuture(transactionDate)) { if (isActive()) {
final String defaultUserMessage = "Transaction date cannot be in the future."; final String defaultUserMessage = "Cannot activate account. Account is already active.";
throw new InvalidSavingsAccountTransactionException("in.the.future", defaultUserMessage, "transactionDate", transactionDate); final ApiParameterError error = ApiParameterError.parameterError("error.msg.savingsaccount.transaction.account.already.active",
defaultUserMessage, "activationDate", activationDate.toString(formatter));
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
final Money amount = Money.of(this.currency, transactionAmount);
final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, transactionDate, amount); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, transactionDate, amount);
this.transactions.add(transaction); this.transactions.add(transaction);
@@ -166,19 +296,69 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return transaction; 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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
final Money amount = Money.of(this.currency, transactionAmount); final Money amount = Money.of(this.currency, transactionAmount);
// FIXME - kw - check transactionDate is not before savings account if (isNotEnoughFundsToWithdraw(amount)) {
// 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)) {
// //
throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), transactionAmount); throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), transactionAmount);
} }
@@ -191,7 +371,24 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return transaction; 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()); return transactionDate.isAfter(DateUtils.getLocalDateOfTenant());
} }
@@ -199,7 +396,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return this.summary.getAccountBalance(this.currency).getAmount(); 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); return this.summary.isLessThanOrEqualToAccountBalance(amount);
} }
@@ -5,6 +5,8 @@
*/ */
package org.mifosplatform.portfolio.savings.domain; 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.interestRateParamName;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.interestRatePeriodFrequencyTypeParamName;
import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.lockinPeriodFrequencyParamName; 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 static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import java.math.BigDecimal; 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.api.JsonCommand;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
@@ -90,6 +96,9 @@ public class SavingsAccountAssembler {
interestRate = product.interestRate(); interestRate = product.interestRate();
} }
final boolean active = fromApiJsonHelper.extractBooleanNamed(activeParamName, element);
final LocalDate activationDate = fromApiJsonHelper.extractLocalDateNamed(activationDateParamName, element);
PeriodFrequencyType interestRatePeriodFrequencyType = null; PeriodFrequencyType interestRatePeriodFrequencyType = null;
Integer interestRatePeriodFrequencyTypeValue = null; Integer interestRatePeriodFrequencyTypeValue = null;
if (command.parameterExists(interestRatePeriodFrequencyTypeParamName)) { if (command.parameterExists(interestRatePeriodFrequencyTypeParamName)) {
@@ -128,11 +137,17 @@ public class SavingsAccountAssembler {
lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); lockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
} }
final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, interestRate, final SavingsAccount account = SavingsAccount.createNewAccount(client, group, product, accountNo, externalId, active,
interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, lockinPeriodFrequency, activationDate, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance,
lockinPeriodFrequencyType); lockinPeriodFrequency, lockinPeriodFrequencyType);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper); 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; return account;
} }
} }
@@ -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());
}
}
@@ -45,7 +45,7 @@ public final class SavingsAccountSummary {
public boolean isLessThanOrEqualToAccountBalance(final Money amount) { public boolean isLessThanOrEqualToAccountBalance(final Money amount) {
final Money accountBalance = getAccountBalance(amount.getCurrency()); final Money accountBalance = getAccountBalance(amount.getCurrency());
return accountBalance.isGreaterThanOrEqualTo(accountBalance); return accountBalance.isGreaterThanOrEqualTo(amount);
} }
public Money getAccountBalance(final MonetaryCurrency currency) { public Money getAccountBalance(final MonetaryCurrency currency) {
@@ -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);
}
}
@@ -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);
}
}
@@ -21,6 +21,6 @@ public class DepositSavingsAccountCommandHandler implements NewCommandSourceHand
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return this.savingAccountWritePlatformService.deposit(command.getLoanId(), command); return this.savingAccountWritePlatformService.deposit(command.getSavingsId(), command);
} }
} }
@@ -21,6 +21,6 @@ public class WithdrawSavingsAccountCommandHandler implements NewCommandSourceHan
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return this.savingAccountWritePlatformService.withdrawal(command.getLoanId(), command); return this.savingAccountWritePlatformService.withdrawal(command.getSavingsId(), command);
} }
} }
@@ -23,6 +23,7 @@ import org.mifosplatform.portfolio.group.data.GroupData;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData; 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.SavingsAccountSummaryData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData;
@@ -92,6 +93,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
public SavingAccountMapper() { public SavingAccountMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400); 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.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("c.id as clientId, c.display_name as clientName, ");
sqlBuilder.append("g.id as groupId, g.name as groupName, "); sqlBuilder.append("g.id as groupId, g.name as groupName, ");
sqlBuilder.append("sp.id as productId, sp.name as productName, "); 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 accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId"); 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 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 String clientName = rs.getString("clientName");
final Long productId = rs.getLong("productId"); final Long productId = rs.getLong("productId");
@@ -167,9 +174,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalInterestPosted, accountBalance); totalInterestPosted, accountBalance);
return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, return SavingsAccountData.instance(id, accountNo, externalId, status, activationDate, groupId, groupName, clientId, clientName,
currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate,
lockinPeriodFrequency, lockinPeriodFrequencyType, summary); minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary);
} }
} }
@@ -413,10 +420,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
groupName = group.getName(); groupName = group.getName();
} }
final SavingsAccountStatusEnumData status = null;
final LocalDate activationDate = null;
final SavingsAccountSummaryData summary = null; final SavingsAccountSummaryData summary = null;
return SavingsAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, return SavingsAccountData.instance(null, null, null, status, activationDate, groupId, groupName, clientId, clientName,
currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate, minRequiredOpeningBalance, productId, productName, currency, interestRate, interestRatePeriodFrequencyType, annualInterestRate,
lockinPeriodFrequency, lockinPeriodFrequencyType, summary); minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, summary);
} }
} }
} }
@@ -16,6 +16,8 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult deleteSavingAccount(Long accountId); CommandProcessingResult deleteSavingAccount(Long accountId);
CommandProcessingResult activate(Long savingsId, JsonCommand command);
CommandProcessingResult deposit(Long savingsId, JsonCommand command); CommandProcessingResult deposit(Long savingsId, JsonCommand command);
CommandProcessingResult withdrawal(Long savingsId, JsonCommand command); CommandProcessingResult withdrawal(Long savingsId, JsonCommand command);
@@ -7,9 +7,12 @@ package org.mifosplatform.portfolio.savings.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.joda.time.LocalDate; 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.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -61,8 +64,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountRepositoryWrapper savingAccountRepository,
final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountDataValidator savingsAccountDataValidator,
final SavingsAccountDataValidator savingsAccountDataValidator,
final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator,
final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepository clientRepository, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepository clientRepository,
final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository) { final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository) {
@@ -124,30 +126,31 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
} }
final Long savingsId = account.getId();
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(account.getId()) // .withEntityId(savingsId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
.withClientId(account.clientId()) // .withClientId(account.clientId()) //
.withGroupId(account.groupId()) // .withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.build(); .build();
} catch (DataAccessException dve) { } catch (DataAccessException dve) {
handleDataIntegrityIssues(command, dve); handleDataIntegrityIssues(command, dve);
return new CommandProcessingResult(Long.valueOf(-1)); return new CommandProcessingResult(Long.valueOf(-1));
} }
} }
@Transactional @Transactional
@Override @Override
public CommandProcessingResult updateSavingAccount(final Long accountId, final JsonCommand command) { public CommandProcessingResult updateSavingAccount(final Long savingsId, final JsonCommand command) {
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
this.savingsAccountDataValidator.validateForUpdate(command.json()); this.savingsAccountDataValidator.validateForUpdate(command.json());
final Map<String, Object> changes = new LinkedHashMap<String, Object>(20); final Map<String, Object> changes = new LinkedHashMap<String, Object>(20);
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(accountId); final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
account.update(command, changes); account.update(command, changes);
@@ -190,10 +193,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(account.getId()) // .withEntityId(savingsId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
.withClientId(account.clientId()) // .withClientId(account.clientId()) //
.withGroupId(account.groupId()) // .withGroupId(account.groupId()) //
.withSavingsId(savingsId) //
.with(changes) // .with(changes) //
.build(); .build();
} catch (DataAccessException dve) { } catch (DataAccessException dve) {
@@ -204,19 +208,46 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional @Transactional
@Override @Override
public CommandProcessingResult deleteSavingAccount(final Long accountId) { public CommandProcessingResult deleteSavingAccount(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(accountId); final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
this.savingAccountRepository.delete(account); this.savingAccountRepository.delete(account);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(accountId) // .withEntityId(savingsId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
.withClientId(account.clientId()) // .withClientId(account.clientId()) //
.withGroupId(account.groupId()) // .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(); .build();
} }
@@ -230,10 +261,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); 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 LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); 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); final Long transactionId = saveTransactionToGenerateTransactionId(deposit);
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
@@ -258,12 +292,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.context.authenticatedUser(); this.context.authenticatedUser();
this.savingsAccountTransactionDataValidator.validate(command);
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); 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); final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal);
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
@@ -7,7 +7,9 @@ package org.mifosplatform.portfolio.savings.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; 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.data.SavingsAccountTransactionEnumData;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType;
public class SavingsEnumerations { public class SavingsEnumerations {
@@ -103,4 +105,35 @@ public class SavingsEnumerations {
} }
return optionData; 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;
}
} }