merging conflicts for 1.8.0 RELEASE

This commit is contained in:
Vishwas Babu A J
2013-08-20 20:15:05 +05:30
228 changed files with 9710 additions and 2590 deletions
+1422 -74
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('GroupSavingSummary', 'Table', NULL, NULL, 'select ifnull(cur.display_symbol, sa.currency_code) as currency,\ncount(sa.id) as totalSavingAccounts, ifnull(sum(sa.account_balance_derived),0) as totalSavings\nfrom m_group topgroup\njoin m_office o on o.id = topgroup.office_id and o.hierarchy like concat(\'${currentUserHierarchy}\', \'%\')\njoin m_group g on g.hierarchy like concat(topgroup.hierarchy, \'%\')\njoin m_savings_account sa on sa.group_id = g.id\nleft join m_currency cur on cur.code = sa.currency_code\nwhere topgroup.id = ${groupId}\nand sa.activatedon_date is not null\ngroup by sa.currency_code', 'Utility query for getting group or center saving summary details for a group_id', 1, 0);
@@ -0,0 +1,2 @@
ALTER TABLE `m_savings_account_transfer`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL DEFAULT NULL AFTER `currency_digits`;
@@ -0,0 +1,30 @@
CREATE TABLE `m_meeting` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`calendar_instance_id` BIGINT(20) NOT NULL,
`meeting_date` DATE NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`, `meeting_date`),
CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;
CREATE TABLE `m_client_attendance` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`client_id` BIGINT(20) NOT NULL DEFAULT '0',
`meeting_id` BIGINT(20) NOT NULL,
`attendance_type_enum` SMALLINT(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `unique_client_meeting_attendance` (`client_id`, `meeting_id`),
INDEX `FK_m_meeting_m_client_attendance` (`meeting_id`),
CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0);
@@ -0,0 +1,14 @@
ALTER TABLE `job`
ADD COLUMN `is_misfired` TINYINT(1) NOT NULL DEFAULT '0' AFTER `scheduler_group`;
CREATE TABLE `scheduler_detail` (
`id` SMALLINT(2) NOT NULL AUTO_INCREMENT,
`is_suspended` TINYINT(1) NOT NULL DEFAULT '0',
`execute_misfired_jobs` TINYINT(1) NOT NULL DEFAULT '1',
`reset_scheduler_on_bootup` TINYINT(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
INSERT INTO `scheduler_detail` (`is_suspended`, `execute_misfired_jobs`, `reset_scheduler_on_bootup`) VALUES (FALSE,TRUE,TRUE);
@@ -0,0 +1,11 @@
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
VALUES ('portfolio', 'TRANSFER_CLIENT', 'CLIENT', 'TRANSFER', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
VALUES ('portfolio', 'TRANSFER_CLIENT_CHECKER', 'CLIENT', 'TRANSFER', 0);
@@ -0,0 +1,2 @@
INSERT INTO `m_permission` (grouping,code,`entity_name`,`action_name`,`can_maker_checker`)
values('portfolio_group','ASSIGNSTAFF_GROUP','GROUP','ASSIGNSTAFF',0);
@@ -0,0 +1,3 @@
ALTER TABLE m_loan_transaction
ADD COLUMN `external_id` VARCHAR(100) NULL DEFAULT NULL AFTER `is_reversed` ,
ADD UNIQUE INDEX `external_id_UNIQUE` (`external_id` ASC) ;
@@ -0,0 +1,2 @@
ALTER TABLE `m_savings_account`
ADD COLUMN `account_type_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `status_enum`;
@@ -0,0 +1 @@
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`) VALUES ('Post Interest For Savings', 'Post Interest For Savings', '0 0 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0);
@@ -0,0 +1,56 @@
INSERT INTO `m_permission`
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`)
VALUES
('transaction_savings', 'READ_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'READ', 0),
('transaction_savings', 'CREATE_ACCOUNTTRANSFER', 'ACCOUNTTRANSFER', 'CREATE', 1),
('transaction_savings', 'CREATE_ACCOUNTTRANSFER_CHECKER', 'ACCOUNTTRANSFER', 'CREATE', 0);
DROP TABLE IF EXISTS `m_savings_account_transfer`;
CREATE TABLE `m_savings_account_transfer` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_office_id` bigint(20) NOT NULL,
`to_office_id` bigint(20) NOT NULL,
`from_client_id` bigint(20) NOT NULL,
`to_client_id` bigint(20) NOT NULL,
`from_savings_account_id` bigint(20) DEFAULT NULL,
`to_savings_account_id` bigint(20) DEFAULT NULL,
`from_loan_account_id` bigint(20) DEFAULT NULL,
`to_loan_account_id` bigint(20) DEFAULT NULL,
`from_savings_transaction_id` bigint(20) DEFAULT NULL,
`from_loan_transaction_id` bigint(20) DEFAULT NULL,
`to_savings_transaction_id` bigint(20) DEFAULT NULL,
`to_loan_transaction_id` bigint(20) DEFAULT NULL,
`is_reversed` tinyint(1) NOT NULL,
`transaction_date` date NOT NULL,
`currency_code` varchar(3) NOT NULL,
`currency_digits` smallint(5) NOT NULL,
`amount` decimal(19,6) NOT NULL,
`description` varchar(200) NOT NULL,
PRIMARY KEY (`id`),
KEY `FKTRAN000000001` (`from_office_id`),
KEY `FKTRAN000000002` (`from_client_id`),
KEY `FKTRAN000000003` (`from_savings_account_id`),
KEY `FKTRAN000000004` (`to_office_id`),
KEY `FKTRAN000000005` (`to_client_id`),
KEY `FKTRAN000000006` (`to_savings_account_id`),
KEY `FKTRAN000000007` (`to_loan_account_id`),
KEY `FKTRAN000000008` (`from_savings_transaction_id`),
KEY `FKTRAN000000009` (`to_savings_transaction_id`),
KEY `FKTRAN000000010` (`to_loan_transaction_id`),
KEY `FKTRAN000000011` (`from_loan_account_id`),
KEY `FKTRAN000000012` (`from_loan_transaction_id`),
CONSTRAINT `FKTRAN000000001` FOREIGN KEY (`from_office_id`) REFERENCES `m_office` (`id`),
CONSTRAINT `FKTRAN000000002` FOREIGN KEY (`from_client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FKTRAN000000003` FOREIGN KEY (`from_savings_account_id`) REFERENCES `m_savings_account` (`id`),
CONSTRAINT `FKTRAN000000004` FOREIGN KEY (`to_office_id`) REFERENCES `m_office` (`id`),
CONSTRAINT `FKTRAN000000005` FOREIGN KEY (`to_client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FKTRAN000000006` FOREIGN KEY (`to_savings_account_id`) REFERENCES `m_savings_account` (`id`),
CONSTRAINT `FKTRAN000000007` FOREIGN KEY (`to_loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `FKTRAN000000008` FOREIGN KEY (`from_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
CONSTRAINT `FKTRAN000000009` FOREIGN KEY (`to_savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`),
CONSTRAINT `FKTRAN000000010` FOREIGN KEY (`to_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`),
CONSTRAINT `FKTRAN000000011` FOREIGN KEY (`from_loan_account_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `FKTRAN000000012` FOREIGN KEY (`from_loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -0,0 +1 @@
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_savings', 'ADJUSTTRANSACTION_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'ADJUSTTRANSACTION', 0);
@@ -0,0 +1,12 @@
ALTER TABLE `m_savings_account`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
ALTER TABLE `m_savings_product`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
ALTER TABLE `m_loan`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
ALTER TABLE `m_product_loan`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `currency_digits`;
ALTER TABLE `m_currency`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
ALTER TABLE `m_organisation_currency`
ADD COLUMN `currency_multiplesof` SMALLINT(5) NULL AFTER `decimal_places`;
+1 -1
View File
@@ -1 +1 @@
releaseVersion=1.7.2.RELEASE
releaseVersion=1.8.0.RELEASE
@@ -0,0 +1,117 @@
package org.mifosplatform.integrationtests;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.mifosplatform.integrationtests.common.ClientHelper;
import org.mifosplatform.integrationtests.common.GroupHelper;
import org.mifosplatform.integrationtests.common.Utils;
import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
/**
* Group Loan Integration Test for checking Loan Application Repayment
* Schedule.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class GroupLoanIntegrationTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper;
@Before
public void setup() {
Utils.initializeRESTAssured();
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
public void checkGroupLoanCreateAndDisburseFlow() {
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
Integer groupID = GroupHelper.createGroup(requestSpec, responseSpec, true);
groupID = GroupHelper.associateClient(requestSpec, responseSpec, groupID.toString(),clientID.toString());
Integer loanProductID = createLoanProduct();
Integer loanID = applyForLoanApplication(groupID, loanProductID);
ArrayList<HashMap> loanSchedule = loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
verifyLoanRepaymentSchedule(loanSchedule);
}
private Integer createLoanProduct() {
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
String loanProductJSON = new LoanProductTestBuilder() //
.withPrincipal("12,000.00") //
.withNumberOfRepayments("4") //
.withRepaymentAfterEvery("1") //
.withRepaymentTypeAsMonth() //
.withinterestRatePerPeriod("1") //
.withInterestRateFrequencyTypeAsMonths() //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
.build();
return loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private Integer applyForLoanApplication(final Integer groupID, final Integer loanProductID) {
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
String loanApplicationJSON = new LoanApplicationTestBuilder() //
.withPrincipal("12,000.00") //
.withLoanTermFrequency("4") //
.withLoanTermFrequencyAsMonths() //
.withNumberOfRepayments("4") //
.withRepaymentEveryAfter("1") //
.withRepaymentFrequencyTypeAsMonths() //
.withInterestRatePerPeriod("2") //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
.withExpectedDisbursementDate("20 September 2011") //
.withSubmittedOnDate("20 September 2011") //
.withLoanType("group")
.build(groupID.toString(), loanProductID.toString());
System.out.println(loanApplicationJSON);
return loanTransactionHelper.getLoanId(loanApplicationJSON);
}
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"));
assertEquals("Checking for Principal Due for 1st Month", new Float("2911.49"), loanSchedule.get(1).get("principalOriginalDue"));
assertEquals("Checking for Interest Due for 1st Month", new Float("240.00"), loanSchedule.get(1).get("interestOriginalDue"));
assertEquals("Checking for Due Date for 2nd Month", new ArrayList<Integer>(Arrays.asList(2011, 11, 20)),
loanSchedule.get(2).get("dueDate"));
assertEquals("Checking for Principal Due for 2nd Month", new Float("2969.71"), loanSchedule.get(2).get("principalDue"));
assertEquals("Checking for Interest Due for 2nd Month", new Float("181.77"), loanSchedule.get(2).get("interestOriginalDue"));
assertEquals("Checking for Due Date for 3rd Month", new ArrayList<Integer>(Arrays.asList(2011, 12, 20)),
loanSchedule.get(3).get("dueDate"));
assertEquals("Checking for Principal Due for 3rd Month", new Float("3029.1"), loanSchedule.get(3).get("principalDue"));
assertEquals("Checking for Interest Due for 3rd Month", new Float("122.38"), loanSchedule.get(3).get("interestOriginalDue"));
assertEquals("Checking for Due Date for 4th Month", new ArrayList<Integer>(Arrays.asList(2012, 1, 20)),
loanSchedule.get(4).get("dueDate"));
assertEquals("Checking for Principal Due for 4th Month", new Float("3089.7"), loanSchedule.get(4).get("principalDue"));
assertEquals("Checking for Interest Due for 4th Month", new Float("61.79"), loanSchedule.get(4).get("interestOriginalDue"));
}
}
@@ -0,0 +1,55 @@
package org.mifosplatform.integrationtests;
import org.junit.Before;
import org.junit.Test;
import org.mifosplatform.integrationtests.common.ClientHelper;
import org.mifosplatform.integrationtests.common.GroupHelper;
import org.mifosplatform.integrationtests.common.Utils;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
/**
* Group Test for checking Group: Creation, Activation, Client Association,
* Updating & Deletion
*/
public class GroupTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
@Before
public void setup() {
Utils.initializeRESTAssured();
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
public void checkGroupFunctions() {
Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
Integer groupID = GroupHelper.createGroup(requestSpec, responseSpec);
GroupHelper.verifyGroupCreatedOnServer(requestSpec, responseSpec, groupID);
groupID = GroupHelper.activateGroup(requestSpec, responseSpec, groupID.toString());
GroupHelper.verifyGroupActivatedOnServer(requestSpec, responseSpec, groupID, true);
groupID = GroupHelper.associateClient(requestSpec, responseSpec, groupID.toString(), clientID.toString());
GroupHelper.verifyGroupMembers(requestSpec, responseSpec, groupID, clientID);
groupID = GroupHelper.disAssociateClient(requestSpec, responseSpec, groupID.toString(), clientID.toString());
GroupHelper.verifyEmptyGroupMembers(requestSpec, responseSpec, groupID);
String updatedGroupName = GroupHelper.randomNameGenerator("Group-", 5);
groupID = GroupHelper.updateGroup(requestSpec, responseSpec, updatedGroupName, groupID.toString());
GroupHelper.verifyGroupDetails(requestSpec, responseSpec, groupID, "name", updatedGroupName);
groupID = GroupHelper.createGroup(requestSpec, responseSpec);
GroupHelper.deleteGroup(requestSpec, responseSpec, groupID.toString());
GroupHelper.verifyGroupDeleted(requestSpec, responseSpec, groupID);
}
}
@@ -0,0 +1,174 @@
package org.mifosplatform.integrationtests.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.google.gson.Gson;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
public class GroupHelper {
private static final String CREATE_GROUP_URL = "/mifosng-provider/api/v1/groups?tenantIdentifier=default";
public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
@SuppressWarnings("unused") final boolean active) {
System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
return createGroup(requestSpec, responseSpec, "04 March 2011");
}
public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String activationDate) {
System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(true, activationDate), "groupId");
}
public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
System.out.println("---------------------------------CREATING A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, CREATE_GROUP_URL, getTestGroupAsJSON(false, ""), "groupId");
}
public static Integer associateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId, final String clientMember) {
String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=associateClients&tenantIdentifier=default";
System.out.println("---------------------------------Associate Client To A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
}
public static Integer disAssociateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId, final String clientMember) {
String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=disassociateClients&tenantIdentifier=default";
System.out.println("---------------------------------Disassociate Client To A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
}
public static Integer activateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId) {
String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=activate&tenantIdentifier=default";
System.out.println("---------------------------------Activate A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, activateGroupAsJSON(""), "groupId");
}
public static Integer updateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String name,
final String groupId) {
String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
System.out.println("---------------------------------UPDATE GROUP---------------------------------------------");
return Utils.performServerPut(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, updateGroupAsJSON(name), "groupId");
}
public static Integer deleteGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId) {
String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
return Utils.performServerDelete(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, "groupId");
}
public static String getTestGroupAsJSON(final boolean active, final String activationDate) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("officeId", "1");
map.put("name", randomNameGenerator("Group_Name_", 5));
map.put("externalId", randomIDGenerator("ID_", 7));
if (active == true) {
map.put("active", "true");
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
map.put("activationDate", activationDate);
} else
map.put("active", "false");
System.out.println("map : " + map);
return new Gson().toJson(map);
}
public static String associateClientAsJSON(final String clientMember) {
HashMap<String, List<String>> map = new HashMap<String, List<String>>();
List<String> list = new ArrayList<String>();
list.add(clientMember);
map.put("clientMembers", list);
System.out.println("map : " + map);
return new Gson().toJson(map);
}
public static String activateGroupAsJSON(final String activationDate) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
if (activationDate != "")
map.put("activationDate", activationDate);
else
map.put("activationDate", "04 March 2011");
System.out.println("map : " + map);
return new Gson().toJson(map);
}
public static String updateGroupAsJSON(final String name) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("name", name);
System.out.println("map : " + map);
return new Gson().toJson(map);
}
public static void verifyGroupCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID) {
System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
Integer responseGroupID = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "id");
assertEquals("ERROR IN CREATING THE GROUP", generatedGroupID, responseGroupID);
}
public static void verifyGroupDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID, final String field, final String expectedValue) {
System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
String responseValue = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, field);
assertEquals("ERROR IN CREATING THE GROUP", expectedValue, responseValue);
}
public static void verifyGroupActivatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID, final boolean generatedGroupStatus) {
System.out.println("------------------------------CHECK GROUP STATUS------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
Boolean responseGroupStatus = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "active");
assertEquals("ERROR IN ACTIVATING THE GROUP", generatedGroupStatus, responseGroupStatus);
}
public static void verifyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID, final Integer groupMember) {
List<String> list = new ArrayList<String>();
System.out.println("------------------------------CHECK GROUP MEMBERS------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?associations=clientMembers&tenantIdentifier=default";
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
assertTrue("ERROR IN GROUP MEMBER", list.toString().contains("id=" + groupMember.toString()));
}
public static void verifyEmptyGroupMembers(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID) {
List<String> list = new ArrayList<String>();
System.out.println("------------------------------CHECK EMPTY GROUP MEMBER LIST------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?associations=clientMembers&tenantIdentifier=default";
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
assertEquals("GROUP MEMBER LIST NOT EMPTY", list, null);
}
public static void verifyGroupDeleted(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID) {
List<String> list = new ArrayList<String>();
System.out.println("------------------------------CHECK GROUP DELETED------------------------------------\n");
String GROUP_URL = "/mifosng-provider/api/v1/groups/?tenantIdentifier=default";
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "pageItems");
assertFalse("GROUP NOT DELETED", list.toString().contains("id=" + generatedGroupID.toString()));
}
public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) {
return Utils.randomStringGenerator(prefix, lenOfRandomSuffix);
}
private static String randomIDGenerator(final String prefix, final int lenOfRandomSuffix) {
return Utils.randomStringGenerator(prefix, lenOfRandomSuffix, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
}
@@ -6,8 +6,6 @@ import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.apache.http.conn.HttpHostConnectException;
@@ -16,13 +14,19 @@ import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
import java.lang.System;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Random;
@SuppressWarnings("unchecked")
public class Utils {
private static final String LOGIN_URL = "/mifosng-provider/api/v1/authentication?username=mifos&password=password&tenantIdentifier=default";
public static void initializeRESTAssured() {
RestAssured.baseURI = "https://localhost";
RestAssured.baseURI ="https://localhost";
RestAssured.port = 8443;
RestAssured.keystore("../keystore.jks", "openmf");
}
@@ -33,10 +37,11 @@ public class Utils {
String json = RestAssured.post(LOGIN_URL).asString();
assertThat("Failed to login into mifosx platform", StringUtils.isBlank(json), is(false));
return JsonPath.with(json).get("base64EncodedAuthenticationKey");
} catch (Exception e) {
}
catch (Exception e) {
if (e instanceof HttpHostConnectException) {
HttpHostConnectException hh = (HttpHostConnectException) e;
fail("Failed to connect to mifosx platform:" + hh.getMessage());
fail("Failed to connect to mifosx platform:"+hh.getMessage());
}
throw new RuntimeException(e);
@@ -44,18 +49,52 @@ public class Utils {
}
public static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String getURL, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
final String getURL, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec)
.expect().spec(responseSpec).log().ifError()
.when().get(getURL).andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
public static <T> T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL)
final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec).body(jsonBodyToSend)
.expect().spec(responseSpec).log().ifError()
.when().post(postURL)
.andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
public static <T> T performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String putURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec).body(jsonBodyToSend)
.expect().spec(responseSpec).log().ifError()
.when().put(putURL)
.andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
public static <T> T performServerDelete(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String deleteURL, final String jsonAttributeToGetBack) {
String json = given().spec(requestSpec)
.expect().spec(responseSpec).log().ifError()
.when().delete(deleteURL)
.andReturn().asString();
return (T) from(json).get(jsonAttributeToGetBack);
}
public static String convertDateToURLFormat(String dateToBeConvert){
SimpleDateFormat oldFormat = new SimpleDateFormat("dd MMMMMM yyyy");
SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd");
String reformattedStr="";
try {
reformattedStr = newFormat.format(oldFormat.parse(dateToBeConvert));
} catch (ParseException e) {
e.printStackTrace();
}
return reformattedStr;
}
public static String randomStringGenerator(final String prefix, final int len, final String sourceSetString) {
int lengthOfSource = sourceSetString.length();
Random rnd = new Random();
@@ -33,12 +33,15 @@ public class LoanApplicationTestBuilder {
private String submittedOnDate = "";
private String loanType = "individual";
public String build(final String clientID, final String loanProductId) {
public String build(final String ID, final String loanProductId) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en_GB");
map.put("clientId", clientID);
if (loanType == "group")
map.put("groupId", ID);
else
map.put("clientId", ID);
map.put("productId", loanProductId);
map.put("principal", principal);
map.put("loanTermFrequency", loanTermFrequency);
@@ -33,7 +33,6 @@ public class GLClosure extends AbstractAuditableCustom<AppUser, Long> {
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@SuppressWarnings("unused")
@Column(name = "is_deleted", nullable = false)
private boolean deleted = true;
@@ -35,20 +35,16 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
@JoinColumn(name = "account_id", nullable = false)
private GLAccount glAccount;
@SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reversal_id")
private JournalEntry reversalJournalEntry;
@SuppressWarnings("unused")
@Column(name = "transaction_id", nullable = false, length = 50)
private String transactionId;
@SuppressWarnings("unused")
@Column(name = "reversed", nullable = false)
private boolean reversed = false;
@SuppressWarnings("unused")
@Column(name = "manual_entry", nullable = false)
private boolean manualEntry = false;
@@ -62,15 +58,12 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@SuppressWarnings("unused")
@Column(name = "description", length = 500)
private String description;
@SuppressWarnings("unused")
@Column(name = "entity_type_enum", length = 50)
private Integer entityType;
@SuppressWarnings("unused")
@Column(name = "entity_id")
private Long entityId;
@@ -20,7 +20,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "acc_rule_tags", uniqueConstraints = { @UniqueConstraint(columnNames = { "acc_rule_id", "tag_id", "acc_type_enum" }, name = "UNIQUE_ACCOUNT_RULE_TAGS") })
public class AccountingTagRule extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "acc_rule_id", nullable = false)
private AccountingRule accountingRule;
@@ -25,171 +25,175 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_command_source")
public class CommandSource extends AbstractPersistable<Long> {
@Column(name = "action_name", nullable = true, length = 100)
private String actionName;
@Column(name = "action_name", nullable = true, length = 100)
private String actionName;
@Column(name = "entity_name", nullable = true, length = 100)
private String entityName;
@Column(name = "entity_name", nullable = true, length = 100)
private String entityName;
@SuppressWarnings("unused")
@Column(name = "office_id")
private Long officeId;
@Column(name = "office_id")
private Long officeId;
@SuppressWarnings("unused")
@Column(name = "group_id")
private Long groupId;
@Column(name = "group_id")
private Long groupId;
@SuppressWarnings("unused")
@Column(name = "client_id")
private Long clientId;
@Column(name = "client_id")
private Long clientId;
@SuppressWarnings("unused")
@Column(name = "loan_id")
private Long loanId;
@Column(name = "loan_id")
private Long loanId;
@SuppressWarnings("unused")
@Column(name = "savings_account_id")
private Long savingsId;
@Column(name = "savings_account_id")
private Long savingsId;
@Column(name = "api_get_url", length = 100)
private String resourceGetUrl;
@Column(name = "api_get_url", length = 100)
private String resourceGetUrl;
@Column(name = "resource_id")
private Long resourceId;
@Column(name = "resource_id")
private Long resourceId;
@Column(name = "subresource_id")
private Long subresourceId;
@Column(name = "subresource_id")
private Long subresourceId;
@Column(name = "command_as_json", length = 1000)
private String commandAsJson;
@Column(name = "command_as_json", length = 1000)
private String commandAsJson;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "maker_id", nullable = false)
private AppUser maker;
@ManyToOne
@JoinColumn(name = "maker_id", nullable = false)
private AppUser maker;
@SuppressWarnings("unused")
@Column(name = "made_on_date", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date madeOnDate;
@Column(name = "made_on_date", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date madeOnDate;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "checker_id", nullable = true)
private AppUser checker;
@ManyToOne
@JoinColumn(name = "checker_id", nullable = true)
private AppUser checker;
@SuppressWarnings("unused")
@Column(name = "checked_on_date", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date checkedOnDate;
@Column(name = "checked_on_date", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date checkedOnDate;
@Column(name = "processing_result_enum", nullable = false)
private Integer processingResult;
@Column(name = "product_id")
private Long productId;
@Column(name = "processing_result_enum", nullable = false)
private Integer processingResult;
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) {
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
command.json(), maker, DateTime.now());
}
@Column(name = "product_id")
private Long productId;
protected CommandSource() {
//
}
public static CommandSource fullEntryFrom(final CommandWrapper wrapper,
final JsonCommand command, final AppUser maker) {
return new CommandSource(wrapper.actionName(), wrapper.entityName(),
wrapper.getHref(), command.entityId(), command.subentityId(),
command.json(), maker, DateTime.now());
}
private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
final Long subresourceId, final String commandSerializedAsJson, final AppUser maker, final DateTime madeOnDateTime) {
this.actionName = actionName;
this.entityName = entityName;
this.resourceGetUrl = href;
this.resourceId = resourceId;
this.subresourceId = subresourceId;
this.commandAsJson = commandSerializedAsJson;
this.maker = maker;
this.madeOnDate = madeOnDateTime.toDate();
this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
}
protected CommandSource() {
//
}
public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) {
this.checker = checker;
this.checkedOnDate = checkedOnDate.toDate();
this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
}
private CommandSource(final String actionName, final String entityName,
final String href, final Long resourceId, final Long subresourceId,
final String commandSerializedAsJson, final AppUser maker,
final DateTime madeOnDateTime) {
this.actionName = actionName;
this.entityName = entityName;
this.resourceGetUrl = href;
this.resourceId = resourceId;
this.subresourceId = subresourceId;
this.commandAsJson = commandSerializedAsJson;
this.maker = maker;
this.madeOnDate = madeOnDateTime.toDate();
this.processingResult = CommandProcessingResultType.PROCESSED
.getValue();
}
public void updateResourceId(final Long resourceId) {
this.resourceId = resourceId;
}
public void markAsChecked(final AppUser checker,
final DateTime checkedOnDate) {
this.checker = checker;
this.checkedOnDate = checkedOnDate.toDate();
this.processingResult = CommandProcessingResultType.PROCESSED
.getValue();
}
public void updateSubresourceId(final Long subresourceId) {
this.subresourceId = subresourceId;
}
public void updateResourceId(final Long resourceId) {
this.resourceId = resourceId;
}
public void updateJsonTo(final String json) {
this.commandAsJson = json;
}
public void updateSubresourceId(final Long subresourceId) {
this.subresourceId = subresourceId;
}
public Long resourceId() {
return this.resourceId;
}
public void updateJsonTo(final String json) {
this.commandAsJson = json;
}
public Long subresourceId() {
return this.subresourceId;
}
public Long resourceId() {
return this.resourceId;
}
public boolean hasJson() {
return StringUtils.isNotBlank(this.commandAsJson);
}
public Long subresourceId() {
return this.subresourceId;
}
public String json() {
return this.commandAsJson;
}
public boolean hasJson() {
return StringUtils.isNotBlank(this.commandAsJson);
}
public String getActionName() {
return this.actionName;
}
public String json() {
return this.commandAsJson;
}
public String getEntityName() {
return this.entityName;
}
public String getActionName() {
return this.actionName;
}
public String getPermissionCode() {
return this.actionName + "_" + this.entityName;
}
public String getEntityName() {
return this.entityName;
}
public Long getResourceId() {
return this.resourceId;
}
public String getPermissionCode() {
return this.actionName + "_" + this.entityName;
}
public Long getSubresourceId() {
return this.subresourceId;
}
public Long getResourceId() {
return this.resourceId;
}
public void markAsAwaitingApproval() {
this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
}
public Long getSubresourceId() {
return this.subresourceId;
}
public boolean isMarkedAsAwaitingApproval() {
if (this.processingResult.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue())) return true;
public void markAsAwaitingApproval() {
this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL
.getValue();
}
return false;
}
public boolean isMarkedAsAwaitingApproval() {
if (this.processingResult
.equals(CommandProcessingResultType.AWAITING_APPROVAL
.getValue())) {
return true;
}
public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
final Long productId) {
this.officeId = officeId;
this.groupId = groupId;
this.clientId = clientId;
this.loanId = loanId;
this.savingsId = savingsId;
this.productId = productId;
}
return false;
}
public String getResourceGetUrl() {
return this.resourceGetUrl;
}
public void updateForAudit(final Long officeId, final Long groupId,
final Long clientId, final Long loanId, final Long savingsId,
final Long productId) {
this.officeId = officeId;
this.groupId = groupId;
this.clientId = clientId;
this.loanId = loanId;
this.savingsId = savingsId;
this.productId = productId;
}
public Long getProductId() {
return this.productId;
}
public String getResourceGetUrl() {
return this.resourceGetUrl;
}
public Long getProductId() {
return this.productId;
}
}
@@ -413,7 +413,11 @@ public class CommandWrapper {
public boolean isUnassignStaff() {
return this.actionName.equalsIgnoreCase("UNASSIGNSTAFF") && this.entityName.equalsIgnoreCase("GROUP");
}
public boolean isAssignStaff() {
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF");
}
public String commandName() {
return this.actionName + "_" + this.entityName;
}
@@ -474,6 +478,10 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("UNDOTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isAdjustSavingsAccountTransaction() {
return this.actionName.equalsIgnoreCase("ADJUSTTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR");
}
@@ -543,6 +551,10 @@ public class CommandWrapper {
public boolean isUpdateGroupRole() {
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("UPDATEROLE");
}
public boolean isTransferClientsBetweenGroups() {
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("TRANSFERCLIENTS");
}
public boolean isClientAssignStaff() {
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF") && this.entityName.equalsIgnoreCase("CLIENT");
@@ -551,6 +563,10 @@ public class CommandWrapper {
public boolean isClientClose() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
}
public boolean isClientTransfer() {
return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT");
}
public boolean isProductMixResource() {
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
@@ -559,4 +575,16 @@ public class CommandWrapper {
public boolean isSchedulerResource() {
return this.entityName.equalsIgnoreCase("SCHEDULER");
}
public boolean isAccountTransferResource() {
return this.entityName.equalsIgnoreCase("ACCOUNTTRANSFER");
}
public boolean isMeetingResource() {
return this.entityName.equalsIgnoreCase("MEETING");
}
public boolean isSaveOrUpdateAttendance(){
return this.actionName.equalsIgnoreCase("SAVEORUPDATEATTENDANCE");
}
}
@@ -342,7 +342,7 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "?command=activate&template=true";
return this;
}
public CommandWrapperBuilder closeClient(final Long clientId) {
this.actionName = "CLOSE";
this.entityName = "CLIENT";
@@ -351,6 +351,15 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "?command=close&template=true";
return this;
}
public CommandWrapperBuilder transferClient(final Long clientId) {
this.actionName = "TRANSFER";
this.entityName = "CLIENT";
this.entityId = clientId;
this.clientId = clientId;
this.href = "/clientId/" + clientId + "?command=transfer";
return this;
}
public CommandWrapperBuilder updateClient(final Long clientId) {
this.actionName = "UPDATE";
@@ -372,30 +381,30 @@ public class CommandWrapperBuilder {
}
public CommandWrapperBuilder createDBDatatable(final String json) {
this.actionName = "CREATE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/";
this.json = json;
return this;
this.actionName = "CREATE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/";
this.json = json;
return this;
}
public CommandWrapperBuilder updateDBDatatable(final String datatable, final String json) {
this.actionName = "UPDATE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/" + datatable;
this.json = json;
return this;
this.actionName = "UPDATE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/" + datatable;
this.json = json;
return this;
}
public CommandWrapperBuilder deleteDBDatatable(final String datatable, final String json) {
this.actionName = "DELETE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/" + datatable;
this.json = json;
return this;
this.actionName = "DELETE";
this.entityName = "DATATABLE";
this.entityId = null;
this.href = "/datatables/" + datatable;
this.json = json;
return this;
}
public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
@@ -808,6 +817,14 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder createAccountTransfer() {
this.actionName = "CREATE";
this.entityName = "ACCOUNTTRANSFER";
this.entityId = null;
this.href = "/accounttransfers";
return this;
}
public CommandWrapperBuilder savingsAccountDeposit(final Long accountId) {
this.actionName = "DEPOSIT";
this.entityName = "SAVINGSACCOUNT";
@@ -837,6 +854,17 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder adjustSavingsAccountTransaction(final Long accountId, final Long transactionId) {
this.actionName = "ADJUSTTRANSACTION";
this.entityName = "SAVINGSACCOUNT";
this.savingsId = accountId;
this.entityId = accountId;
this.subentityId = transactionId;
this.transactionId = transactionId.toString();
this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=modify";
return this;
}
public CommandWrapperBuilder savingsAccountInterestCalculation(final Long accountId) {
this.actionName = "CALCULATEINTEREST";
this.entityName = "SAVINGSACCOUNT";
@@ -945,7 +973,7 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=activate";
return this;
}
public CommandWrapperBuilder saveGroupCollectionSheet(final Long groupId) {
this.actionName = "SAVECOLLECTIONSHEET";
this.entityName = "GROUP";
@@ -954,7 +982,7 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=saveCollectionSheet";
return this;
}
public CommandWrapperBuilder deleteGroup(final Long groupId) {
this.actionName = "DELETE";
this.entityName = "GROUP";
@@ -981,6 +1009,15 @@ public class CommandWrapperBuilder {
this.href = "/groups/" + groupId + "?command=disassociateClients";
return this;
}
public CommandWrapperBuilder transferClientsBetweenGroups(final Long sourceGroupId) {
this.actionName = "TRANSFERCLIENTS";
this.entityName = "GROUP";
this.entityId = sourceGroupId;
this.groupId = sourceGroupId;
this.href = "/groups/" + sourceGroupId + "?command=transferClients";
return this;
}
public CommandWrapperBuilder unassignGroupStaff(final Long groupId) {
this.actionName = "UNASSIGNSTAFF";
@@ -991,6 +1028,15 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder assignGroupStaff(final Long groupId) {
this.actionName = "ASSIGNSTAFF";
this.entityName = "GROUP";
this.entityId = groupId;
this.groupId = groupId;
this.href = "/groups/" + groupId + "?command=assignStaff";
return this;
}
public CommandWrapperBuilder createCollateral(final Long loanId) {
this.actionName = "CREATE";
this.entityName = "COLLATERAL";
@@ -1089,7 +1135,7 @@ public class CommandWrapperBuilder {
this.href = "/accountingrules/" + accountingRuleId;
return this;
}
public CommandWrapperBuilder createHoliday() {
this.actionName = "CREATE";
this.entityName = "HOLIDAY";
@@ -1103,7 +1149,7 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = null;
this.href = "/groups/"+groupId+"?command=assignRole";
this.href = "/groups/" + groupId + "?command=assignRole";
return this;
}
@@ -1112,7 +1158,7 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = roleId;
this.href = "/groups/"+groupId+"?command=unassignRole";
this.href = "/groups/" + groupId + "?command=unassignRole";
return this;
}
@@ -1121,10 +1167,10 @@ public class CommandWrapperBuilder {
this.entityName = "GROUP";
this.groupId = groupId;
this.entityId = roleId;
this.href = "/groups/"+groupId+"?command=updateRole";
this.href = "/groups/" + groupId + "?command=updateRole";
return this;
}
public CommandWrapperBuilder unassignClientStaff(final Long clientId) {
this.actionName = "UNASSIGNSTAFF";
this.entityName = "CLIENT";
@@ -1160,7 +1206,7 @@ public class CommandWrapperBuilder {
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
public CommandWrapperBuilder deleteProductMix(final Long productId) {
this.actionName = "DELETE";
this.entityName = "PRODUCTMIX";
@@ -1169,17 +1215,52 @@ public class CommandWrapperBuilder {
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
public CommandWrapperBuilder withProduct(final Long productId) {
this.productId = productId;
return this;
}
public CommandWrapperBuilder updateJobDetail(final Long jobId) {
this.actionName = "UPDATE";
this.entityName = "SCHEDULER";
this.entityId = jobId;
this.href = "/updateJobDetail/" + jobId+"/updateJobDetail";
this.href = "/updateJobDetail/" + jobId + "/updateJobDetail";
return this;
}
public CommandWrapperBuilder createMeeting(final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "CREATE";
this.entityName = "MEETING";
this.supportedEntityType = supportedEntityType;
this.supportedEntityId = supportedEntityId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings";
return this;
}
public CommandWrapperBuilder updateMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
this.actionName = "UPDATE";
this.entityName = "MEETING";
this.entityId = meetingId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
return this;
}
public CommandWrapperBuilder deleteMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
this.actionName = "DELETE";
this.entityName = "MEETING";
this.entityId = meetingId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
return this;
}
public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "SAVEORUPDATEATTENDANCE";
this.entityName = "MEETING";
this.entityId = entityId;
this.supportedEntityType = supportedEntityType;
this.supportedEntityId = supportedEntityId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + entityId + "?command=saveOrUpdateAttendance";
return this;
}
}
@@ -105,7 +105,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
private NewCommandSourceHandler findCommandHandler(final CommandWrapper wrapper) {
NewCommandSourceHandler handler = null;
if (wrapper.isConfigurationResource()) {
if (wrapper.isAccountTransferResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createAccountTransferCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
}
else if (wrapper.isConfigurationResource()) {
handler = this.applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDatatableResource()) {
if (wrapper.isCreateDatatable()) {
@@ -163,6 +170,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isClientClose()) {
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isClientTransfer()) {
handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -405,6 +414,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("applyAnnualFeeSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSavingsAccountUndoTransaction()) {
handler = this.applicationContext.getBean("undoTransactionSavingsAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isAdjustSavingsAccountTransaction()) {
handler = this.applicationContext.getBean("savingsTransactionAdjustmentCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -441,6 +452,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("unassignRoleCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateGroupRole()) {
handler = this.applicationContext.getBean("updateGroupRoleCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isAssignStaff()) {
handler = this.applicationContext.getBean("assignGroupStaffCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isTransferClientsBetweenGroups()) {
handler = this.applicationContext.getBean("transferClientsBetweenGroupsCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -511,6 +526,18 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isMeetingResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
handler = this.applicationContext.getBean("updateMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDelete()) {
handler = this.applicationContext.getBean("deleteMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSaveOrUpdateAttendance()) {
handler = this.applicationContext.getBean("updateMeetingAttendanceCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -22,72 +22,85 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_code_value", uniqueConstraints = { @UniqueConstraint(columnNames = { "code_id", "code_value" }, name = "code_value_duplicate") })
@Table(name = "m_code_value", uniqueConstraints = { @UniqueConstraint(columnNames = {
"code_id", "code_value" }, name = "code_value_duplicate") })
public class CodeValue extends AbstractPersistable<Long> {
@Column(name = "code_value", length = 100)
private String label;
@Column(name = "code_value", length = 100)
private String label;
@Column(name = "order_position")
private int position;
@Column(name = "order_position")
private int position;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "code_id", nullable = false)
private Code code;
@ManyToOne
@JoinColumn(name = "code_id", nullable = false)
private Code code;
public static CodeValue createNew(Code code, final String label, final int position) {
return new CodeValue(code, label, position);
}
public static CodeValue createNew(final Code code, final String label,
final int position) {
return new CodeValue(code, label, position);
}
protected CodeValue() {
//
}
protected CodeValue() {
//
}
private CodeValue(final Code code, final String label, final int position) {
this.code = code;
this.label = StringUtils.defaultIfEmpty(label, null);
this.position = position;
}
private CodeValue(final Code code, final String label, final int position) {
this.code = code;
this.label = StringUtils.defaultIfEmpty(label, null);
this.position = position;
}
public String label() {
return label;
}
public String label() {
return this.label;
}
public int position() {
return position;
}
public static CodeValue fromJson(Code code, final JsonCommand command) {
public int position() {
return this.position;
}
final String label = command.stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue());
Integer position = command.integerValueSansLocaleOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue());
if(position == null) position = new Integer(0);
return new CodeValue(code, label, position.intValue());
}
public Map<String, Object> update(final JsonCommand command) {
public static CodeValue fromJson(final Code code, final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(2);
final String label = command
.stringValueOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.NAME
.getValue());
Integer position = command
.integerValueSansLocaleOfParameterNamed(CODEVALUE_JSON_INPUT_PARAMS.POSITION
.getValue());
if (position == null) {
position = new Integer(0);
}
return new CodeValue(code, label, position.intValue());
}
final String labelParamName = CODEVALUE_JSON_INPUT_PARAMS.NAME.getValue();
if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
final String newValue = command.stringValueOfParameterNamed(labelParamName);
actualChanges.put(labelParamName, newValue);
this.label = StringUtils.defaultIfEmpty(newValue, null);
}
final String positionParamName = CODEVALUE_JSON_INPUT_PARAMS.POSITION.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(positionParamName, this.position)) {
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(positionParamName);
actualChanges.put(positionParamName, newValue);
this.position = newValue.intValue();
}
return actualChanges;
}
public Map<String, Object> update(final JsonCommand command) {
public CodeValueData toData() {
return CodeValueData.instance(this.getId(), this.label, this.position);
}
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(
2);
final String labelParamName = CODEVALUE_JSON_INPUT_PARAMS.NAME
.getValue();
if (command.isChangeInStringParameterNamed(labelParamName, this.label)) {
final String newValue = command
.stringValueOfParameterNamed(labelParamName);
actualChanges.put(labelParamName, newValue);
this.label = StringUtils.defaultIfEmpty(newValue, null);
}
final String positionParamName = CODEVALUE_JSON_INPUT_PARAMS.POSITION
.getValue();
if (command.isChangeInIntegerSansLocaleParameterNamed(
positionParamName, this.position)) {
final Integer newValue = command
.integerValueSansLocaleOfParameterNamed(positionParamName);
actualChanges.put(positionParamName, newValue);
this.position = newValue.intValue();
}
return actualChanges;
}
public CodeValueData toData() {
return CodeValueData.instance(getId(), this.label, this.position);
}
}
@@ -14,10 +14,9 @@ import javax.persistence.UniqueConstraint;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "c_external_service", uniqueConstraints = { @UniqueConstraint(columnNames = ("name")) })
@Table(name = "c_external_service", uniqueConstraints = { @UniqueConstraint(columnNames = "name") })
public class ExternalService extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@Column(name = "name", nullable = false)
private String name;
@@ -15,7 +15,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "c_configuration")
public class GlobalConfigurationProperty extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@Column(name = "name", nullable = false)
private final String name;
@@ -306,6 +306,10 @@ public final class JsonCommand {
final String value = this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand);
return StringUtils.defaultIfEmpty(value, "");
}
public String stringValueOfParameterNamedAllowingNull(final String parameterName) {
return this.fromApiJsonHelper.extractStringNamed(parameterName, parsedCommand);
}
public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) {
boolean isChanged = false;
@@ -128,6 +128,7 @@ public class DataValidatorBuilder {
return this;
}
/***FIXME: Vishwas, why does this method have a parameter? Seems wrong***/
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
if (!trueOfFalseFieldProvided && !ignoreNullValue) {
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
@@ -44,7 +44,6 @@ public class ScheduledJobDetail extends AbstractPersistable<Long> {
@Temporal(TemporalType.TIMESTAMP)
private Date previousRunStartTime;
@SuppressWarnings("unused")
@Column(name = "next_run_time")
@Temporal(TemporalType.TIMESTAMP)
private Date nextRunTime;
@@ -69,6 +68,9 @@ public class ScheduledJobDetail extends AbstractPersistable<Long> {
@Column(name = "scheduler_group")
private Short schedulerGroup;
@Column(name = "is_misfired")
private boolean triggerMisfired;
protected ScheduledJobDetail() {
}
@@ -109,11 +111,15 @@ public class ScheduledJobDetail extends AbstractPersistable<Long> {
this.previousRunStartTime = previousRunStartTime;
}
public Date getNextRunTime() {
return this.nextRunTime;
}
public void updateNextRunTime(final Date nextRunTime) {
this.nextRunTime = nextRunTime;
}
public void updateJobKey(String jobKey) {
public void updateJobKey(final String jobKey) {
this.jobKey = jobKey;
}
@@ -125,7 +131,7 @@ public class ScheduledJobDetail extends AbstractPersistable<Long> {
return this.currentlyRunning;
}
public void updateCurrentlyRunningStatus(boolean currentlyRunning) {
public void updateCurrentlyRunningStatus(final boolean currentlyRunning) {
this.currentlyRunning = currentlyRunning;
}
@@ -152,4 +158,12 @@ public class ScheduledJobDetail extends AbstractPersistable<Long> {
return actualChanges;
}
public boolean isTriggerMisfired() {
return this.triggerMisfired;
}
public void updateTriggerMisfired(boolean triggerMisfired) {
this.triggerMisfired = triggerMisfired;
}
}
@@ -1,7 +1,10 @@
package org.mifosplatform.infrastructure.jobs.domain;
import javax.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -14,4 +17,9 @@ public interface ScheduledJobDetailRepository extends JpaRepository<ScheduledJob
@Query("from ScheduledJobDetail jobDetail where jobDetail.id=:jobId")
ScheduledJobDetail findByJobId(@Param("jobId") Long jobId);
@Lock(value=LockModeType.PESSIMISTIC_WRITE)
@Query("from ScheduledJobDetail jobDetail where jobDetail.jobKey = :jobKey")
ScheduledJobDetail findByJobKeyWithLock(@Param("jobKey") String jobKey);
}
@@ -16,39 +16,39 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "job_run_history")
public class ScheduledJobRunHistory extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "job_id")
@SuppressWarnings("unused")
private ScheduledJobDetail scheduledJobDetail;
@SuppressWarnings("unused")
@Column(name = "version")
@SuppressWarnings("unused")
private Long version;
@SuppressWarnings("unused")
@Column(name = "start_time")
@Temporal(TemporalType.TIMESTAMP)
@SuppressWarnings("unused")
private Date startTime;
@SuppressWarnings("unused")
@Column(name = "end_time")
@Temporal(TemporalType.TIMESTAMP)
@SuppressWarnings("unused")
private Date endTime;
@SuppressWarnings("unused")
@Column(name = "status")
@SuppressWarnings("unused")
private String status;
@SuppressWarnings("unused")
@Column(name = "error_message")
@SuppressWarnings("unused")
private String errorMessage;
@SuppressWarnings("unused")
@Column(name = "trigger_type")
@SuppressWarnings("unused")
private String triggerType;
@SuppressWarnings("unused")
@Column(name = "error_log")
@SuppressWarnings("unused")
private String errorLog;
public ScheduledJobRunHistory() {
@@ -0,0 +1,49 @@
package org.mifosplatform.infrastructure.jobs.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "scheduler_detail")
public class SchedulerDetail extends AbstractPersistable<Long> {
@Column(name = "execute_misfired_jobs")
private boolean executeInstructionForMisfiredJobs;
@Column(name = "is_suspended")
private boolean suspended;
@Column(name = "reset_scheduler_on_bootup")
private boolean resetSchedulerOnBootup;
protected SchedulerDetail() {
}
public boolean isExecuteInstructionForMisfiredJobs() {
return this.executeInstructionForMisfiredJobs;
}
public void updateExecuteInstructionForMisfiredJobs(boolean executeInstructionForMisfiredJobs) {
this.executeInstructionForMisfiredJobs = executeInstructionForMisfiredJobs;
}
public boolean isSuspended() {
return this.suspended;
}
public void updateSuspendedState(boolean suspended) {
this.suspended = suspended;
}
public boolean isResetSchedulerOnBootup() {
return this.resetSchedulerOnBootup;
}
public void updateResetSchedulerOnBootup(boolean resetSchedulerOnBootup) {
this.resetSchedulerOnBootup = resetSchedulerOnBootup;
}
}
@@ -0,0 +1,8 @@
package org.mifosplatform.infrastructure.jobs.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SchedulerDetailRepository extends JpaRepository<SchedulerDetail, Long>, JpaSpecificationExecutor<SchedulerDetail> {
}
@@ -4,7 +4,7 @@ public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
"Apply Holidays To Loans");
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings");
private final String name;
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
@@ -15,6 +14,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformInternalServerExc
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronMethodParser;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
import org.mifosplatform.infrastructure.jobs.exception.JobNotFoundException;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.quartz.JobDataMap;
@@ -24,6 +24,7 @@ import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -50,8 +51,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
private final TenantDetailsService tenantDetailsService;
private final HashMap<Long, Boolean> schedulerStatus = new HashMap<Long, Boolean>(2);
private final HashMap<String, Scheduler> schedulers = new HashMap<String, Scheduler>(4);
@Autowired
@@ -68,24 +67,32 @@ public class JobRegisterServiceImpl implements JobRegisterService {
for (MifosPlatformTenant tenant : allTenants) {
ThreadLocalContextUtil.setTenant(tenant);
List<ScheduledJobDetail> scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
schedulerStatus.put(tenant.getId(), true);
for (ScheduledJobDetail jobDetails : scheduledJobDetails) {
scheduleJob(jobDetails);
jobDetails.updateTriggerMisfired(false);
this.schedularWritePlatformService.saveOrUpdate(jobDetails);
}
SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
if (schedulerDetail.isResetSchedulerOnBootup()) {
schedulerDetail.updateSuspendedState(false);
this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
}
}
}
public void executeJob(final ScheduledJobDetail scheduledJobDetail) {
public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
try {
final JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, SchedulerServiceConstants.TRIGGER_TYPE_APPLICATION);
if (triggerType == null) {
triggerType = SchedulerServiceConstants.TRIGGER_TYPE_APPLICATION;
}
jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
String key = scheduledJobDetail.getJobKey();
JobKey jobKey = constructJobKey(key);
String schedulerName = getSchedulerName(scheduledJobDetail);
Scheduler scheduler = schedulers.get(schedulerName);
if (scheduler == null || scheduler.isInStandbyMode() || !scheduler.checkExists(jobKey)) {
if (scheduler == null || !scheduler.checkExists(jobKey)) {
JobDetail jobDetail = createJobDetail(scheduledJobDetail);
String tempSchedulerName = "temp" + scheduledJobDetail.getId();
List<Class<? extends JobListener>> listenerClasses = new ArrayList<Class<? extends JobListener>>(2);
@@ -101,7 +108,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
}
} catch (Exception e) {
e.printStackTrace();
throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", "Job execution failed for job with id:"
+ scheduledJobDetail.getId(), scheduledJobDetail.getId());
}
@@ -128,32 +134,48 @@ public class JobRegisterServiceImpl implements JobRegisterService {
@Override
public void pauseScheduler() {
schedulerStatus.put(ThreadLocalContextUtil.getTenant().getId(), false);
for (Map.Entry<String, Scheduler> schedulerEntry : this.schedulers.entrySet()) {
try {
String schedulerName = schedulerEntry.getValue().getSchedulerName();
if (isCurrentTenantsScheduler(schedulerName)) {
schedulerEntry.getValue().standby();
}
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
}
SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
if (!schedulerDetail.isSuspended()) {
schedulerDetail.updateSuspendedState(true);
this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
}
}
@Override
public void startScheduler() {
for (Map.Entry<String, Scheduler> schedulerEntry : this.schedulers.entrySet()) {
try {
String schedulerName = schedulerEntry.getValue().getSchedulerName();
if (isCurrentTenantsScheduler(schedulerName)) {
schedulerEntry.getValue().start();
SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
if (schedulerDetail.isSuspended()) {
schedulerDetail.updateSuspendedState(false);
this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
if (schedulerDetail.isExecuteInstructionForMisfiredJobs()) {
List<ScheduledJobDetail> scheduledJobDetails = this.schedularWritePlatformService.retrieveAllJobs();
for (ScheduledJobDetail jobDetail : scheduledJobDetails) {
if (jobDetail.isTriggerMisfired()) {
if (jobDetail.isActiveSchedular()) {
executeJob(jobDetail, SchedulerServiceConstants.TRIGGER_TYPE_CRON);
}
String schedulerName = getSchedulerName(jobDetail);
Scheduler scheduler = schedulers.get(schedulerName);
if (scheduler != null) {
String key = jobDetail.getJobKey();
JobKey jobKey = constructJobKey(key);
try {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
if (trigger.getNextFireTime() != null && trigger.getNextFireTime().after(jobDetail.getNextRunTime())) {
jobDetail.updateNextRunTime(trigger.getNextFireTime());
}
}
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
}
}
jobDetail.updateTriggerMisfired(false);
this.schedularWritePlatformService.saveOrUpdate(jobDetail);
}
}
} catch (SchedulerException e) {
logger.error(e.getMessage(), e);
}
}
schedulerStatus.put(ThreadLocalContextUtil.getTenant().getId(), true);
}
@Override
@@ -166,23 +188,12 @@ public class JobRegisterServiceImpl implements JobRegisterService {
public void executeJob(final Long jobId) {
ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId);
if (scheduledJobDetail == null) { throw new JobNotFoundException(String.valueOf(jobId)); }
executeJob(scheduledJobDetail);
executeJob(scheduledJobDetail, null);
}
@Override
public boolean isSchedulerRunning() {
return this.schedulerStatus.get(ThreadLocalContextUtil.getTenant().getId());
}
private boolean isCurrentTenantsScheduler(final String schedulerName) {
int beginIndex = SchedulerServiceConstants.SCHEDULER.length();
int endIndex = schedulerName.length();
if (schedulerName.contains(SchedulerServiceConstants.SCHEDULER_GROUP)) {
endIndex = schedulerName.indexOf(SchedulerServiceConstants.SCHEDULER_GROUP);
}
String tenantId = schedulerName.substring(beginIndex, endIndex);
MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
return tenant.getId() == Long.valueOf(tenantId);
return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended();
}
private void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
@@ -195,9 +206,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
JobDetail jobDetail = createJobDetail(scheduledJobDetails);
Trigger trigger = createTrigger(scheduledJobDetails, jobDetail);
Scheduler scheduler = getScheduler(scheduledJobDetails);
if (!this.schedulerStatus.get(ThreadLocalContextUtil.getTenant().getId()) && !scheduler.isInStandbyMode()) {
scheduler.standby();
}
scheduler.scheduleJob(jobDetail, trigger);
scheduledJobDetails.updateJobKey(getJobKeyAsString(jobDetail.getKey()));
scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
@@ -253,6 +261,9 @@ public class JobRegisterServiceImpl implements JobRegisterService {
listenerClasses.add(SchedulerJobListener.class);
}
schedulerFactoryBean.setGlobalJobListeners(getGlobalListener(listenerClasses));
TriggerListener globalTriggerListener = applicationContext.getBean("schedulerTriggerListener", TriggerListener.class);
TriggerListener[] globalTriggerListeners = { globalTriggerListener };
schedulerFactoryBean.setGlobalTriggerListeners(globalTriggerListeners);
Properties quartzProperties = new Properties();
quartzProperties.put(SchedulerFactoryBean.PROP_THREAD_COUNT, Integer.toString(noOfThreads));
schedulerFactoryBean.setQuartzProperties(quartzProperties);
@@ -6,6 +6,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
public interface SchedularWritePlatformService {
@@ -21,6 +22,12 @@ public interface SchedularWritePlatformService {
public ScheduledJobDetail findByJobId(Long jobId);
CommandProcessingResult updateJobDetail(Long jobId, JsonCommand command);
public CommandProcessingResult updateJobDetail(Long jobId, JsonCommand command);
public SchedulerDetail retriveSchedulerDetail();
public void updateSchedulerDetail(final SchedulerDetail schedulerDetail);
public boolean processJobDetailForExecution(String jobKey, String triggerType);
}
@@ -1,5 +1,6 @@
package org.mifosplatform.infrastructure.jobs.service;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -11,6 +12,8 @@ import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetailRepository;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistoryRepository;
import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail;
import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetailRepository;
import org.mifosplatform.infrastructure.jobs.exception.JobNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -23,13 +26,17 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
private final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository;
private final SchedulerDetailRepository schedulerDetailRepository;
private final JobDetailDataValidator dataValidator;
@Autowired
public SchedularWritePlatformServiceJpaRepositoryImpl(final ScheduledJobDetailRepository scheduledJobDetailsRepository,
final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository, final JobDetailDataValidator dataValidator) {
final ScheduledJobRunHistoryRepository scheduledJobRunHistoryRepository, final JobDetailDataValidator dataValidator,
final SchedulerDetailRepository schedulerDetailRepository) {
this.scheduledJobDetailsRepository = scheduledJobDetailsRepository;
this.scheduledJobRunHistoryRepository = scheduledJobRunHistoryRepository;
this.schedulerDetailRepository = schedulerDetailRepository;
this.dataValidator = dataValidator;
}
@@ -71,6 +78,22 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
return this.scheduledJobDetailsRepository.findByJobId(jobId);
}
@Override
@Transactional
public void updateSchedulerDetail(final SchedulerDetail schedulerDetail) {
this.schedulerDetailRepository.save(schedulerDetail);
}
@Override
public SchedulerDetail retriveSchedulerDetail() {
SchedulerDetail schedulerDetail = null;
List<SchedulerDetail> schedulerDetailList = schedulerDetailRepository.findAll();
if (schedulerDetailList != null) {
schedulerDetail = schedulerDetailList.get(0);
}
return schedulerDetail;
}
@Transactional
@Override
public CommandProcessingResult updateJobDetail(final Long jobId, final JsonCommand command) {
@@ -89,4 +112,24 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
}
@Transactional
@Override
public boolean processJobDetailForExecution(String jobKey, String triggerType) {
boolean isStopExecution = false;
final ScheduledJobDetail scheduledJobDetail = scheduledJobDetailsRepository.findByJobKeyWithLock(jobKey);
if (scheduledJobDetail.isCurrentlyRunning()
|| (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && (scheduledJobDetail.getNextRunTime().after(new Date())))) {
isStopExecution = true;
}
final SchedulerDetail schedulerDetail = retriveSchedulerDetail();
if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && schedulerDetail.isSuspended()) {
scheduledJobDetail.updateTriggerMisfired(true);
isStopExecution = true;
} else if (!isStopExecution) {
scheduledJobDetail.updateCurrentlyRunningStatus(true);
}
scheduledJobDetailsRepository.save(scheduledJobDetail);
return isStopExecution;
}
}
@@ -2,12 +2,9 @@ package org.mifosplatform.infrastructure.jobs.service;
import java.util.Date;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail;
import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobRunHistory;
import org.mifosplatform.infrastructure.jobs.exception.JobInProcessExecution;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
@@ -31,12 +28,9 @@ public class SchedulerJobListener implements JobListener {
private final SchedularWritePlatformService schedularService;
private final TenantDetailsService tenantDetailsService;
@Autowired
public SchedulerJobListener(final SchedularWritePlatformService schedularService, final TenantDetailsService tenantDetailsService) {
public SchedulerJobListener(final SchedularWritePlatformService schedularService) {
this.schedularService = schedularService;
this.tenantDetailsService = tenantDetailsService;
}
@Override
@@ -45,16 +39,7 @@ public class SchedulerJobListener implements JobListener {
}
@Override
public void jobToBeExecuted(final JobExecutionContext context) {
String tenantIdentifier = context.getTrigger().getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
ThreadLocalContextUtil.setTenant(tenant);
JobKey key = context.getTrigger().getJobKey();
String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
final ScheduledJobDetail scheduledJobDetail = schedularService.findByJobKey(jobKey);
if (scheduledJobDetail.isCurrentlyRunning()) { throw new JobInProcessExecution(scheduledJobDetail.getJobName()); }
scheduledJobDetail.updateCurrentlyRunningStatus(true);
this.schedularService.saveOrUpdate(scheduledJobDetail);
public void jobToBeExecuted(@SuppressWarnings("unused") final JobExecutionContext context) {
}
@@ -93,7 +78,8 @@ public class SchedulerJobListener implements JobListener {
if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
}
if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON) {
if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && trigger.getNextFireTime() != null
&& trigger.getNextFireTime().after(scheduledJobDetails.getNextRunTime())) {
scheduledJobDetails.updateNextRunTime(trigger.getNextFireTime());
}
@@ -0,0 +1,66 @@
package org.mifosplatform.infrastructure.jobs.service;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SchedulerTriggerListener implements TriggerListener {
private final String name = "Global trigger Listner";
private final SchedularWritePlatformService schedularService;
private final TenantDetailsService tenantDetailsService;
@Autowired
public SchedulerTriggerListener(final SchedularWritePlatformService schedularService, final TenantDetailsService tenantDetailsService) {
this.schedularService = schedularService;
this.tenantDetailsService = tenantDetailsService;
}
@Override
public String getName() {
return this.name;
}
@Override
public void triggerFired(@SuppressWarnings("unused") Trigger trigger, @SuppressWarnings("unused") JobExecutionContext context) {
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
ThreadLocalContextUtil.setTenant(tenant);
JobKey key = trigger.getJobKey();
String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup();
String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON;
if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) {
triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE);
}
return schedularService.processJobDetailForExecution(jobKey, triggerType);
}
@Override
public void triggerMisfired(@SuppressWarnings("unused") Trigger trigger) {
}
@Override
public void triggerComplete(@SuppressWarnings("unused") Trigger trigger, @SuppressWarnings("unused") JobExecutionContext context,
@SuppressWarnings("unused") CompletedExecutionInstruction triggerInstructionCode) {
}
}
@@ -30,7 +30,6 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
public class Holiday extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@Column(name = "name", unique = true, nullable = false, length = 100)
private String name;
@@ -49,7 +48,6 @@ public class Holiday extends AbstractPersistable<Long> {
@Column(name = "processed", nullable = false)
private boolean processed;
@SuppressWarnings("unused")
@Column(name = "description", length = 100)
private String description;
@@ -13,6 +13,7 @@ public class CurrencyData {
private final String code;
private final String name;
private final int decimalPlaces;
private final Integer inMultiplesOf;
private final String displaySymbol;
@SuppressWarnings("unused")
private final String nameCode;
@@ -20,13 +21,14 @@ public class CurrencyData {
private final String displayLabel;
public static CurrencyData blank() {
return new CurrencyData("", "", 0, "", "");
return new CurrencyData("", "", 0,0, "", "");
}
public CurrencyData(final String code, final String name, final int decimalPlaces, final String displaySymbol, final String nameCode) {
public CurrencyData(final String code, final String name, final int decimalPlaces,final Integer inMultiplesOf, final String displaySymbol, final String nameCode) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.displaySymbol = displaySymbol;
this.nameCode = nameCode;
this.displayLabel = generateDisplayLabel();
@@ -40,6 +42,10 @@ public class CurrencyData {
return this.decimalPlaces;
}
public Integer currencyInMultiplesOf() {
return this.inMultiplesOf;
}
@Override
public boolean equals(Object obj) {
CurrencyData currencyData = (CurrencyData) obj;
@@ -23,6 +23,9 @@ public class ApplicationCurrency extends AbstractPersistable<Long> {
@Column(name = "decimal_places", nullable = false)
private final Integer decimalPlaces;
@Column(name = "currency_multiplesof")
private final Integer inMultiplesOf;
@Column(name = "name", nullable = false, length = 50)
private final String name;
@@ -36,19 +39,22 @@ public class ApplicationCurrency extends AbstractPersistable<Long> {
this.code = null;
this.name = null;
this.decimalPlaces = null;
this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces) {
return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, currency.nameCode, currency.displaySymbol);
public static ApplicationCurrency from(final ApplicationCurrency currency, final int decimalPlaces, final Integer inMultiplesOf) {
return new ApplicationCurrency(currency.code, currency.name, decimalPlaces, inMultiplesOf, currency.nameCode,
currency.displaySymbol);
}
private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final String nameCode,
final String displaySymbol) {
private ApplicationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
@@ -65,6 +71,10 @@ public class ApplicationCurrency extends AbstractPersistable<Long> {
return this.decimalPlaces;
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
public String getNameCode() {
return nameCode;
}
@@ -74,14 +84,14 @@ public class ApplicationCurrency extends AbstractPersistable<Long> {
}
public CurrencyData toData() {
return new CurrencyData(this.code, this.name, this.decimalPlaces, this.displaySymbol, this.nameCode);
return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode);
}
public CurrencyData toData(final int digitsAfterDecimalSupported) {
return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, this.displaySymbol, this.nameCode);
public CurrencyData toData(final int digitsAfterDecimalSupported, final Integer inMultiplesOf) {
return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, inMultiplesOf, this.displaySymbol, this.nameCode);
}
public OrganisationCurrency toOrganisationCurrency() {
return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.nameCode, this.displaySymbol);
return new OrganisationCurrency(this.code, this.name, this.decimalPlaces,this.inMultiplesOf, this.nameCode, this.displaySymbol);
}
}
@@ -38,7 +38,7 @@ public class ApplicationCurrencyRepositoryWrapper {
if (defaultApplicationCurrency == null) { throw new CurrencyNotFoundException(currency.getCode()); }
final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency,
currency.getDigitsAfterDecimal());
currency.getDigitsAfterDecimal(),currency.getCurrencyInMultiplesOf());
return applicationCurrency;
}
@@ -17,18 +17,23 @@ public class MonetaryCurrency {
@Column(name = "currency_digits", nullable = false)
private final int digitsAfterDecimal;
@Column(name = "currency_multiplesof")
private final Integer inMultiplesOf;
protected MonetaryCurrency() {
this.code = null;
this.digitsAfterDecimal = 0;
this.inMultiplesOf = 0;
}
public MonetaryCurrency(final String code, final int digitsAfterDecimal) {
public MonetaryCurrency(final String code, final int digitsAfterDecimal,final Integer inMultiplesOf) {
this.code = code;
this.digitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;
}
public MonetaryCurrency copy() {
return new MonetaryCurrency(this.code, this.digitsAfterDecimal);
return new MonetaryCurrency(this.code, this.digitsAfterDecimal,this.inMultiplesOf);
}
public String getCode() {
@@ -38,4 +43,8 @@ public class MonetaryCurrency {
public int getDigitsAfterDecimal() {
return digitsAfterDecimal;
}
public Integer getCurrencyInMultiplesOf() {
return inMultiplesOf;
}
}
@@ -21,6 +21,9 @@ public class Money implements Comparable<Money> {
@Column(name = "currency_digits")
private final int currencyDigitsAfterDecimal;
@Column(name = "currency_multiplesof")
private final Integer inMultiplesOf;
@Column(name = "amount", scale = 6, precision = 19)
private final BigDecimal amount;
@@ -44,46 +47,47 @@ public class Money implements Comparable<Money> {
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount));
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
currency.getCurrencyInMultiplesOf());
}
public static Money zero(final MonetaryCurrency currency) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO);
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf());
}
protected Money() {
this.currencyCode = null;
this.currencyDigitsAfterDecimal = 0;
this.inMultiplesOf = 0;
this.amount = null;
}
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount) {
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) {
this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;
final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
final BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
final BigDecimal amountScaled = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
this.amount = amountScaled;
// TODO - KW - below was test of using floor/ceil functions to support
// round monetary amounts into multiples of say 20/50.
// if (this.currencyDigitsAfterDecimal == 0) {
// double existingVal = amountScaled.doubleValue();
// double ceilingOfValue = ceiling(existingVal, Double.valueOf("20.0"));
// double floorOfValue = floor(existingVal, Double.valueOf("20.0"));
//
// double floorDiff = existingVal - floorOfValue;
// double ceilDiff = ceilingOfValue - existingVal;
//
// if (ceilDiff > floorDiff) {
// this.amount = BigDecimal.valueOf(floorOfValue);
// } else {
// this.amount = BigDecimal.valueOf(ceilingOfValue);
// }
// } else {
// this.amount = amountScaled;
// }
// round monetary amounts into multiplesof say 20/50.
if (inMultiplesOf!= null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf>0 && amountScaled.doubleValue()>0) {
double existingVal = amountScaled.doubleValue();
double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
double floorOfValue = floor(existingVal, inMultiplesOf);
double floorDiff = existingVal - floorOfValue;
double ceilDiff = ceilingOfValue - existingVal;
if (ceilDiff > floorDiff) {
this.amount = BigDecimal.valueOf(floorOfValue);
} else {
this.amount = BigDecimal.valueOf(ceilingOfValue);
}
} else {
this.amount = amountScaled;
}
}
public static double ceiling(final double n, final double s) {
@@ -119,7 +123,7 @@ public class Money implements Comparable<Money> {
}
public Money copy() {
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros());
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf);
}
public Money plus(final Iterable<? extends Money> moniesToAdd) {
@@ -263,6 +267,10 @@ public class Money implements Comparable<Money> {
return this.currencyDigitsAfterDecimal;
}
public int getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
public BigDecimal getAmount() {
return this.amount;
}
@@ -298,7 +306,7 @@ public class Money implements Comparable<Money> {
}
private MonetaryCurrency monetaryCurrency() {
return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal);
return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal, this.inMultiplesOf);
}
public Money zero() {
@@ -58,14 +58,15 @@ public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformServ
final String code = rs.getString("code");
final String name = rs.getString("name");
final int decimalPlaces = JdbcSupport.getInteger(rs, "decimalPlaces");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final String displaySymbol = rs.getString("displaySymbol");
final String nameCode = rs.getString("nameCode");
return new CurrencyData(code, name, decimalPlaces, displaySymbol, nameCode);
return new CurrencyData(code, name, decimalPlaces, inMultiplesOf, displaySymbol, nameCode);
}
public String schema() {
return " c.code as code, c.name as name, c.decimal_places as decimalPlaces, c.display_symbol as displaySymbol, c.internationalized_name_code as nameCode ";
return " c.code as code, c.name as name, c.decimal_places as decimalPlaces,c.currency_multiplesof as inMultiplesOf, c.display_symbol as displaySymbol, c.internationalized_name_code as nameCode ";
}
}
}
@@ -41,7 +41,7 @@ import org.springframework.stereotype.Component;
public class OfficeTransactionsApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "transactionDate", "fromOfficeId",
"fromOfficeName", "toOfficeId", "toOfficeIdName", "currencyCode", "digitsAfterDecimal", "transactionAmount", "description",
"fromOfficeName", "toOfficeId", "toOfficeIdName", "currencyCode", "digitsAfterDecimal","inMultiplesOf", "transactionAmount", "description",
"allowedOffices", "currencyOptions"));
private final String resourceNameForReadPermissions = "OFFICE";
@@ -0,0 +1,45 @@
/**
* 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.organisation.office.domain;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link OfficeRepository} that adds NULL checking and Error
* handling capabilities
* </p>
*/
@Service
public class OfficeRepositoryWrapper {
private final OfficeRepository repository;
@Autowired
public OfficeRepositoryWrapper(final OfficeRepository repository) {
this.repository = repository;
}
public Office findOneWithNotFoundDetection(final Long id) {
final Office office = this.repository.findOne(id);
if (office == null) { throw new OfficeNotFoundException(id); }
return office;
}
public void save(final Office entity) {
this.repository.save(entity);
}
public void saveAndFlush(final Office entity) {
this.repository.saveAndFlush(entity);
}
public void delete(final Office entity) {
this.repository.delete(entity);
}
}
@@ -28,30 +28,24 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_office_transaction")
public class OfficeTransaction extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_office_id")
private Office from;
@SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_office_id")
private Office to;
@SuppressWarnings("unused")
@Column(name = "transaction_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date transactionDate;
@SuppressWarnings("unused")
@Embedded
private MonetaryCurrency currency;
@SuppressWarnings("unused")
@Column(name = "transaction_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal transactionAmount;
@SuppressWarnings("unused")
@Column(name = "description", nullable = true, length = 100)
private String description;
@@ -18,23 +18,21 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_organisation_currency")
public class OrganisationCurrency extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@Column(name = "code", nullable = false, length = 3)
private final String code;
@SuppressWarnings("unused")
@Column(name = "decimal_places", nullable = false)
private final Integer decimalPlaces;
@SuppressWarnings("unused")
@Column(name = "currency_multiplesof")
private final Integer inMultiplesOf;
@Column(name = "name", nullable = false, length = 50)
private final String name;
@SuppressWarnings("unused")
@Column(name = "internationalized_name_code", nullable = false, length = 50)
private final String nameCode;
@SuppressWarnings("unused")
@Column(name = "display_symbol", nullable = true, length = 10)
private final String displaySymbol;
@@ -42,15 +40,17 @@ public class OrganisationCurrency extends AbstractPersistable<Long> {
this.code = null;
this.name = null;
this.decimalPlaces = null;
this.inMultiplesOf = null;
this.nameCode = null;
this.displaySymbol = null;
}
public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final String nameCode,
final String displaySymbol) {
public OrganisationCurrency(final String code, final String name, final int decimalPlaces, final Integer inMultiplesOf,
final String nameCode, final String displaySymbol) {
this.code = code;
this.name = name;
this.decimalPlaces = decimalPlaces;
this.inMultiplesOf = inMultiplesOf;
this.nameCode = nameCode;
this.displaySymbol = displaySymbol;
}
@@ -90,7 +90,7 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
public String schema() {
return " ot.id as id, ot.transaction_date as transactionDate, ot.from_office_id as fromOfficeId, fromoff.name as fromOfficeName, "
+ " ot.to_office_id as toOfficeId, tooff.name as toOfficeName, ot.transaction_amount as transactionAmount, ot.description as description, "
+ " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, "
+ " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, rc.currency_multiplesof as inMultiplesOf, "
+ " rc.name as currencyName, rc.internationalized_name_code as currencyNameCode, rc.display_symbol as currencyDisplaySymbol "
+ " from m_office_transaction ot "
+ " left join m_office fromoff on fromoff.id = ot.from_office_id "
@@ -112,9 +112,10 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode);
CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount");
String description = rs.getString("description");
@@ -159,7 +159,7 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP
final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
final ApplicationCurrency appCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
final MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces());
final MonetaryCurrency currency = new MonetaryCurrency(appCurrency.getCode(), appCurrency.getDecimalPlaces(),appCurrency.getCurrencyInMultiplesOf());
final Money amount = Money.of(currency, command.bigDecimalValueOfParameterNamed("transactionAmount"));
final OfficeTransaction entity = OfficeTransaction.fromJson(fromOffice, toOffice, amount, command);
@@ -48,7 +48,7 @@ public class StaffApiResource {
* {@link StaffData}.
*/
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "firstname", "lastname", "displayName",
"officeId", "officeName", "loanOfficerFlag", "allowedOffices"));
"officeId", "officeName", "loanOfficerFlag", "externalId", "allowedOffices"));
private final String resourceNameForPermissions = "STAFF";
@@ -8,8 +8,7 @@ package org.mifosplatform.organisation.staff.data;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.portfolio.client.data.ClientLoanAccountSummaryData;
import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData;
import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData;
/**
*
@@ -17,23 +16,23 @@ import org.mifosplatform.portfolio.group.data.GroupAccountSummaryData;
public class StaffAccountSummaryCollectionData {
@SuppressWarnings("unused")
private final List<ClientSummary> clients;
private final List<LoanAccountSummary> clients;
@SuppressWarnings("unused")
private final List<GroupSummary> groups;
private final List<LoanAccountSummary> groups;
public StaffAccountSummaryCollectionData(final List<ClientSummary> clients, final List<GroupSummary> groups) {
public StaffAccountSummaryCollectionData(final List<LoanAccountSummary> clients, final List<LoanAccountSummary> groups) {
this.clients = clients;
this.groups = groups;
}
public static final class ClientSummary {
public static final class LoanAccountSummary {
private final Long id;
private final String displayName;
private Collection<ClientLoanAccountSummaryData> loans;
private Collection<LoanAccountSummaryData> loans;
public ClientSummary(Long id, String displayName) {
public LoanAccountSummary(Long id, String displayName) {
this.id = id;
this.displayName = displayName;
}
@@ -46,41 +45,13 @@ public class StaffAccountSummaryCollectionData {
return displayName;
}
public Collection<ClientLoanAccountSummaryData> getLoans() {
public Collection<LoanAccountSummaryData> getLoans() {
return loans;
}
public void setLoans(Collection<ClientLoanAccountSummaryData> loans) {
public void setLoans(Collection<LoanAccountSummaryData> loans) {
this.loans = loans;
}
}
public static final class GroupSummary {
private final Long id;
private final String name;
private Collection<GroupAccountSummaryData> loans;
public GroupSummary(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Collection<GroupAccountSummaryData> getLoans() {
return loans;
}
public void setLoans(Collection<GroupAccountSummaryData> loans) {
this.loans = loans;
}
}
}
@@ -21,32 +21,34 @@ public class StaffData {
private final Long officeId;
private final String officeName;
private final Boolean isLoanOfficer;
private final String externalId;
@SuppressWarnings("unused")
private final Collection<OfficeData> allowedOffices;
public static StaffData templateData(final StaffData staff, final Collection<OfficeData> allowedOffices) {
return new StaffData(staff.id, staff.firstname, staff.lastname, staff.displayName, staff.officeId, staff.officeName,
staff.isLoanOfficer, allowedOffices);
staff.isLoanOfficer, staff.externalId, allowedOffices);
}
public static StaffData lookup(final Long id, final String displayName) {
return new StaffData(id, null, null, displayName, null, null, null, null);
return new StaffData(id, null, null, displayName, null, null, null, null, null);
}
public static StaffData instance(final Long id, final String firstname, final String lastname, final String displayName,
final Long officeId, final String officeName, final Boolean isLoanOfficer) {
return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, null);
final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId) {
return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, null);
}
private StaffData(final Long id, final String firstname, final String lastname, final String displayName, final Long officeId,
final String officeName, final Boolean isLoanOfficer, final Collection<OfficeData> allowedOffices) {
final String officeName, final Boolean isLoanOfficer, final String externalId, final Collection<OfficeData> allowedOffices) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.displayName = displayName;
this.officeName = officeName;
this.isLoanOfficer = isLoanOfficer;
this.externalId = externalId;
this.officeId = officeId;
this.allowedOffices = allowedOffices;
}
@@ -22,7 +22,8 @@ import org.mifosplatform.organisation.office.domain.Office;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name") })
@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name") ,
@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
public class Staff extends AbstractPersistable<Long> {
@Column(name = "firstname", length = 50)
@@ -34,6 +35,9 @@ public class Staff extends AbstractPersistable<Long> {
@Column(name = "display_name", length = 100)
private String displayName;
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
// Office to which this employee belongs
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
@@ -46,7 +50,6 @@ public class Staff extends AbstractPersistable<Long> {
@Column(name = "organisational_role_enum", nullable = true)
private Integer organisationalRoleType;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "organisational_role_parent_staff_id", nullable = true)
private Staff organisationalRoleParentStaff;
@@ -54,29 +57,33 @@ public class Staff extends AbstractPersistable<Long> {
public static Staff fromJson(final Office staffOffice, final JsonCommand command) {
final String firstnameParamName = "firstname";
String firstname = command.stringValueOfParameterNamed(firstnameParamName);
final String firstname = command.stringValueOfParameterNamed(firstnameParamName);
final String lastnameParamName = "lastname";
String lastname = command.stringValueOfParameterNamed(lastnameParamName);
final String lastname = command.stringValueOfParameterNamed(lastnameParamName);
final String externalIdParamName = "externalId";
final String externalId = command.stringValueOfParameterNamedAllowingNull(externalIdParamName);
final String isLoanOfficerParamName = "isLoanOfficer";
final boolean isLoanOfficer = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
return new Staff(staffOffice, firstname, lastname, isLoanOfficer);
return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
}
public static Staff createNew(final Office staffOffice, final String firstname, final String lastname, final boolean isLoanOfficer) {
return new Staff(staffOffice, firstname, lastname, isLoanOfficer);
public static Staff createNew(final Office staffOffice, final String firstname, final String lastname, final String externalId, final boolean isLoanOfficer) {
return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
}
protected Staff() {
//
}
private Staff(final Office staffOffice, final String firstname, final String lastname, final boolean isLoanOfficer) {
private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId, final boolean isLoanOfficer) {
this.office = staffOffice;
this.firstname = StringUtils.defaultIfEmpty(firstname, null);
this.lastname = StringUtils.defaultIfEmpty(lastname, null);
this.externalId = externalId;
this.loanOfficer = isLoanOfficer;
deriveDisplayName(firstname);
}
@@ -125,6 +132,13 @@ public class Staff extends AbstractPersistable<Long> {
deriveDisplayName(this.firstname);
}
final String externalIdParamName = "externalId";
if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) {
final String newValue = command.stringValueOfParameterNamed(externalIdParamName);
actualChanges.put(externalIdParamName, newValue);
this.externalId = newValue;
}
final String isLoanOfficerParamName = "isLoanOfficer";
if (command.isChangeInBooleanParameterNamed(isLoanOfficerParamName, this.loanOfficer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
@@ -31,7 +31,7 @@ public final class StaffCommandFromApiJsonDeserializer {
/**
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("firstname", "lastname", "officeId", "isLoanOfficer"));
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("firstname", "lastname", "officeId", "externalId", "isLoanOfficer"));
private final FromJsonHelper fromApiJsonHelper;
@@ -38,7 +38,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
public String schema() {
return " s.id as id,s.office_id as officeId, o.name as officeName, s.firstname as firstname, s.lastname as lastname,"
+ " s.display_name as displayName, s.is_loan_officer as isLoanOfficer from m_staff s "
+ " s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId from m_staff s "
+ " join m_office o on o.id = s.office_id";
}
@@ -52,8 +52,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final Long officeId = rs.getLong("officeId");
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
final String officeName = rs.getString("officeName");
final String externalId = rs.getString("externalId");
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer);
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
}
}
@@ -64,7 +65,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("s.id as id, s.office_id as officeId, o.name as officeName,");
sqlBuilder.append("s.firstname as firstname, s.lastname as lastname,");
sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer ");
sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId ");
sqlBuilder.append("from m_office o ");
sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') ");
sqlBuilder.append("join m_staff s on s.office_id = ohierarchy.id ");
@@ -88,8 +89,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final Long officeId = rs.getLong("officeId");
final String officeName = rs.getString("officeName");
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
final String externalId = rs.getString("externalId");
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer);
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
}
}
@@ -98,7 +98,7 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
}
if (!changesOnly.isEmpty()) {
this.staffRepository.save(staffForUpdate);
this.staffRepository.saveAndFlush(staffForUpdate);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(staffId)
@@ -115,7 +115,14 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
*/
private void handleStaffDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("display_name")) {
if (realCause.getMessage().contains("external_id")) {
final String externalId = command.stringValueOfParameterNamed("externalId");
throw new PlatformDataIntegrityException("error.msg.staff.duplicate.externalId", "Staff with externalId `" + externalId
+ "` already exists", "externalId", externalId);
}
else if (realCause.getMessage().contains("display_name")) {
final String lastname = command.stringValueOfParameterNamed("lastname");
String displayName = lastname;
if (!StringUtils.isBlank(displayName)) {
@@ -29,10 +29,8 @@ public class WorkingDaysRepositoryWrapper {
public WorkingDays findOne() {
final List<WorkingDays> workingDaysList = this.repository.findAll();
if (workingDaysList == null || workingDaysList.isEmpty()) {
throw new WorkingDaysNotFoundException();
}
if (workingDaysList == null || workingDaysList.isEmpty()) { throw new WorkingDaysNotFoundException(); }
return workingDaysList.get(0);
}
@@ -9,7 +9,7 @@ package org.mifosplatform.organisation.workingdays.service;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
public class WorkingDaysUtil {
@@ -38,6 +38,6 @@ public class WorkingDaysUtil {
}
public static boolean isWorkingDay(WorkingDays workingDays, final LocalDate date){
return CalendarHelper.isValidRedurringDate(workingDays.getRecurrence(), date, date);
return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
}
}
@@ -0,0 +1,50 @@
/**
* 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.account;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
public class AccountTransfersApiConstants {
public static final String ACCOUNT_TRANSFER_RESOURCE_NAME = "accounttransfer";
// general
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
// savings product and account parameters
public static final String idParamName = "id";
public static final String fromOfficeIdParamName = "fromOfficeId";
public static final String fromClientIdParamName = "fromClientId";
public static final String fromAccountIdParamName = "fromAccountId";
public static final String fromAccountTypeParamName = "fromAccountType";
public static final String toOfficeIdParamName = "toOfficeId";
public static final String toClientIdParamName = "toClientId";
public static final String toAccountIdParamName = "toAccountId";
public static final String toAccountTypeParamName = "toAccountType";
// transaction parameters
public static final String transferDateParamName = "transferDate";
public static final String transferAmountParamName = "transferAmount";
public static final String transferDescriptionParamName = "transferDescription";
public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, dateFormatParamName,
fromOfficeIdParamName, fromClientIdParamName, fromAccountTypeParamName, fromAccountIdParamName, toOfficeIdParamName,
toClientIdParamName, toAccountTypeParamName, toAccountIdParamName, transferDateParamName, transferAmountParamName,
transferDescriptionParamName));
/**
* These parameters will match the class level parameters of
* {@link AccountTransferData}. Where possible, we try to get response
* parameters to match those of request parameters.
*/
public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, transferDescriptionParamName,
"currency"));
}
@@ -0,0 +1,67 @@
/**
* 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.account;
import java.util.ArrayList;
import java.util.List;
public enum PortfolioAccountType {
INVALID(0, "accountType.invalid"), //
LOAN(1, "accountType.loan"), //
SAVINGS(2, "accountType.savings");
private final Integer value;
private final String code;
private PortfolioAccountType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static Object[] integerValues() {
final List<Integer> values = new ArrayList<Integer>();
for (final PortfolioAccountType enumType : values()) {
if (enumType.getValue() > 0) {
values.add(enumType.getValue());
}
}
return values.toArray();
}
public static PortfolioAccountType fromInt(final Integer type) {
PortfolioAccountType enumType = PortfolioAccountType.INVALID;
if (type != null) {
switch (type) {
case 1:
enumType = LOAN;
break;
case 2:
enumType = SAVINGS;
break;
}
}
return enumType;
}
public boolean isSavingsAccount() {
return this.value == Integer.valueOf(2);
}
public boolean isLoanAccount() {
return this.value == Integer.valueOf(1);
}
}
@@ -0,0 +1,122 @@
/**
* 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.account.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.account.AccountTransfersApiConstants;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/accounttransfers")
@Component
@Scope("singleton")
public class AccountTransfersApiResource {
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
@Autowired
public AccountTransfersApiResource(final PlatformSecurityContext context,
final DefaultToApiJsonSerializer<AccountTransferData> toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String template(@QueryParam("fromOfficeId") final Long fromOfficeId, @QueryParam("fromClientId") final Long fromClientId,
@QueryParam("fromAccountId") final Long fromAccountId, @QueryParam("fromAccountType") final Integer fromAccountType,
@QueryParam("toOfficeId") final Long toOfficeId, @QueryParam("toClientId") final Long toClientId,
@QueryParam("toAccountId") final Long toAccountId, @QueryParam("toAccountType") final Integer toAccountType,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
AccountTransferData transferData = this.accountTransfersReadPlatformService.retrieveTemplate(fromOfficeId, fromClientId,
fromAccountId, fromAccountType, toOfficeId, toClientId, toAccountId, toAccountType);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transferData, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String create(final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createAccountTransfer().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sqlSearch") final String sqlSearch,
@QueryParam("externalId") final String externalId, @QueryParam("offset") final Integer offset,
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
@QueryParam("sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forLoans(sqlSearch, externalId, offset, limit, orderBy, sortOrder);
final Page<AccountTransferData> transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfers, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("{transferId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("transferId") final Long transferId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
AccountTransferData transfer = this.accountTransfersReadPlatformService.retrieveOne(transferId);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transfer, AccountTransfersApiConstants.RESPONSE_DATA_PARAMETERS);
}
}
@@ -0,0 +1,122 @@
/**
* 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.account.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;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.portfolio.client.data.ClientData;
/**
* Immutable data object representing a savings account.
*/
@SuppressWarnings("unused")
public class AccountTransferData {
private final Long id;
private final Boolean reversed;
private final CurrencyData currency;
private final BigDecimal transferAmount;
private final LocalDate transferDate;
private final String transferDescription;
private final OfficeData fromOffice;
private final ClientData fromClient;
private final EnumOptionData fromAccountType;
private final PortfolioAccountData fromAccount;
private final OfficeData toOffice;
private final ClientData toClient;
private final EnumOptionData toAccountType;
private final PortfolioAccountData toAccount;
// template
private final Collection<OfficeData> fromOfficeOptions;
private final Collection<ClientData> fromClientOptions;
private final Collection<EnumOptionData> fromAccountTypeOptions;
private final Collection<PortfolioAccountData> fromAccountOptions;
private final Collection<OfficeData> toOfficeOptions;
private final Collection<ClientData> toClientOptions;
private final Collection<EnumOptionData> toAccountTypeOptions;
private final Collection<PortfolioAccountData> toAccountOptions;
public static AccountTransferData template(final OfficeData fromOffice, final ClientData fromClient,
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final LocalDate transferDate,
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
final Long id = null;
CurrencyData currency = null;
if (fromAccount != null) {
currency = fromAccount.currency();
}
final BigDecimal transferAmount = BigDecimal.ZERO;
final String transferDescription = null;
final Boolean reversed = null;
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, fromOfficeOptions, fromClientOptions,
fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
}
public static AccountTransferData instance(final Long id, final Boolean reversed, final LocalDate transferDate,
final CurrencyData currency, final BigDecimal transferAmount, final String transferDescription, final OfficeData fromOffice,
final OfficeData toOffice, final ClientData fromClient, final ClientData toClient, final EnumOptionData fromAccountType,
final PortfolioAccountData fromAccount, final EnumOptionData toAccountType, final PortfolioAccountData toAccount) {
return new AccountTransferData(id, reversed, fromOffice, fromClient, fromAccountType, fromAccount, currency, transferAmount,
transferDate, transferDescription, toOffice, toClient, toAccountType, toAccount, null, null, null, null, null, null,
null, null);
}
public static AccountTransferData transferBasicDetails(final Long id, final CurrencyData currency, final BigDecimal transferAmount,
final LocalDate transferDate, final String description, final Boolean reversed) {
final EnumOptionData fromAccountType = null;
final EnumOptionData toAccountType = null;
return new AccountTransferData(id, reversed, null, null, fromAccountType, null, currency, transferAmount, transferDate,
description, null, null, toAccountType, null, null, null, null, null, null, null, null, null);
}
private AccountTransferData(final Long id, final Boolean reversed, final OfficeData fromOffice, final ClientData fromClient,
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final CurrencyData currency,
final BigDecimal transferAmount, final LocalDate transferDate, final String transferDescription, final OfficeData toOffice,
final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions) {
this.id = id;
this.reversed = reversed;
this.fromOffice = fromOffice;
this.fromClient = fromClient;
this.fromAccountType = fromAccountType;
this.fromAccount = fromAccount;
this.toOffice = toOffice;
this.toClient = toClient;
this.toAccountType = toAccountType;
this.toAccount = toAccount;
this.currency = currency;
this.transferAmount = transferAmount;
this.transferDate = transferDate;
this.transferDescription = transferDescription;
this.fromOfficeOptions = fromOfficeOptions;
this.fromClientOptions = fromClientOptions;
this.fromAccountTypeOptions = fromAccountTypeOptions;
this.fromAccountOptions = fromAccountOptions;
this.toOfficeOptions = toOfficeOptions;
this.toClientOptions = toClientOptions;
this.toAccountTypeOptions = toAccountTypeOptions;
this.toAccountOptions = toAccountOptions;
}
}
@@ -0,0 +1,108 @@
/**
* 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.account.data;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromOfficeIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toOfficeIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDateParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDescriptionParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public class AccountTransfersDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public AccountTransfersDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validate(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, REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(ACCOUNT_TRANSFER_RESOURCE_NAME);
final JsonElement element = command.parsedJson();
final Long fromOfficeId = fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
final Long fromClientId = fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
final Long fromAccountId = fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
final Integer fromAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType)
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
final Long toOfficeId = fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
final Long toClientId = fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
final Long toAccountId = fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
final Integer toAccountType = fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType)
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element);
baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull();
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element);
baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount();
final String transactionDescription = fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element);
baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank().notExceedingLengthOf(200);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -0,0 +1,78 @@
/**
* 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.account.data;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
/**
* Immutable data object representing a savings account.
*/
@SuppressWarnings("unused")
public class PortfolioAccountData {
private final Long id;
private final String accountNo;
private final String externalId;
private final Long groupId;
private final String groupName;
private final Long clientId;
private final String clientName;
private final Long productId;
private final String productName;
private final Long fieldOfficerId;
private final String fieldOfficerName;
private final CurrencyData currency;
public static PortfolioAccountData lookup(final Long accountId, final String accountNo) {
return new PortfolioAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null);
}
public PortfolioAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldofficerId,
final String fieldofficerName, final CurrencyData currency) {
this.id = id;
this.accountNo = accountNo;
this.externalId = externalId;
this.groupId = groupId;
this.groupName = groupName;
this.clientId = clientId;
this.clientName = clientName;
this.productId = productId;
this.productName = productName;
this.fieldOfficerId = fieldofficerId;
this.fieldOfficerName = fieldofficerName;
this.currency = currency;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) { return false; }
final PortfolioAccountData rhs = (PortfolioAccountData) obj;
return new EqualsBuilder().append(this.id, rhs.id).append(this.accountNo, rhs.accountNo).append(this.productId, rhs.productId).append(this.productName, rhs.productName).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(this.id).append(this.accountNo).append(this.productId).append(this.productName).toHashCode();
}
public Long clientId() {
return this.clientId;
}
public CurrencyData currency() {
return this.currency;
}
public String currencyCode() {
return this.currency.code();
}
}
@@ -0,0 +1,132 @@
/**
* 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.account.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_savings_account_transfer")
public class AccountTransfer extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "from_office_id", nullable = false)
private Office fromOffice;
@ManyToOne
@JoinColumn(name = "from_client_id", nullable = false)
private Client fromClient;
@ManyToOne
@JoinColumn(name = "from_savings_account_id", nullable = true)
private SavingsAccount fromAccount;
@ManyToOne
@JoinColumn(name = "to_office_id", nullable = false)
private Office toOffice;
@ManyToOne
@JoinColumn(name = "to_client_id", nullable = false)
private Client toClient;
@ManyToOne
@JoinColumn(name = "to_savings_account_id", nullable = true)
private SavingsAccount toSavingsAccount;
@ManyToOne
@JoinColumn(name = "from_savings_transaction_id", nullable = true)
private SavingsAccountTransaction fromTransaction;
@ManyToOne
@JoinColumn(name = "to_savings_transaction_id", nullable = true)
private SavingsAccountTransaction toSavingsTransaction;
@ManyToOne
@JoinColumn(name = "to_loan_account_id", nullable = true)
private Loan toLoanAccount;
@ManyToOne
@JoinColumn(name = "to_loan_transaction_id", nullable = true)
private LoanTransaction toLoanTransaction;
@Column(name = "is_reversed", nullable = false)
private final boolean reversed = false;
@Temporal(TemporalType.DATE)
@Column(name = "transaction_date")
private Date date;
@Embedded
private MonetaryCurrency currency;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@Column(name = "description", length = 100)
private String description;
public static AccountTransfer savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit, final LocalDate transactionDate,
final Money transactionAmount, final String description) {
return new AccountTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount, null, withdrawal,
deposit, null, transactionDate, transactionAmount, description);
}
public static AccountTransfer savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction, final LocalDate transactionDate,
final Money transactionAmount, final String description) {
return new AccountTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, null, toLoanAccount, withdrawal, null,
loanRepaymentTransaction, transactionDate, transactionAmount, description);
}
protected AccountTransfer() {
//
}
private AccountTransfer(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, final Loan toLoanAccount,
final SavingsAccountTransaction withdrawal, final SavingsAccountTransaction deposit,
final LoanTransaction loanRepaymentTransaction, final LocalDate transactionDate, final Money transactionAmount,
final String description) {
this.fromOffice = fromOffice;
this.fromClient = fromClient;
this.fromAccount = fromSavingsAccount;
this.toOffice = toOffice;
this.toClient = toClient;
this.toSavingsAccount = toSavingsAccount;
this.fromTransaction = withdrawal;
this.toSavingsTransaction = deposit;
this.toLoanAccount = toLoanAccount;
this.toLoanTransaction = loanRepaymentTransaction;
this.date = transactionDate.toDate();
this.currency = transactionAmount.getCurrency();
this.amount = transactionAmount.getAmountDefaultedToNullIfZero();
this.description = description;
}
}
@@ -0,0 +1,114 @@
/**
* 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.account.domain;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromOfficeIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toOfficeIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDateParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDescriptionParamName;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.JsonElement;
@Service
public class AccountTransferAssembler {
private final ClientRepositoryWrapper clientRepository;
private final OfficeRepository officeRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public AccountTransferAssembler(final ClientRepositoryWrapper clientRepository, final OfficeRepository officeRepository,
final SavingsAccountAssembler savingsAccountAssembler, final FromJsonHelper fromApiJsonHelper) {
this.clientRepository = clientRepository;
this.officeRepository = officeRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.fromApiJsonHelper = fromApiJsonHelper;
}
public AccountTransfer assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccountTransaction withdrawal,
final SavingsAccountTransaction deposit) {
final JsonElement element = command.parsedJson();
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId);
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
final Office toOffice = this.officeRepository.findOne(toOfficeId);
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount);
final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
return AccountTransfer.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toSavingsAccount,
withdrawal, deposit, transactionDate, transactionMonetaryAmount, description);
}
public AccountTransfer assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
final Loan toLoanAccount, final SavingsAccountTransaction withdrawal, final LoanTransaction loanRepaymentTransaction) {
final JsonElement element = command.parsedJson();
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
final Office fromOffice = this.officeRepository.findOne(fromOfficeId);
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
final Office toOffice = this.officeRepository.findOne(toOfficeId);
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
final Money transactionMonetaryAmount = Money.of(fromSavingsAccount.getCurrency(), transactionAmount);
final String description = command.stringValueOfParameterNamed(transferDescriptionParamName);
return AccountTransfer.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
withdrawal, loanRepaymentTransaction, transactionDate, transactionMonetaryAmount, description);
}
}
@@ -0,0 +1,13 @@
/**
* 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.account.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface AccountTransferRepository extends JpaRepository<AccountTransfer, Long>, JpaSpecificationExecutor<AccountTransfer> {
}
@@ -0,0 +1,15 @@
/**
* 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.account.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class AccountTransferNotFoundException extends AbstractPlatformResourceNotFoundException {
public AccountTransferNotFoundException(final Long id) {
super("error.msg.accounttransfer.id.invalid", "Account transfer with identifier " + id + " does not exist", id);
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.account.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.account.service.AccountTransfersWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateAccountTransferCommandHandler implements NewCommandSourceHandler {
private final AccountTransfersWritePlatformService writePlatformService;
@Autowired
public CreateAccountTransferCommandHandler(final AccountTransfersWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return writePlatformService.create(command);
}
}
@@ -0,0 +1,38 @@
/**
* 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.account.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
public class AccountTransferEnumerations {
public static EnumOptionData accountType(final Integer type) {
return accountType(PortfolioAccountType.fromInt(type));
}
public static EnumOptionData accountType(final PortfolioAccountType type) {
EnumOptionData optionData = null;
if (type != null) {
switch (type) {
case INVALID:
break;
case LOAN:
optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
"Loan Account");
break;
case SAVINGS:
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
break;
}
}
return optionData;
}
}
@@ -0,0 +1,20 @@
/**
* 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.account.service;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
import org.mifosplatform.portfolio.group.service.SearchParameters;
public interface AccountTransfersReadPlatformService {
AccountTransferData retrieveTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
Page<AccountTransferData> retrieveAll(SearchParameters searchParameters);
AccountTransferData retrieveOne(Long transferId);
}
@@ -0,0 +1,331 @@
/**
* 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.account.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.exception.UnsupportedCommandException;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.PaginationHelper;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.office.service.OfficeReadPlatformService;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.exception.AccountTransferNotFoundException;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@Service
public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ClientReadPlatformService clientReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService;
private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
// mapper
private final AccountTransfersMapper accountTransfersMapper;
// pagination
private final PaginationHelper<AccountTransferData> paginationHelper = new PaginationHelper<AccountTransferData>();
@Autowired
public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource,
final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.clientReadPlatformService = clientReadPlatformService;
this.officeReadPlatformService = officeReadPlatformService;
this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
this.accountTransfersMapper = new AccountTransfersMapper();
}
@Override
public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType);
final Collection<EnumOptionData> toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
// TODO - remove this after doing overpaid loan to savings transfers
Integer mostRelevantFromAccountType = fromAccountType;
if (mostRelevantFromAccountType == null) {
mostRelevantFromAccountType = 2; // savings
}
final Integer mostRelevantToAccountType = toAccountType;
final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
// check for now that only savings account type is passed and used.
if (mostRelevantFromAccountType != 2) { throw new UnsupportedCommandException("transferFromLoanAccountNotSupported"); }
// from settings
OfficeData fromOffice = null;
ClientData fromClient = null;
PortfolioAccountData fromAccount = null;
OfficeData toOffice = null;
ClientData toClient = null;
PortfolioAccountData toAccount = null;
// template
Collection<PortfolioAccountData> fromAccountOptions = null;
Collection<PortfolioAccountData> toAccountOptions = null;
Long mostRelevantFromOfficeId = fromOfficeId;
Long mostRelevantFromClientId = fromClientId;
Long mostRelevantToOfficeId = toOfficeId;
Long mostRelevantToClientId = toClientId;
if (fromAccountId != null && mostRelevantFromAccountType == 2) {
fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, PortfolioAccountType.SAVINGS.getValue());
// override provided fromClient with client of account
mostRelevantFromClientId = fromAccount.clientId();
} else if (fromAccountId != null && mostRelevantFromAccountType == 1) { // loans
// fetch loan account
}
if (mostRelevantFromClientId != null) {
fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
mostRelevantFromOfficeId = fromClient.officeId();
fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(mostRelevantFromAccountType,
mostRelevantFromClientId);
}
Collection<OfficeData> fromOfficeOptions = null;
Collection<ClientData> fromClientOptions = null;
if (mostRelevantFromOfficeId != null) {
fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
}
// defaults
final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
Collection<ClientData> toClientOptions = null;
if (toAccountId != null && fromAccount != null) {
toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, fromAccount.currencyCode());
mostRelevantToClientId = toAccount.clientId();
}
if (mostRelevantToClientId != null) {
toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
mostRelevantToOfficeId = toClient.officeId();
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
}
if (mostRelevantToOfficeId != null) {
toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
if (toClientOptions != null && toClientOptions.size() == 1) {
toClient = new ArrayList<ClientData>(toClientOptions).get(0);
toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
}
}
return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
}
private Collection<PortfolioAccountData> retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions,
final Integer toAccountType, final Long toClientId) {
final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null;
Collection<PortfolioAccountData> accountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(toAccountType,
toClientId, currencyCode);
if (!CollectionUtils.isEmpty(accountOptions)) {
accountOptions.remove(excludeThisAccountFromOptions);
} else {
accountOptions = null;
}
return accountOptions;
}
@Override
public Page<AccountTransferData> retrieveAll(final SearchParameters searchParameters) {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
sqlBuilder.append(this.accountTransfersMapper.schema());
if (searchParameters.isOrderByRequested()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
final Object[] objectArray = new Object[2];
final int arrayPos = 0;
final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
final String sqlCountRows = "SELECT FOUND_ROWS()";
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
this.accountTransfersMapper);
}
@Override
public AccountTransferData retrieveOne(final Long transferId) {
try {
final String sql = "select " + this.accountTransfersMapper.schema() + " where sat.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId });
} catch (final EmptyResultDataAccessException e) {
throw new AccountTransferNotFoundException(transferId);
}
}
private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> {
private final String schemaSql;
public AccountTransfersMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("sat.id as id, sat.is_reversed as isReversed,");
sqlBuilder.append("sat.transaction_date as transferDate, sat.amount as transferAmount,");
sqlBuilder.append("sat.description as transferDescription,");
sqlBuilder.append("sat.currency_code as currencyCode, sat.currency_digits as currencyDigits,");
sqlBuilder.append("sat.currency_multiplesof as inMultiplesOf, ");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,");
sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,");
sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,");
sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,");
sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType");
sqlBuilder.append(" FROM m_savings_account_transfer sat ");
sqlBuilder.append("join m_currency curr on curr.code = sat.currency_code ");
sqlBuilder.append("join m_office fromoff on fromoff.id = sat.from_office_id ");
sqlBuilder.append("join m_office tooff on tooff.id = sat.to_office_id ");
sqlBuilder.append("join m_client fromclient on fromclient.id = sat.from_client_id ");
sqlBuilder.append("join m_client toclient on toclient.id = sat.to_client_id ");
sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = sat.from_savings_account_id ");
sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = sat.to_savings_account_id ");
sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = sat.to_loan_account_id ");
sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = sat.from_savings_transaction_id ");
sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = sat.to_savings_transaction_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final boolean reversed = rs.getBoolean("isReversed");
final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate");
final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount");
final String transferDescription = rs.getString("transferDescription");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
final String fromOfficeName = rs.getString("fromOfficeName");
final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
final String toOfficeName = rs.getString("toOfficeName");
final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
final String fromClientName = rs.getString("fromClientName");
final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
final String toClientName = rs.getString("toClientName");
final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
final EnumOptionData fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
final PortfolioAccountData fromSavingsAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo);
PortfolioAccountData toAccount = null;
EnumOptionData toAccountType = null;
final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId");
final String toLoanAccountNo = rs.getString("toLoanAccountNo");
if (toSavingsAccountId != null) {
toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo);
toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
} else if (toLoanAccountId != null) {
toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo);
toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
}
return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice,
toOffice, fromClient, toClient, fromAccountType, fromSavingsAccount, toAccountType, toAccount);
}
}
}
@@ -0,0 +1,14 @@
/**
* 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.account.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface AccountTransfersWritePlatformService {
CommandProcessingResult create(JsonCommand command);
}
@@ -0,0 +1,153 @@
/**
* 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.account.service;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.fromAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.toAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferAmountParamName;
import static org.mifosplatform.portfolio.account.AccountTransfersApiConstants.transferDateParamName;
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.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransfersDataValidator;
import org.mifosplatform.portfolio.account.domain.AccountTransfer;
import org.mifosplatform.portfolio.account.domain.AccountTransferAssembler;
import org.mifosplatform.portfolio.account.domain.AccountTransferRepository;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService {
private final AccountTransfersDataValidator accountTransfersDataValidator;
private final AccountTransferAssembler accountTransferAssembler;
private final AccountTransferRepository accountTransferRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final SavingsAccountDomainService savingsAccountDomainService;
private final LoanAssembler loanAccountAssembler;
private final LoanAccountDomainService loanAccountDomainService;
@Autowired
public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
final AccountTransferAssembler accountTransferAssembler, final AccountTransferRepository accountTransferRepository,
final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService) {
this.accountTransfersDataValidator = accountTransfersDataValidator;
this.accountTransferAssembler = accountTransferAssembler;
this.accountTransferRepository = accountTransferRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.savingsAccountDomainService = savingsAccountDomainService;
this.loanAccountAssembler = loanAssembler;
this.loanAccountDomainService = loanAccountDomainService;
}
@Transactional
@Override
public CommandProcessingResult create(final JsonCommand command) {
this.accountTransfersDataValidator.validate(command);
final LocalDate transactionDate = command.localDateValueOfParameterNamed(transferDateParamName);
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(transferAmountParamName);
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
final PaymentDetail paymentDetail = null;
Long fromSavingsAccountId = null;
Long transferTransactionId = null;
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail);
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail);
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(command, withdrawal,
deposit);
this.accountTransferRepository.saveAndFlush(transferTransaction);
transferTransactionId = transferTransaction.getId();
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
//
fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail);
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(toLoanAccountId,
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
this.accountTransferRepository.saveAndFlush(transferTransaction);
transferTransactionId = transferTransaction.getId();
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
// FIXME - kw - ADD overpaid loan to savings account transfer support.
} else {
}
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId);
if (fromAccountType.isSavingsAccount()) {
builder.withSavingsId(fromSavingsAccountId);
}
return builder.build();
}
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
}
private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
}
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
}
}
@@ -0,0 +1,21 @@
/**
* 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.account.service;
import java.util.Collection;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
public interface PortfolioAccountReadPlatformService {
PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId);
PortfolioAccountData retrieveOne(Long accountId, Integer accountTypeId, String currencyCode);
Collection<PortfolioAccountData> retrieveAllForLookup(Integer toAccountType, Long toClientId);
Collection<PortfolioAccountData> retrieveAllForLookup(Integer toAccountType, Long toClientId, String currencyCode);
}
@@ -0,0 +1,248 @@
/**
* 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.account.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.exception.AccountTransferNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
private final JdbcTemplate jdbcTemplate;
// mapper
private final PortfolioSavingsAccountMapper savingsAccountMapper;
private final PortfolioLoanAccountMapper loanAccountMapper;
@Autowired
public PortfolioAccountReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
this.loanAccountMapper = new PortfolioLoanAccountMapper();
}
@Override
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
return retrieveOne(accountId, accountTypeId, null);
}
@Override
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
Object[] sqlParams = new Object[] {accountId};
PortfolioAccountData accountData = null;
try {
String sql = null;
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
switch (accountType) {
case INVALID:
break;
case LOAN:
sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
if (currencyCode != null) {
sql += " and la.currency_code = ?";
sqlParams = new Object[] {accountId, currencyCode};
}
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
break;
case SAVINGS:
sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
if (currencyCode != null) {
sql += " and la.currency_code = ?";
sqlParams = new Object[] {accountId, currencyCode};
}
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
break;
default:
break;
}
} catch (final EmptyResultDataAccessException e) {
throw new AccountTransferNotFoundException(accountId);
}
return accountData;
}
@Override
public Collection<PortfolioAccountData> retrieveAllForLookup(final Integer accountTypeId, final Long clientId) {
return retrieveAllForLookup(accountTypeId, clientId, null);
}
@Override
public Collection<PortfolioAccountData> retrieveAllForLookup(final Integer accountTypeId, final Long clientId, final String currencyCode) {
Object[] sqlParams = new Object[] {clientId};
Collection<PortfolioAccountData> accounts = null;
String sql = null;
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
switch (accountType) {
case INVALID:
break;
case LOAN:
sql = "select " + this.loanAccountMapper.schema() + " where la.client_id = ? and la.loan_status_id = 300";
if (currencyCode != null) {
sql += " and la.currency_code = ?";
sqlParams = new Object[] {clientId, currencyCode};
}
accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams);
break;
case SAVINGS:
sql = "select " + this.savingsAccountMapper.schema() + " where sa.client_id = ? and sa.status_enum = 300";
if (currencyCode != null) {
sql += " and sa.currency_code = ?";
sqlParams = new Object[] {clientId, currencyCode};
}
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams);
break;
default:
break;
}
return accounts;
}
private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
private final String schemaSql;
public PortfolioSavingsAccountMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
sqlBuilder.append("sp.id as productId, sp.name as productName, ");
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
sqlBuilder.append("from m_savings_account sa ");
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long groupId = JdbcSupport.getLong(rs, "groupId");
final String groupName = rs.getString("groupName");
final Long clientId = JdbcSupport.getLong(rs, "clientId");
final String clientName = rs.getString("clientName");
final Long productId = rs.getLong("productId");
final String productName = rs.getString("productName");
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
final String fieldOfficerName = rs.getString("fieldOfficerName");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
currencyDisplaySymbol, currencyNameCode);
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, currency);
}
}
private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
private final String schemaSql;
public PortfolioLoanAccountMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
sqlBuilder.append("from m_loan la ");
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long groupId = JdbcSupport.getLong(rs, "groupId");
final String groupName = rs.getString("groupName");
final Long clientId = JdbcSupport.getLong(rs, "clientId");
final String clientName = rs.getString("clientName");
final Long productId = rs.getLong("productId");
final String productName = rs.getString("productName");
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
final String fieldOfficerName = rs.getString("fieldOfficerName");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf,
currencyDisplaySymbol, currencyNameCode);
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, currency);
}
}
}
@@ -0,0 +1,57 @@
/**
* 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.accountdetails.data;
import java.util.Collection;
/**
* Immutable data object representing a summary of various accounts.
*/
public class AccountSummaryCollectionData {
@SuppressWarnings("unused")
private final Collection<LoanAccountSummaryData> loanAccounts;
@SuppressWarnings("unused")
private final Collection<SavingsAccountSummaryData> savingsAccounts;
@SuppressWarnings("unused")
private final Collection<LoanAccountSummaryData> memberLoanAccounts;
@SuppressWarnings("unused")
private final Collection<SavingsAccountSummaryData> memberSavingsAccounts;
public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
final Collection<SavingsAccountSummaryData> savingsAccounts) {
this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.memberLoanAccounts = null;
this.memberSavingsAccounts = null;
}
public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
final Collection<SavingsAccountSummaryData> savingsAccounts, final Collection<LoanAccountSummaryData> memberLoanAccounts,
Collection<SavingsAccountSummaryData> memberSavingsAccounts) {
this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts);
this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts);
}
private Collection<LoanAccountSummaryData> defaultLoanAccountsIfEmpty(final Collection<LoanAccountSummaryData> collection) {
Collection<LoanAccountSummaryData> returnCollection = null;
if (collection != null && !collection.isEmpty()) {
returnCollection = collection;
}
return returnCollection;
}
private Collection<SavingsAccountSummaryData> defaultSavingsAccountsIfEmpty(final Collection<SavingsAccountSummaryData> collection) {
Collection<SavingsAccountSummaryData> returnCollection = null;
if (collection != null && !collection.isEmpty()) {
returnCollection = collection;
}
return returnCollection;
}
}
@@ -3,16 +3,16 @@
* 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.client.data;
package org.mifosplatform.portfolio.accountdetails.data;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
/**
* Immutable data object for client loan accounts.
* Immutable data object for loan accounts.
*/
@SuppressWarnings("unused")
public class ClientLoanAccountSummaryData {
public class LoanAccountSummaryData {
private final Long id;
private final String accountNo;
@@ -23,7 +23,7 @@ public class ClientLoanAccountSummaryData {
private final EnumOptionData loanType;
private final Integer loanCycle;
public ClientLoanAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
public LoanAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
final String loanProductName, final LoanStatusEnumData loanStatus, final EnumOptionData loanType, final Integer loanCycle) {
this.id = id;
this.accountNo = accountNo;
@@ -3,18 +3,19 @@
* 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.client.data;
package org.mifosplatform.portfolio.accountdetails.data;
import java.math.BigDecimal;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData;
/**
* Immutable data object for client savings accounts.
* Immutable data object for savings accounts.
*/
@SuppressWarnings("unused")
public class ClientSavingsAccountSummaryData {
public class SavingsAccountSummaryData {
private final Long id;
private final String accountNo;
@@ -24,10 +25,11 @@ public class ClientSavingsAccountSummaryData {
private final SavingsAccountStatusEnumData status;
private final CurrencyData currency;
private final BigDecimal accountBalance;
private final EnumOptionData accountType;
public ClientSavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
final String productName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
final BigDecimal accountBalance) {
final BigDecimal accountBalance, final EnumOptionData accountType) {
this.id = id;
this.accountNo = accountNo;
this.externalId = externalId;
@@ -36,5 +38,6 @@ public class ClientSavingsAccountSummaryData {
this.status = status;
this.currency = currency;
this.accountBalance = accountBalance;
this.accountType = accountType;
}
}
@@ -0,0 +1,81 @@
/**
* 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.accountdetails.domain;
/**
* Enum representation of account types .
*/
public enum AccountType {
INVALID(0, "accountType.invalid"), //
INDIVIDUAL(1, "accountType.individual"), //
GROUP(2, "accountType.group"), //
JLG(3, "accountType.jlg");//JLG account given in group context
private final Integer value;
private final String code;
private AccountType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public static AccountType fromInt(final Integer accountTypeValue) {
AccountType enumeration = AccountType.INVALID;
switch (accountTypeValue) {
case 1:
enumeration = AccountType.INDIVIDUAL;
break;
case 2:
enumeration = AccountType.GROUP;
break;
case 3:
enumeration = AccountType.JLG;
break;
}
return enumeration;
}
public static AccountType fromName(final String name){
AccountType accountType = AccountType.INVALID;
for (AccountType type : AccountType.values()) {
if(type.getName().equals(name)) {
accountType = type;
break;
}
}
return accountType;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return code;
}
public String getName(){
return name().toLowerCase();
}
public boolean isInvalid(){
return this.value.equals(AccountType.INVALID.getValue());
}
public boolean isIndividualAccount(){
return this.value.equals(AccountType.INDIVIDUAL.getValue());
}
public boolean isGroupAccount(){
return this.value.equals(AccountType.GROUP.getValue());
}
public boolean isJLGAccount(){
return this.value.equals(AccountType.JLG.getValue());
}
}
@@ -0,0 +1,18 @@
package org.mifosplatform.portfolio.accountdetails.service;
import java.util.Collection;
import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData;
public interface AccountDetailsReadPlatformService {
public AccountSummaryCollectionData retrieveClientAccountDetails(final Long clientId);
public AccountSummaryCollectionData retrieveGroupAccountDetails(final Long groupId);
public Collection<LoanAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId);
public Collection<LoanAccountSummaryData> retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId,final Long loanOfficerId);
}
@@ -0,0 +1,184 @@
package org.mifosplatform.portfolio.accountdetails.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.mifosplatform.portfolio.accountdetails.data.LoanAccountSummaryData;
import org.mifosplatform.portfolio.accountdetails.data.SavingsAccountSummaryData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.mifosplatform.portfolio.savings.service.SavingsEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements AccountDetailsReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ClientReadPlatformService clientReadPlatformService;
private final GroupReadPlatformService groupReadPlatformService;
@Autowired
public AccountDetailsReadPlatformServiceJpaRepositoryImpl(final ClientReadPlatformService clientReadPlatformService,
final RoutingDataSource dataSource, final GroupReadPlatformService groupReadPlatformService) {
this.clientReadPlatformService = clientReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.groupReadPlatformService = groupReadPlatformService;
}
@Override
public AccountSummaryCollectionData retrieveClientAccountDetails(Long clientId) {
// Check if client exists
clientReadPlatformService.retrieveOne(clientId);
String loanwhereClause = " where l.client_id = ?";
String savingswhereClause = " where sa.client_id = ? order by sa.status_enum ASC, sa.account_no ASC";
List<LoanAccountSummaryData> loanAccounts = retrieveLoanAccountDetails(loanwhereClause, new Object[] { clientId });
List<SavingsAccountSummaryData> savingsAccounts = retrieveAccountDetails(savingswhereClause, new Object[] { clientId });
return new AccountSummaryCollectionData(loanAccounts, savingsAccounts);
}
@Override
public AccountSummaryCollectionData retrieveGroupAccountDetails(Long groupId) {
// Check if group exists
groupReadPlatformService.retrieveOne(groupId);
String loanWhereClauseForGroup = " where l.group_id = ? and l.client_id is null";
String loanWhereClauseForMembers = " where l.group_id = ? and l.client_id is not null";
String savingswhereClauseForGroup = " where sa.group_id = ? and sa.client_id is null order by sa.status_enum ASC, sa.account_no ASC";
String savingswhereClauseForMembers = " where sa.group_id = ? and sa.client_id is not null order by sa.status_enum ASC, sa.account_no ASC";
List<LoanAccountSummaryData> groupLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForGroup, new Object[] { groupId });
List<SavingsAccountSummaryData> groupSavingsAccounts = retrieveAccountDetails(savingswhereClauseForGroup, new Object[] { groupId });
List<LoanAccountSummaryData> memberLoanAccounts = retrieveLoanAccountDetails(loanWhereClauseForMembers, new Object[] { groupId });
List<SavingsAccountSummaryData> memberSavingsAccounts = retrieveAccountDetails(savingswhereClauseForMembers,
new Object[] { groupId });
return new AccountSummaryCollectionData(groupLoanAccounts, groupSavingsAccounts, memberLoanAccounts, memberSavingsAccounts);
}
@Override
public Collection<LoanAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) {
// Check if client exists
clientReadPlatformService.retrieveOne(clientId);
String loanWhereClause = " where l.client_id = ? and l.loan_officer_id = ?";
return retrieveLoanAccountDetails(loanWhereClause, new Object[] { clientId, loanOfficerId });
}
@Override
public Collection<LoanAccountSummaryData> retrieveGroupLoanAccountsByLoanOfficerId(final Long groupId, final Long loanOfficerId) {
// Check if group exists
groupReadPlatformService.retrieveOne(groupId);
String loanWhereClause = " where l.group_id = ? and l.client_id is null and l.loan_officer_id = ?";
return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId, loanOfficerId });
}
private List<LoanAccountSummaryData> retrieveLoanAccountDetails(String loanwhereClause, Object[] inputs) {
LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper();
String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause;
return this.jdbcTemplate.query(sql, rm, inputs);
}
/**
* @param entityId
* @return
*/
private List<SavingsAccountSummaryData> retrieveAccountDetails(String savingswhereClause, Object[] inputs) {
SavingsAccountSummaryDataMapper savingsAccountSummaryDataMapper = new SavingsAccountSummaryDataMapper();
final String savingsSql = "select " + savingsAccountSummaryDataMapper.schema() + savingswhereClause;
return this.jdbcTemplate.query(savingsSql, savingsAccountSummaryDataMapper, inputs);
}
private static final class SavingsAccountSummaryDataMapper implements RowMapper<SavingsAccountSummaryData> {
final String schemaSql;
public SavingsAccountSummaryDataMapper() {
StringBuilder accountsSummary = new StringBuilder();
accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, ");
accountsSummary.append("sa.account_type_enum as accountType, ");
accountsSummary.append("sa.account_balance_derived as accountBalance, ");
accountsSummary
.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, ");
accountsSummary.append("sa.product_id as productId, p.name as productName ");
accountsSummary.append("from m_savings_account sa ");
accountsSummary.append("join m_savings_product as p on p.id = sa.product_id ");
accountsSummary.append("join m_currency curr on curr.code = sa.currency_code ");
this.schemaSql = accountsSummary.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long productId = JdbcSupport.getLong(rs, "productId");
final String productName = rs.getString("productName");
final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum");
final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance");
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId);
final Integer accountType = JdbcSupport.getInteger(rs, "accountType");
final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType);
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, status, currency, accountBalance,
accountTypeData);
}
}
private static final class LoanAccountSummaryDataMapper implements RowMapper<LoanAccountSummaryData> {
public String loanAccountSummarySchema() {
StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,");
accountsSummary.append("l.product_id as productId, lp.name as productName,")
.append("l.loan_status_id as statusId, l.loan_type_enum as loanType, ").append("l.loan_product_counter as loanCycle ")
.append(" from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id");
return accountsSummary.toString();
}
@Override
public LoanAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long productId = JdbcSupport.getLong(rs, "productId");
final String loanProductName = rs.getString("productName");
final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId");
final LoanStatusEnumData loanStatus = LoanEnumerations.status(loanStatusId);
final Integer loanTypeId = JdbcSupport.getInteger(rs, "loanType");
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeId);
final Integer loanCycle = JdbcSupport.getInteger(rs, "loanCycle");
return new LoanAccountSummaryData(id, accountNo, externalId, productId, loanProductName, loanStatus, loanType, loanCycle);
}
}
}
@@ -0,0 +1,38 @@
package org.mifosplatform.portfolio.accountdetails.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
public class AccountEnumerations {
public static EnumOptionData loanType(final Integer loanTypeId) {
return loanType(AccountType.fromInt(loanTypeId));
}
public static EnumOptionData loanType(final String name) {
return loanType(AccountType.fromName(name));
}
public static EnumOptionData loanType(final AccountType type) {
EnumOptionData optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid");
switch (type) {
case INVALID:
optionData = new EnumOptionData(AccountType.INVALID.getValue().longValue(), AccountType.INVALID.getCode(), "Invalid");
break;
case INDIVIDUAL:
optionData = new EnumOptionData(AccountType.INDIVIDUAL.getValue().longValue(), AccountType.INDIVIDUAL.getCode(), "Individual");
break;
case GROUP:
optionData = new EnumOptionData(AccountType.GROUP.getValue().longValue(), AccountType.GROUP.getCode(), "Group");
break;
case JLG:
optionData = new EnumOptionData(AccountType.JLG.getValue().longValue(), AccountType.JLG.getCode(), "JLG");
break;
}
return optionData;
}
}
@@ -20,6 +20,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarDropdownReadPlatformService;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -105,7 +106,7 @@ public class CalendarsApiResource {
Collection<CalendarData> calendarsData = new ArrayList<CalendarData>();
List<Integer> calendarTypeOptions = createIntegerListFromQueryParameter(calendarType);
List<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter(calendarType);
if (!associationParameters.isEmpty()) {
@@ -191,37 +192,4 @@ public class CalendarsApiResource {
return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions);
}
public List<Integer> createIntegerListFromQueryParameter(String calendarTypeQuery) {
List<Integer> calendarTypeOptions = new ArrayList<Integer>();
// adding all calendar Types if query parameter is "all"
if(calendarTypeQuery.equalsIgnoreCase("all")){
calendarTypeOptions.add(1);
calendarTypeOptions.add(2);
calendarTypeOptions.add(3);
calendarTypeOptions.add(4);
return calendarTypeOptions;
}
// creating a list of calendar type options from the comma separated query parameter.
List<String> calendarTypeOptionsInQuery = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
while (st.hasMoreElements()) {
calendarTypeOptionsInQuery.add(st.nextElement().toString());
}
for(String calType : calendarTypeOptionsInQuery){
if(calType.equalsIgnoreCase("collection")) {
calendarTypeOptions.add(1);
} else if(calType.equalsIgnoreCase("training")) {
calendarTypeOptions.add(2);
} else if(calType.equalsIgnoreCase("audit")) {
calendarTypeOptions.add(3);
} else if(calType.equalsIgnoreCase("general")) {
calendarTypeOptions.add(4);
}
}
return calendarTypeOptions;
}
}
@@ -23,7 +23,7 @@ import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.useradministration.domain.AppUser;
@Entity
@@ -213,13 +213,13 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
//FIXME: AA - Is this restriction required only for collection type meetings or for all?.
//Do not allow to change meeting frequency
if(!CalendarHelper.isFrequencySame(this.recurrence, newValue)){
if(!CalendarUtils.isFrequencySame(this.recurrence, newValue)){
final String defaultUserMessage = "Update of meeting frequency is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
}
//Do not allow to change meeting interval
if(!CalendarHelper.isIntervalSame(this.recurrence, newValue)){
if(!CalendarUtils.isIntervalSame(this.recurrence, newValue)){
final String defaultUserMessage = "Update of meeting interval is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
}
@@ -66,4 +66,20 @@ public enum CalendarEntityType {
public String toString() {
return name().toString();
}
public static boolean isGroup(final Integer value) {
return CalendarEntityType.GROUPS.value.equals(value) ? true : false;
}
public static boolean isGroup(final String name) {
return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isCenter(final Integer value) {
return CalendarEntityType.CENTERS.value.equals(value) ? true : false;
}
public static boolean isCenter(final String name) {
return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false;
}
}
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.calendar.domain;
import java.util.Collection;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -14,12 +15,19 @@ import org.springframework.data.repository.query.Param;
public interface CalendarInstanceRepository extends JpaRepository<CalendarInstance, Long>, JpaSpecificationExecutor<CalendarInstance> {
CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
CalendarInstance findByCalendarIdAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId);
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
/**Should use in clause, can I do it without creating a new class?**/
@Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id=:groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3")
List<CalendarInstance> findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("clientId") Long clientId,
@Param("groupId") Long groupId);
}
@@ -0,0 +1,51 @@
/**
* 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.calendar.domain;
import org.mifosplatform.portfolio.calendar.exception.CalendarInstanceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link CalendarInstanceRepository} that is responsible for checking
* if {@link CalendarInstance} is returned when using <code>findOne</code>
* repository method and throwing an appropriate not found exception.
* </p>
*
* <p>
* This is to avoid need for checking and throwing in multiple areas of code
* base where {@link CalendarInstanceRepository} is required.
* </p>
*/
@Service
public class CalendarInstanceRepositoryWrapper {
private final CalendarInstanceRepository repository;
@Autowired
public CalendarInstanceRepositoryWrapper(final CalendarInstanceRepository repository) {
this.repository = repository;
}
public CalendarInstance findOneWithNotFoundDetection(final Long CalendarInstanceId) {
final CalendarInstance calendatInstance = this.repository.findOne(CalendarInstanceId);
if (calendatInstance == null) { throw new CalendarInstanceNotFoundException(CalendarInstanceId); }
return calendatInstance;
}
public void save(final CalendarInstance calendatInstance) {
this.repository.save(calendatInstance);
}
public void delete(final CalendarInstance calendatInstance) {
this.repository.delete(calendatInstance);
}
public void saveAndFlush(final CalendarInstance calendatInstance) {
this.repository.saveAndFlush(calendatInstance);
}
}
@@ -0,0 +1,22 @@
/**
* 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.calendar.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when CalendarInstance resources are not found.
*/
public class CalendarInstanceNotFoundException extends AbstractPlatformResourceNotFoundException {
public CalendarInstanceNotFoundException(final Long id) {
super("error.msg.calendar.instance.id.invalid", "Calendar Instance with identifier " + id + " does not exist", id);
}
public CalendarInstanceNotFoundException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.calendar.instance." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.calendar.service;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import java.util.Collection;
@@ -23,9 +24,13 @@ public interface CalendarReadPlatformService {
CalendarData retrieveNewCalendarDetails();
CalendarData generateRecurringDate(final CalendarData calendarData);
CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate);
Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData);
CalendarData retrieveLoanCalendar(final Long loanId);
CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId);
}
@@ -18,6 +18,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -71,7 +72,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
if (remindById != null && remindById != 0) remindBy = CalendarEnumerations.calendarRemindBy(remindById);
final Integer firstReminder = rs.getInt("firstReminder");
final Integer secondReminder = rs.getInt("secondReminder");
final String humanReadable = CalendarHelper.getRRuleReadable(startDate, recurrence);
final String humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
@@ -113,13 +114,27 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date ";
result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
} else if (!calendarTypeOptions.isEmpty()) {
String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions);
String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ( " + sqlCalendarTypeOptions
+ " ) order by c.start_date ";
result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
}
return result;
}
@Override
public CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId) {
final CalendarDataMapper rm = new CalendarDataMapper();
String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and calendar_type_enum = ? order by c.start_date ";
List<CalendarData> result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId, CalendarType.COLLECTION.getValue() });
if(!result.isEmpty() && result.size() > 0){
return result.get(0);
}
return null;
}
@Override
public Collection<CalendarData> retrieveParentCalendarsByEntity(final Long entityId, final Integer entityTypeId,
@@ -137,7 +152,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? order by c.start_date ";
result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() });
} else {
String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions);
String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? and c.calendar_type_enum in ("
+ sqlCalendarTypeOptions + ") order by c.start_date ";
result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() });
@@ -162,6 +177,11 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
@Override
public CalendarData generateRecurringDate(final CalendarData calendarData) {
return generateRecurringDate(calendarData, null);
}
@Override
public CalendarData generateRecurringDate(CalendarData calendarData, LocalDate tillDate) {
if (!calendarData.isRepeating()) return calendarData;
final String rrule = calendarData.getRecurrence();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
@@ -172,13 +192,22 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
startDate = currentDate.minusYears(1);
}
if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
if(tillDate != null){
if(endDate != null){
if(endDate.isAfter(tillDate)){
endDate = tillDate;//to retrieve meeting dates till specified date (tillDate)
}
}else{
endDate = tillDate;//end date is null then fetch meeting dates tillDate
}
}else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
endDate = currentDate.plusYears(1);
}
final Collection<LocalDate> recurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, endDate);
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, endDate);
return new CalendarData(calendarData, recurringDates, nextTenRecurringDates);
}
@Override
@@ -238,21 +267,5 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
return conditionSql;
}
/**
* function returns a comma separated list of calendar_type_enum values ex.
* 1,2,3,4
*
* @param calendarTypeOptions
* @return
*/
public String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
String sqlCalendarTypeOptions = "";
int size = calendarTypeOptions.size();
for (int i = 0; i < size - 1; i++) {
sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
}
sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
return sqlCalendarTypeOptions;
}
}
@@ -11,6 +11,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
@@ -32,10 +34,10 @@ import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class CalendarHelper {
public class CalendarUtils {
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
return getNextRecurringDate(recur, seedDate, startDate);
}
@@ -82,7 +84,7 @@ public class CalendarHelper {
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) {
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getRecurringDates(recur, seedDate, periodStartDate,
periodEndDate, maxCount);
@@ -210,7 +212,7 @@ public class CalendarHelper {
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return false; }
return isValidRecurringDate(recur, seedDate, date);
@@ -248,7 +250,7 @@ public class CalendarHelper {
}
public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
if(recur.getFrequency().equals(Recur.DAILY)){
meetingFrequencyType = PeriodFrequencyType.DAYS;
@@ -277,12 +279,12 @@ public class CalendarHelper {
}
public static int getInterval(final String recurringRule){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return recur.getInterval();
}
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency){
final Recur recur = CalendarHelper.getICalRecur(calendar.getRecurrence());
final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
if (recur == null) { return null; }
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
@@ -310,7 +312,7 @@ public class CalendarHelper {
}
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
if(isValidRecurringDate(recur, seedDate, oldRepaymentDate)){
return oldRepaymentDate;
@@ -320,7 +322,7 @@ public class CalendarHelper {
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
LocalDate tmpDate = repaymentDate;
if(isValidRecurringDate(recur, seedDate, repaymentDate)){
@@ -364,4 +366,53 @@ public class CalendarHelper {
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
return (oldRecur.getInterval() == newRecur.getInterval());
}
public static List<Integer> createIntegerListFromQueryParameter(String calendarTypeQuery) {
List<Integer> calendarTypeOptions = new ArrayList<Integer>();
// adding all calendar Types if query parameter is "all"
if(calendarTypeQuery.equalsIgnoreCase("all")){
calendarTypeOptions.add(1);
calendarTypeOptions.add(2);
calendarTypeOptions.add(3);
calendarTypeOptions.add(4);
return calendarTypeOptions;
}
// creating a list of calendar type options from the comma separated query parameter.
List<String> calendarTypeOptionsInQuery = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
while (st.hasMoreElements()) {
calendarTypeOptionsInQuery.add(st.nextElement().toString());
}
for(String calType : calendarTypeOptionsInQuery){
if(calType.equalsIgnoreCase("collection")) {
calendarTypeOptions.add(1);
} else if(calType.equalsIgnoreCase("training")) {
calendarTypeOptions.add(2);
} else if(calType.equalsIgnoreCase("audit")) {
calendarTypeOptions.add(3);
} else if(calType.equalsIgnoreCase("general")) {
calendarTypeOptions.add(4);
}
}
return calendarTypeOptions;
}
/**
* function returns a comma separated list of calendar_type_enum values ex.
* 1,2,3,4
*
* @param calendarTypeOptions
* @return
*/
public static String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
String sqlCalendarTypeOptions = "";
int size = calendarTypeOptions.size();
for (int i = 0; i < size - 1; i++) {
sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
}
sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
return sqlCalendarTypeOptions;
}
}
@@ -128,7 +128,7 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId);
final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId);
this.calendarInstanceRepository.saveAndFlush(calendarInstanceForUpdate);
return new CommandProcessingResultBuilder() //
.withCommandId(null) //

Some files were not shown because too many files have changed in this diff Show More