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