diff --git a/mifosng-db/migrations/core_db/V112__mixreport_sql_support.sql b/mifosng-db/migrations/core_db/V112__mixreport_sql_support.sql new file mode 100644 index 000000000..8a7799db6 --- /dev/null +++ b/mifosng-db/migrations/core_db/V112__mixreport_sql_support.sql @@ -0,0 +1,114 @@ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('xbrlmapping', 'UPDATE_XBRLMAPPING', 'XBRLMAPPING', 'UPDATE', 0); + +DROP TABLE IF EXISTS `mix_taxonomy`; + +CREATE TABLE `mix_taxonomy` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET latin1 DEFAULT NULL, + `namespace_id` int(11) DEFAULT NULL, + `dimension` varchar(100) CHARACTER SET latin1 DEFAULT NULL, + `type` int(11) DEFAULT NULL, + `description` varchar(1000) CHARACTER SET latin1 DEFAULT NULL, + `need_mapping` tinyint(1) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `mix_taxonomy` WRITE; + +INSERT INTO `mix_taxonomy` (`id`, `name`, `namespace_id`, `dimension`, `type`, `description`, `need_mapping`) +VALUES + (1,'AdministrativeExpense',1,NULL,3,NULL,1), + (2,'Assets',3,NULL,1,'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.',1), + (3,'Assets',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',1), + (4,'Assets',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',1), + (5,'CashAndCashEquivalents',1,NULL,1,NULL,1), + (6,'Deposits',3,NULL,1,'The total value of funds placed in an account with an MFI that are payable to a depositor. This item includes any current, checking, or savings accounts that are payable on demand. It also includes time deposits which have a fixed maturity date and compulsory deposits.',1), + (7,'Deposits',3,'DepositProductsDimension:CompulsoryMember',1,'The value of deposits that an MFI\'s clients are required to maintain as a condition of an existing or future loan.',NULL), + (8,'Deposits',3,'DepositProductsDimension:VoluntaryMember',1,'The value of deposits that an MFI\'s clients are not required to maintain as a condition of an existing or future loan.',NULL), + (9,'Deposits',3,'LocationDimension:RuralMember',1,'Located in rural areas. Segmentation based on location.',NULL), + (10,'Deposits',3,'LocationDimension:UrbanMember',1,'Located in urban areas. Segmentation based on location.',NULL), + (11,'Deposits',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL), + (12,'Deposits',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL), + (13,'EmployeeBenefitsExpense',1,NULL,3,NULL,NULL), + (14,'Equity',1,NULL,1,NULL,NULL), + (15,'Expense',1,NULL,3,NULL,NULL), + (16,'FinancialExpense',3,NULL,3,'All costs All costs incurred in raising funds from third parties, fee expenses from non-financial services, net gains (losses) due to changes in fair value of financial liabilities, impairment losses net of reversals of financial assets other than loan portfolio and net gains (losses) from restatement of financial statements in terms of the measuring unit current at the end of the reporting period.',NULL), + (17,'FinancialRevenueOnLoans',3,NULL,2,'Interest and non-interest income generated by the provision of credit services to the clients. Fees and commissions for late payment are also included.',NULL), + (18,'ImpairmentLossAllowanceGrossLoanPortfolio',3,NULL,2,'An allowance for the risk of losses in the gross loan portfolio due to default .',NULL), + (19,'Liabilities',1,NULL,1,NULL,NULL), + (20,'Liabilities',3,'MaturityDimension:LessThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL), + (21,'Liabilities',3,'MaturityDimension:MoreThanOneYearMember',1,'Segmentation based on the life of an asset or liability.',NULL), + (22,'LoanPortfolioGross',3,NULL,2,'All outstanding principals due for all outstanding client loans. This includes current, delinquent, and renegotiated loans, but not loans that have been written off. It does not include interest receivable.',NULL), + (23,'LoanPortfolioGross',3,'CreditProductsDimension:MicroenterpriseMember',2,'Loans that finance the production or trade of goods and services for an individual\'s microenterprise, whether or not the microenterprise is legally registered. Segmentation based on loan product.',NULL), + (24,'LoanPortfolioGross',3,'DelinquencyDimension:OneMonthOrMoreMember',2,'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal past due or renegotiated. Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal past due or renegotiated.',NULL), + (25,'LoanPortfolioGross',3,'DelinquencyDimension:ThreeMonthsOrMoreMember',2,'Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal past due or renegotiated.? Segmentation based on the principal balance of all loans outstanding that have one or more installments of principal past due or renegotiated.',NULL), + (26,'LoanPortfolioGross',3,'LocationDimension:RuralMember',2,'Located in rural areas. Segmentation based on geographic location.',NULL), + (27,'LoanPortfolioGross',3,'LocationDimension:UrbanMember',2,'Located in urbal areas. Segmentation based on geographic location.',NULL), + (28,'LoanPortfolioGross',3,'MaturityDimension:LessThanOneYearMember',2,'Segmentation based on the life of an asset or liability.',NULL), + (29,'LoanPortfolioGross',3,'MaturityDimension:MoreThanOneYearMember',2,'Segmentation based on the life of an asset or liability.',NULL), + (30,'NetLoanLoss',3,'',3,'Referred to the value of delinquency loans written off net of any principal recovery.',NULL), + (31,'NetLoanLossProvisionExpense',3,NULL,3,'Represent the net value of loan portfolio impairment loss considering any reversal on impairment loss and any recovery on loans written off recognized as a income during the accounting period.',NULL), + (32,'NetOperatingIncome',3,NULL,2,'Total operating revenue less all expenses related to the MFI\'s core financial service operation including total financial expense, impairment loss and operating expense. Donations are excluded.',NULL), + (33,'NetOperatingIncomeNetOfTaxExpense',3,NULL,3,'Net operating income reported incorporating the effect of taxes. Taxes include all domestic and foreign taxes which are based on taxable profits, other taxes related to personnel, financial transactions or value-added taxes are not considered in calculation of this value.',NULL), + (34,'NumberOfActiveBorrowers',3,NULL,0,'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.',NULL), + (35,'NumberOfActiveBorrowers',3,'GenderDimension:FemaleMember',0,'The number of individuals who currently have an outstanding loan balance with the MFI or are primarily responsible for repaying any portion of the gross loan portfolio. Individuals who have multiple loans with an MFI should be counted as a single borrower.',NULL), + (36,'NumberOfBoardMembers',3,'GenderDimension:FemaleMember',0,'The number of members that comprise the board of directors at the end of the reporting period who are female.',NULL), + (37,'NumberOfDepositAccounts',3,NULL,0,'The number of individuals who currently have funds on deposit with the MFI on a voluntary basis; i.e., they are not required to maintain the deposit account to access a loan. This number applies only to deposits held by an MFI, not to those deposits held in other institutions by the MFI\'s clients. The number should be based on the number of individuals rather than the number of groups. A single deposit account may represent multiple depositors.',NULL), + (38,'NumberOfDepositors',3,'',0,'The number of deposit accounts, both voluntary and compulsory, opened at the MFI whose balances the institution is liable to repay. The number should be based on the number of individual accounts rather than on the number of groups.',NULL), + (39,'NumberOfEmployees',3,NULL,0,'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.',NULL), + (40,'NumberOfEmployees',3,'GenderDimension:FemaleMember',0,'The number of individuals who are actively employed by an entity. This number includes contract employees or advisors who dedicate a substantial portion of their time to the entity, even if they are not on the entity\'s employees roster.',NULL), + (41,'NumberOfLoanOfficers',3,NULL,0,'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.',NULL), + (42,'NumberOfLoanOfficers',3,'GenderDimension:FemaleMember',0,'The number of employees whose main activity is to manage a portion of the gross loan portfolio. A loan officer is a staff member of record who is directly responsible for arranging and monitoring client loans.',NULL), + (43,'NumberOfManagers',3,'GenderDimension:FemaleMember',0,'The number of members that comprise the management of the institution who are female.',NULL), + (44,'NumberOfOffices',3,NULL,0,'The number of staffed points of service and administrative sites used to deliver or support the delivery of financial services to microfinance clients.',NULL), + (45,'NumberOfOutstandingLoans',3,NULL,0,'The number of loans in the gross loan portfolio. For MFIs using a group lending methodology, the number of loans should refer to the number of individuals receiving loans as part of a group or as part of a group loan.',NULL), + (46,'OperatingExpense',3,NULL,3,'Includes expenses not related to financial and credit loss impairment, such as personnel expenses, depreciation, amortization and administrative expenses.',NULL), + (47,'OperatingIncome',3,NULL,2,'Includes all financial income and other operating revenue which is generated from non-financial services. Operating income also includes net gains (losses) from holding financial assets (changes on their values during the period and foreign exchange differences). Donations or any revenue not related with an MFI\'s core business of making loans and providing financial services are not considered under this category.',NULL), + (48,'WriteOffsOnGrossLoanPortfolio',3,NULL,2,'The value of loans that have been recognized as uncollectible for accounting purposes. A write-off is an accounting procedure that removes the outstanding balance of the loan from the gross loan portfolio and impairment loss allowance. Thus, the write-off does not affect the net loan portfolio, total assets, or any equity account. If the impairment loss allowance is insufficient to cover the amount written off, the excess amount will result in an additional impairment loss on loans recognised in profit or loss of the period.',NULL); + +UNLOCK TABLES; + + +DROP TABLE IF EXISTS `mix_taxonomy_mapping`; + +CREATE TABLE `mix_taxonomy_mapping` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `identifier` varchar(50) CHARACTER SET latin1 NOT NULL DEFAULT '', + `config` varchar(200) CHARACTER SET latin1 DEFAULT NULL, + `last_update_date` datetime DEFAULT NULL, + `currency` varchar(11) CHARACTER SET latin1 DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `mix_taxonomy_mapping` WRITE; + +INSERT INTO `mix_taxonomy_mapping` (`id`, `identifier`, `config`, `last_update_date`, `currency`) +VALUES + (1,'default',NULL,NULL,''); + +UNLOCK TABLES; + + +DROP TABLE IF EXISTS `mix_xbrl_namespace`; + +CREATE TABLE `mix_xbrl_namespace` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `prefix` varchar(20) CHARACTER SET latin1 NOT NULL DEFAULT '', + `url` varchar(100) CHARACTER SET latin1 DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UNQUE` (`prefix`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `mix_xbrl_namespace` WRITE; + +INSERT INTO `mix_xbrl_namespace` (`id`, `prefix`, `url`) +VALUES + (1,'ifrs','http://xbrl.iasb.org/taxonomy/2009-04-01/ifrs'), + (2,'iso4217','http://www.xbrl.org/2003/iso4217'), + (3,'mix','http://www.themix.org/int/fr/ifrs/basi/YYYY-MM-DD/mx-cor'), + (4,'xbrldi','http://xbrl.org/2006/xbrldi'), + (5,'xbrli','http://www.xbrl.org/2003/instance'), + (6,'link','http://www.xbrl.org/2003/linkbase'), + (7,'dc-all','http://www.themix.org/int/fr/ifrs/basi/2010-08-31/dc-all'); + +UNLOCK TABLES; diff --git a/mifosng-provider/build.gradle b/mifosng-provider/build.gradle index 4618a16e8..eb9617560 100644 --- a/mifosng-provider/build.gradle +++ b/mifosng-provider/build.gradle @@ -393,6 +393,20 @@ task showTenantListDBInfo<<{ flywayInfo.execute() } +task repairTenantDB<<{ + description="repair migrate" + + def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/list_db' + def tenantsDbName = 'mifosplatform-tenants'; + if (rootProject.hasProperty("dbName")) { + tenantsDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mysql://localhost:3306/$tenantsDbName" + flyway.locations= [filePath] + flywayRepair.execute() +} + /* * Support publication of artifacts versioned by topic branch. * CI builds supply `-P BRANCH_NAME=` to gradle at build time. diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/XBRLIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/XBRLIntegrationTest.java new file mode 100644 index 000000000..581e98eec --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/XBRLIntegrationTest.java @@ -0,0 +1,48 @@ +package org.mifosplatform.integrationtests; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.junit.Before; +import org.junit.Test; +import org.mifosplatform.integrationtests.common.Utils; +import org.mifosplatform.integrationtests.common.xbrl.XBRLIntegrationTestHelper; + +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; + +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class XBRLIntegrationTest { + + private RequestSpecification requestSpec; + private ResponseSpecification responseSpec; + + private XBRLIntegrationTestHelper xbrlHelper; + + @Before + public void setUp() throws Exception { + Utils.initializeRESTAssured(); + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + } + + @Test + public void shouldRetrieveTaxonomyList() { + xbrlHelper = new XBRLIntegrationTestHelper(requestSpec, responseSpec); + + ArrayList taxonomyList = xbrlHelper.getTaxonomyList(); + this.verifyTaxonomyList(taxonomyList); + } + + private void verifyTaxonomyList(ArrayList taxonomyList) { + System.out.println("--------------------VERIFYING TAXONOMY LIST--------------------------"); + assertEquals("Checking for the 1st taxonomy", "AdministrativeExpense", taxonomyList.get(0).get("name")); + } + +} diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java new file mode 100644 index 000000000..0bb5b4824 --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/xbrl/XBRLIntegrationTestHelper.java @@ -0,0 +1,36 @@ +package org.mifosplatform.integrationtests.common.xbrl; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.mifosplatform.integrationtests.common.Utils; + +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +@SuppressWarnings("rawtypes") +public class XBRLIntegrationTestHelper { + + private final RequestSpecification requestSpec; + private final ResponseSpecification responseSpec; + + private static final String GET_TAXONOMY_LIST_URL = "/mifosng-provider/api/v1/mixtaxonomy?tenantIdentifier=default"; + private static final String TAXONOMY_MAPPING_URL = "/mifosng-provider/api/v1/mixmapping?tenantIdentifier=default"; + + public XBRLIntegrationTestHelper(RequestSpecification requestSpec, ResponseSpecification responseSpec) { + this.requestSpec = requestSpec; + this.responseSpec = responseSpec; + } + + public ArrayList getTaxonomyList() { + ArrayList response = Utils.performServerGet(requestSpec, responseSpec, GET_TAXONOMY_LIST_URL, ""); + + return response; + } + + public HashMap getTaxonomyMapping() { + HashMap response = Utils.performServerGet(requestSpec, responseSpec, TAXONOMY_MAPPING_URL, "config"); + return response; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index daf95146e..e44557141 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -5,14 +5,17 @@ */ package org.mifosplatform.accounting.producttoaccountmapping.serialization; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; + import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.apache.commons.lang.StringUtils; -import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -24,8 +27,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.*; - import com.google.gson.JsonElement; /** diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index c95e4133a..840758ee2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -5,16 +5,18 @@ */ package org.mifosplatform.accounting.producttoaccountmapping.service; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; + import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.producttoaccountmapping.serialization.ProductToGLAccountMappingFromApiJsonDeserializer; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; @@ -22,8 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.*; - import com.google.gson.JsonElement; @Service diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java index 43d535d12..9416c3f5c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -8,9 +8,9 @@ package org.mifosplatform.accounting.producttoaccountmapping.service; import java.util.HashMap; import java.util.Map; -import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; import org.mifosplatform.accounting.glaccount.domain.GLAccountType; import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 1146fbd65..864f10867 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -288,7 +288,7 @@ public class CommandWrapper { public boolean isGroupActivation() { return this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityName.equalsIgnoreCase("GROUP"); } - + public boolean isGroupClose() { return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("GROUP"); } @@ -300,7 +300,7 @@ public class CommandWrapper { public boolean isCenterClose() { return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CENTER"); } - + public boolean isClientIdentifierResource() { return this.entityName.equals("CLIENTIDENTIFIER"); } @@ -421,11 +421,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; } @@ -465,7 +465,7 @@ public class CommandWrapper { public boolean isSavingsAccountDeposit() { return this.actionName.equalsIgnoreCase("DEPOSIT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } - + public boolean isSavingsAccountClose() { return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT"); } @@ -540,6 +540,10 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("DISASSOCIATECLIENTS"); } + public boolean isXBRLMappingResource() { + return this.entityName.equalsIgnoreCase("XBRLMAPPING"); + } + public boolean isHolidayResource() { return this.entityName.equalsIgnoreCase("HOLIDAY"); } @@ -563,7 +567,7 @@ 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"); } @@ -575,19 +579,19 @@ public class CommandWrapper { public boolean isClientClose() { return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT"); } - + public boolean isProposeClientTransfer() { return this.actionName.equalsIgnoreCase("PROPOSETRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); } - + public boolean isWithdrawClientTransfer() { return this.actionName.equalsIgnoreCase("WITHDRAWTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); } - + public boolean isAcceptClientTransfer() { return this.actionName.equalsIgnoreCase("ACCEPTTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); } - + public boolean isRejectClientTransfer() { return this.actionName.equalsIgnoreCase("REJECTTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); } @@ -595,7 +599,7 @@ public class CommandWrapper { public boolean isProductMixResource() { return this.entityName.equalsIgnoreCase("PRODUCTMIX"); } - + public boolean isSchedulerResource() { return this.entityName.equalsIgnoreCase("SCHEDULER"); } @@ -603,12 +607,12 @@ public class CommandWrapper { public boolean isAccountTransferResource() { return this.entityName.equalsIgnoreCase("ACCOUNTTRANSFER"); } - + public boolean isMeetingResource() { return this.entityName.equalsIgnoreCase("MEETING"); } - - public boolean isSaveOrUpdateAttendance(){ + + public boolean isSaveOrUpdateAttendance() { return this.actionName.equalsIgnoreCase("SAVEORUPDATEATTENDANCE"); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index b6a6c4752..9210db4e2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -1195,6 +1195,14 @@ public class CommandWrapperBuilder { this.href = "/accountingrules/" + accountingRuleId; return this; } + + public CommandWrapperBuilder updateTaxonomyMapping(final Long mappingId) { + this.actionName = "UPDATE"; + this.entityName = "XBRLMAPPING"; + this.entityId = mappingId; + this.href = "/xbrlmapping"; + return this; + } public CommandWrapperBuilder createHoliday() { this.actionName = "CREATE"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index 3872e32d8..7b4daa8f9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -517,6 +517,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } + } else if (wrapper.isXBRLMappingResource()) { + if (wrapper.isUpdate()) { + handler = applicationContext.getBean("updateTaxonomyMappingCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } } else if (wrapper.isHolidayResource()) { if (wrapper.isCreate()) { @@ -551,6 +557,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } } else { + throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/api/DocumentManagementApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/api/DocumentManagementApiResource.java index dbf103a2b..901fd87fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/api/DocumentManagementApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/api/DocumentManagementApiResource.java @@ -5,9 +5,27 @@ */ package org.mifosplatform.infrastructure.documentmanagement.api; -import com.sun.jersey.core.header.FormDataContentDisposition; -import com.sun.jersey.multipart.FormDataBodyPart; -import com.sun.jersey.multipart.FormDataParam; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.UriInfo; + import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; @@ -22,17 +40,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.core.UriInfo; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import com.sun.jersey.core.header.FormDataContentDisposition; +import com.sun.jersey.multipart.FormDataBodyPart; +import com.sun.jersey.multipart.FormDataParam; @Path("{entityType}/{entityId}/documents") @Component diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/data/FileData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/data/FileData.java index 6c7761154..984ee35e0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/data/FileData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/data/FileData.java @@ -1,13 +1,13 @@ package org.mifosplatform.infrastructure.documentmanagement.data; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class FileData { private static final Logger logger = LoggerFactory.getLogger(FileData.class); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformService.java index 20a803cc2..50c3741be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformService.java @@ -5,11 +5,11 @@ */ package org.mifosplatform.infrastructure.documentmanagement.service; +import java.util.Collection; + import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData; import org.mifosplatform.infrastructure.documentmanagement.data.FileData; -import java.util.Collection; - public interface DocumentReadPlatformService { Collection retrieveAllDocuments(String entityType, Long entityId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java index 1b3d31d9b..16d53d686 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/documentmanagement/service/DocumentReadPlatformServiceImpl.java @@ -5,6 +5,10 @@ */ package org.mifosplatform.infrastructure.documentmanagement.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.infrastructure.documentmanagement.contentrepository.ContentRepository; @@ -19,10 +23,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collection; - @Service public class DocumentReadPlatformServiceImpl implements DocumentReadPlatformService { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java index 5afcb3311..a298a8e46 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java @@ -5,6 +5,28 @@ */ package org.mifosplatform.portfolio.calendar.api; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +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; @@ -25,12 +47,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.UriInfo; -import java.util.*; - @Path("/{entityType}/{entityId}/calendars") @Component @Scope("singleton") diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java index 4a8b719e4..4b31d1f10 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java @@ -11,6 +11,8 @@ import org.mifosplatform.portfolio.calendar.data.CalendarData; import java.util.Collection; import java.util.List; +import org.mifosplatform.portfolio.calendar.data.CalendarData; + public interface CalendarReadPlatformService { CalendarData retrieveCalendar(final Long calendarId, Long entityId, Integer entityTypeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index 577ce2edb..c1774dc7b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.savings.domain; + import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java index 087c799c1..e25bdb565 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.service; +import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; + import java.util.Map; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; @@ -25,8 +27,6 @@ import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName; - @Service public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements SavingsProductWritePlatformService { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/api/TaxonomyMappingApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/api/TaxonomyMappingApiResource.java new file mode 100644 index 000000000..f4cb72d00 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/api/TaxonomyMappingApiResource.java @@ -0,0 +1,80 @@ +package org.mifosplatform.xbrl.mapping.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +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.ToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.xbrl.mapping.data.TaxonomyMappingData; +import org.mifosplatform.xbrl.mapping.service.ReadTaxonomyMappingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/mixmapping") +@Component +@Scope("singleton") +public class TaxonomyMappingApiResource { + + private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("identifier", "config")); + + private final PlatformSecurityContext context; + private final ToApiJsonSerializer toApiJsonSerializer; + private final ReadTaxonomyMappingService readTaxonomyMappingService; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + @Autowired + public TaxonomyMappingApiResource(PlatformSecurityContext context, ToApiJsonSerializer toApiJsonSerializer, + ReadTaxonomyMappingService readTaxonomyMappingService, + PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + ApiRequestParameterHelper apiRequestParameterHelper) { + + this.context = context; + this.toApiJsonSerializer = toApiJsonSerializer; + this.readTaxonomyMappingService = readTaxonomyMappingService; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.apiRequestParameterHelper = apiRequestParameterHelper; + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTaxonomyMapping(@Context final UriInfo uriInfo) { + context.authenticatedUser(); + TaxonomyMappingData mappingData = this.readTaxonomyMappingService.retrieveTaxonomyMapping(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, mappingData, RESPONSE_DATA_PARAMETERS); + } + + @PUT + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateTaxonomyMapping(final String jsonRequestBody) { + // TODO support multiple configuration file loading + final Long mappingId = (long) 1; + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTaxonomyMapping(mappingId).withJson(jsonRequestBody) + .build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/data/TaxonomyMappingData.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/data/TaxonomyMappingData.java new file mode 100644 index 000000000..a7be0e61e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/data/TaxonomyMappingData.java @@ -0,0 +1,21 @@ +package org.mifosplatform.xbrl.mapping.data; + +public class TaxonomyMappingData { + + private final String identifier; + private final String config; + + public TaxonomyMappingData(final String identifier, final String config) { + this.identifier = identifier; + this.config = config; + } + + public String getIdentifier() { + return this.identifier; + } + + public String getConfig() { + return this.config; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMapping.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMapping.java new file mode 100644 index 000000000..578368792 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMapping.java @@ -0,0 +1,49 @@ +package org.mifosplatform.xbrl.mapping.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "mix_taxonomy_mapping") +public class TaxonomyMapping extends AbstractPersistable { + + @Column(name = "identifier") + private String identifier; + + @Column(name = "config") + private String config; + + @Column(name = "currency") + private String currency; + + protected TaxonomyMapping() { + // default + } + + private TaxonomyMapping(final String identifier, final String config, final String currency) { + this.identifier = StringUtils.defaultIfEmpty(identifier, null); + this.config = StringUtils.defaultIfEmpty(config, null); + this.currency = StringUtils.defaultIfEmpty(currency, null); + } + + public static TaxonomyMapping fromJson(final JsonCommand command) { + final String identifier = command.stringValueOfParameterNamed("identifier"); + final String config = command.stringValueOfParameterNamed("config"); + final String currency = command.stringValueOfParameterNamed("currency"); + return new TaxonomyMapping(identifier, config, currency); + } + + public void update(final JsonCommand command) { + + this.identifier = command.stringValueOfParameterNamed("identifier"); + this.config = command.stringValueOfParameterNamed("config"); + this.currency = command.stringValueOfParameterNamed("currency"); + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMappingRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMappingRepository.java new file mode 100644 index 000000000..46fd1ffc9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/domain/TaxonomyMappingRepository.java @@ -0,0 +1,8 @@ +package org.mifosplatform.xbrl.mapping.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface TaxonomyMappingRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/handler/UpdateTaxonomyMappingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/handler/UpdateTaxonomyMappingCommandHandler.java new file mode 100644 index 000000000..9f02cf7d3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/handler/UpdateTaxonomyMappingCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.xbrl.mapping.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.xbrl.mapping.service.WriteTaxonomyMappingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UpdateTaxonomyMappingCommandHandler implements NewCommandSourceHandler { + + private final WriteTaxonomyMappingService writeTaxonomyService; + + @Autowired + public UpdateTaxonomyMappingCommandHandler(WriteTaxonomyMappingService writeTaxonomyService) { + this.writeTaxonomyService = writeTaxonomyService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.writeTaxonomyService.updateMapping(command.entityId(), command); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingService.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingService.java new file mode 100644 index 000000000..0896528c9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingService.java @@ -0,0 +1,8 @@ +package org.mifosplatform.xbrl.mapping.service; + +import org.mifosplatform.xbrl.mapping.data.TaxonomyMappingData; + +public interface ReadTaxonomyMappingService { + + TaxonomyMappingData retrieveTaxonomyMapping(); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingServiceImpl.java new file mode 100644 index 000000000..27488e2f3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/ReadTaxonomyMappingServiceImpl.java @@ -0,0 +1,50 @@ +package org.mifosplatform.xbrl.mapping.service; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.xbrl.mapping.data.TaxonomyMappingData; +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 ReadTaxonomyMappingServiceImpl implements ReadTaxonomyMappingService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ReadTaxonomyMappingServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + private static final class TaxonomyMappingMapper implements RowMapper { + + public String schema() { + return "identifier, config " + "from mix_taxonomy_mapping"; + } + + @Override + public TaxonomyMappingData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final String identifier = rs.getString("identifier"); + final String config = rs.getString("config"); + return new TaxonomyMappingData(identifier, config); + } + + } + + @Override + public TaxonomyMappingData retrieveTaxonomyMapping() { + try { + final TaxonomyMappingMapper rm = new TaxonomyMappingMapper(); + String sqlString = "select " + rm.schema(); + return this.jdbcTemplate.queryForObject(sqlString, rm); + } catch (final EmptyResultDataAccessException e) { + return null; + } + + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingService.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingService.java new file mode 100644 index 000000000..da020d074 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingService.java @@ -0,0 +1,9 @@ +package org.mifosplatform.xbrl.mapping.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface WriteTaxonomyMappingService { + + CommandProcessingResult updateMapping(Long mappingId, JsonCommand command); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingServiceImpl.java new file mode 100644 index 000000000..06df7ff0f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/mapping/service/WriteTaxonomyMappingServiceImpl.java @@ -0,0 +1,48 @@ +package org.mifosplatform.xbrl.mapping.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.xbrl.mapping.domain.TaxonomyMapping; +import org.mifosplatform.xbrl.mapping.domain.TaxonomyMappingRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WriteTaxonomyMappingServiceImpl implements WriteTaxonomyMappingService { + + private final PlatformSecurityContext context; + private final TaxonomyMappingRepository mappingRepository; + + @Autowired + public WriteTaxonomyMappingServiceImpl(final PlatformSecurityContext context, final TaxonomyMappingRepository mappingRepository) { + this.context = context; + this.mappingRepository = mappingRepository; + } + + @Transactional + @Override + public CommandProcessingResult updateMapping(final Long mappingId, final JsonCommand command) { + try { + context.authenticatedUser(); + + TaxonomyMapping mapping = this.mappingRepository.findOne(mappingId); + if (mapping == null) { + mapping = TaxonomyMapping.fromJson(command); + } else { + mapping.update(command); + } + + this.mappingRepository.saveAndFlush(mapping); + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(mapping.getId()).build(); + + } catch (DataIntegrityViolationException dve) { + return CommandProcessingResult.empty(); + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/api/XBRLReportApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/api/XBRLReportApiResource.java new file mode 100644 index 000000000..1cd5d39e2 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/api/XBRLReportApiResource.java @@ -0,0 +1,45 @@ +package org.mifosplatform.xbrl.report.api; + +import java.sql.Date; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.xbrl.report.service.XBRLBuilder; +import org.mifosplatform.xbrl.report.service.XBRLResultService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/mixreport") +@Component +@Scope("singleton") +public class XBRLReportApiResource { + + private final PlatformSecurityContext context; + private final XBRLResultService xbrlResultService; + @Autowired + private XBRLBuilder xbrlBuilder; + + @Autowired + public XBRLReportApiResource(final PlatformSecurityContext context, final XBRLResultService xbrlResultService) { + this.context = context; + this.xbrlResultService = xbrlResultService; + } + + @GET + @Produces({ MediaType.APPLICATION_XML }) + public String retrieveXBRLReport(@QueryParam("startDate") final Date startDate, @QueryParam("endDate") final Date endDate, + @QueryParam("currency") final String currency) { + + context.authenticatedUser(); + + String xbrl = xbrlBuilder.build(this.xbrlResultService.getXBRLResult(startDate, endDate, currency)); + return xbrl; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/ContextData.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/ContextData.java new file mode 100644 index 000000000..bbe39d08f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/ContextData.java @@ -0,0 +1,57 @@ +package org.mifosplatform.xbrl.report.data; + +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; + +public class ContextData { + + private final String dimensionType; + private final String dimension; + private final Integer periodType; + + public ContextData(String dimensionType, String dimension, Integer taxonomyType) { + this.dimensionType = dimensionType; + this.dimension = dimension; + this.periodType = (taxonomyType == TaxonomyData.BALANCESHEET || taxonomyType == TaxonomyData.PORTFOLIO) ? 0 : 1; + } + + public String getDimensionType() { + return this.dimensionType; + } + + public String getDimension() { + return this.dimension; + } + + public Integer getPeriodType() { + return this.periodType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.dimension == null) ? 0 : this.dimension.hashCode()); + result = prime * result + ((this.dimensionType == null) ? 0 : this.dimensionType.hashCode()); + result = prime * result + ((this.periodType == null) ? 0 : this.periodType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + ContextData other = (ContextData) obj; + if (this.dimension == null) { + if (other.dimension != null) return false; + } else if (!this.dimension.equals(other.dimension)) return false; + if (this.dimensionType == null) { + if (other.dimensionType != null) return false; + } else if (!this.dimensionType.equals(other.dimensionType)) return false; + if (this.periodType == null) { + if (other.periodType != null) return false; + } else if (!this.periodType.equals(other.periodType)) return false; + return true; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/NamespaceData.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/NamespaceData.java new file mode 100644 index 000000000..861aabbfa --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/data/NamespaceData.java @@ -0,0 +1,28 @@ +package org.mifosplatform.xbrl.report.data; + +public class NamespaceData { + + private final Long id; + private final String prefix; + private final String url; + + public NamespaceData(Long id, String prefix, String url) { + + this.id = id; + this.prefix = prefix; + this.url = url; + } + + public Long getId() { + return this.id; + } + + public String getPrefix() { + return this.prefix; + } + + public String getUrl() { + return this.url; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/exception/XBRLMappingInvalidException.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/exception/XBRLMappingInvalidException.java new file mode 100644 index 000000000..b5575007f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/exception/XBRLMappingInvalidException.java @@ -0,0 +1,11 @@ +package org.mifosplatform.xbrl.report.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class XBRLMappingInvalidException extends AbstractPlatformDomainRuleException { + + public XBRLMappingInvalidException(String msg) { + super("error.msg.xbrl.report.mapping.invalid.id", "Mapping does not exist", msg); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceService.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceService.java new file mode 100644 index 000000000..68cd84ba7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceService.java @@ -0,0 +1,10 @@ +package org.mifosplatform.xbrl.report.service; + +import org.mifosplatform.xbrl.report.data.NamespaceData; + +public interface ReadNamespaceService { + + NamespaceData retrieveNamespaceById(Long id); + + NamespaceData retrieveNamespaceByPrefix(String prefix); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceServiceImpl.java new file mode 100644 index 000000000..209051654 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/ReadNamespaceServiceImpl.java @@ -0,0 +1,55 @@ +package org.mifosplatform.xbrl.report.service; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.xbrl.report.data.NamespaceData; +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 ReadNamespaceServiceImpl implements ReadNamespaceService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ReadNamespaceServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + private static final class NamespaceMapper implements RowMapper { + + public String schema() { + return "select id, prefix, url " + "from mix_xbrl_namespace"; + } + + @Override + public NamespaceData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final long id = rs.getLong("id"); + final String prefix = rs.getString("prefix"); + final String url = rs.getString("url"); + return new NamespaceData(id, prefix, url); + } + + } + + @Override + public NamespaceData retrieveNamespaceById(Long id) { + final NamespaceMapper mapper = new NamespaceMapper(); + String sql = mapper.schema() + " where id=" + id; + + return this.jdbcTemplate.queryForObject(sql, mapper); + } + + @Override + public NamespaceData retrieveNamespaceByPrefix(String prefix) { + final NamespaceMapper mapper = new NamespaceMapper(); + String sql = mapper.schema() + " where prefix='" + prefix + "'"; + + return this.jdbcTemplate.queryForObject(sql, mapper); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLBuilder.java new file mode 100644 index 000000000..30124557c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLBuilder.java @@ -0,0 +1,191 @@ +package org.mifosplatform.xbrl.report.service; + +import java.math.BigDecimal; +import java.sql.Date; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.mifosplatform.xbrl.report.data.ContextData; +import org.mifosplatform.xbrl.report.data.NamespaceData; +import org.mifosplatform.xbrl.report.exception.XBRLMappingInvalidException; +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; +import org.mifosplatform.xbrl.taxonomy.data.XBRLData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class XBRLBuilder { + + private static final String SCHEME_URL = "http://www.themix.org"; + private static final String IDENTIFIER = "000000"; + private static final String UNITID_PURE = "Unit1"; + private static final String UNITID_CUR = "Unit2"; + + private Element root; + private HashMap contextMap; + Date startDate; + Date endDate; + private Integer instantScenarioCounter = 1; + private Integer durationScenarioCounter = 1; + + @Autowired + private ReadNamespaceService readNamespaceService; + + public String build(XBRLData xbrlData) { + return this.build(xbrlData.getResultMap(), xbrlData.getStartDate(), xbrlData.getEndDate(), xbrlData.getCurrency()); + } + + public String build(Map map, Date startDate, Date endDate, String currency) { + instantScenarioCounter = 1; + durationScenarioCounter = 1; + contextMap = new HashMap(); + Document doc = DocumentHelper.createDocument(); + root = doc.addElement("xbrl"); + + root.addElement("schemaRef").addNamespace("link", + "http://www.themix.org/sites/default/files/Taxonomy2010/dct/dc-all_2010-08-31.xsd"); + + this.startDate = startDate; + this.endDate = endDate; + + for (Entry entry : map.entrySet()) { + TaxonomyData taxonomy = entry.getKey(); + BigDecimal value = entry.getValue(); + this.addTaxonomy(root, taxonomy, value); + + } + + this.addContexts(); + this.addCurrencyUnit(currency); + this.addNumberUnit(); + + doc.setXMLEncoding("UTF-8"); + + return doc.asXML(); + } + + Element addTaxonomy(Element rootElement, TaxonomyData taxonomy, BigDecimal value) { + + // throw an error is start / endate is null + if (startDate == null || endDate == null) { throw new XBRLMappingInvalidException("start date and end date should not be null"); } + + String prefix = taxonomy.getNamespace(); + String qname = taxonomy.getName(); + if (prefix != null && (!prefix.isEmpty())) { + NamespaceData ns = readNamespaceService.retrieveNamespaceByPrefix(prefix); + if (ns != null) { + + root.addNamespace(prefix, ns.getUrl()); + } + qname = prefix + ":" + taxonomy.getName(); + + } + Element xmlElement = rootElement.addElement(qname); + + String dimension = taxonomy.getDimension(); + SimpleDateFormat timeFormat = new SimpleDateFormat("MM_dd_yyyy"); + + ContextData context = null; + if (dimension != null) { + String[] dims = dimension.split(":"); + + if (dims.length == 2) { + context = new ContextData(dims[0], dims[1], taxonomy.getType()); + if (contextMap.containsKey(context)) { + + } else { + + } + } + } + + if (context == null) { + context = new ContextData(null, null, taxonomy.getType()); + } + + if (!contextMap.containsKey(context)) { + + String startDateStr = timeFormat.format(startDate); + String endDateStr = timeFormat.format(endDate); + + String contextRefID = (context.getPeriodType() == 0) ? ("As_Of_" + endDateStr + (instantScenarioCounter++)) : ("Duration_" + + startDateStr + "_To_" + endDateStr + (durationScenarioCounter++)); + + contextMap.put(context, contextRefID); + } + + xmlElement.addAttribute("contextRef", contextMap.get(context)); + xmlElement.addAttribute("unitRef", getUnitRef(taxonomy)); + xmlElement.addAttribute("decimals", getNumberOfDecimalPlaces(value).toString()); + + // add the child + xmlElement.addText(value.toPlainString()); + + return xmlElement; + } + + private String getUnitRef(TaxonomyData tx) { + return tx.isPortfolio() ? UNITID_PURE : UNITID_CUR; + } + + /** + * Adds the generic number unit + */ + void addNumberUnit() { + Element numerUnit = root.addElement("unit"); + numerUnit.addAttribute("id", UNITID_PURE); + Element measure = numerUnit.addElement("measure"); + measure.addText("xbrli:pure"); + + } + + /** + * Adds the currency unit to the document + * + * @param currencyCode + */ + public void addCurrencyUnit(String currencyCode) { + Element currencyUnitElement = root.addElement("unit"); + currencyUnitElement.addAttribute("id", UNITID_CUR); + Element measure = currencyUnitElement.addElement("measure"); + measure.addText("iso4217:" + currencyCode); + + } + + public void addContexts() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + for (Entry entry : contextMap.entrySet()) { + ContextData context = entry.getKey(); + Element contextElement = root.addElement("context"); + contextElement.addAttribute("id", entry.getValue()); + contextElement.addElement("entity").addElement("identifier").addAttribute("scheme", SCHEME_URL).addText(IDENTIFIER); + + Element periodElement = contextElement.addElement("period"); + + if (context.getPeriodType() == 0) { + periodElement.addElement("instant").addText(format.format(endDate)); + } else { + periodElement.addElement("startDate").addText(format.format(startDate)); + periodElement.addElement("endDate").addText(format.format(endDate)); + } + + String dimension = context.getDimension(); + String dimType = context.getDimensionType(); + if (dimType != null && dimension != null) { + contextElement.addElement("scenario").addElement("explicitMember").addAttribute("dimension", dimType).addText(dimension); + } + } + + } + + private Integer getNumberOfDecimalPlaces(BigDecimal bigDecimal) { + String string = bigDecimal.stripTrailingZeros().toPlainString(); + int index = string.indexOf("."); + return index < 0 ? 0 : string.length() - index - 1; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultService.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultService.java new file mode 100644 index 000000000..6420a82a3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultService.java @@ -0,0 +1,11 @@ +package org.mifosplatform.xbrl.report.service; + +import java.sql.Date; + +import org.mifosplatform.xbrl.taxonomy.data.XBRLData; + +public interface XBRLResultService { + + XBRLData getXBRLResult(Date startDate, Date endDate, String currency); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultServiceImpl.java new file mode 100644 index 000000000..f92793b6d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/report/service/XBRLResultServiceImpl.java @@ -0,0 +1,192 @@ +package org.mifosplatform.xbrl.report.service; + +import java.math.BigDecimal; +import java.sql.Date; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.xbrl.mapping.data.TaxonomyMappingData; +import org.mifosplatform.xbrl.mapping.service.ReadTaxonomyMappingService; +import org.mifosplatform.xbrl.report.exception.XBRLMappingInvalidException; +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; +import org.mifosplatform.xbrl.taxonomy.data.XBRLData; +import org.mifosplatform.xbrl.taxonomy.service.ReadTaxonomyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; + +import com.google.gson.Gson; + +@Component +public class XBRLResultServiceImpl implements XBRLResultService { + + private static final ScriptEngine SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("JavaScript"); + + private final ReadTaxonomyMappingService readTaxonomyMappingService; + private final ReadTaxonomyService readTaxonomyService; + private final JdbcTemplate jdbcTemplate; + private HashMap accountBalanceMap; + + @Autowired + public XBRLResultServiceImpl(final RoutingDataSource dataSource, final ReadTaxonomyMappingService readTaxonomyMappingService, + final ReadTaxonomyService readTaxonomyService) { + this.readTaxonomyMappingService = readTaxonomyMappingService; + this.readTaxonomyService = readTaxonomyService; + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public XBRLData getXBRLResult(Date startDate, Date endDate, String currency) { + + HashMap config = this.retrieveTaxonomyConfig(startDate, endDate); + if (config == null || config.size() == 0) { throw new XBRLMappingInvalidException("Mapping is empty"); } + return new XBRLData(config, startDate, endDate, currency); + } + + @SuppressWarnings("unchecked") + private HashMap retrieveTaxonomyConfig(Date startDate, Date endDate) { + TaxonomyMappingData taxonomyMapping = this.readTaxonomyMappingService.retrieveTaxonomyMapping(); + if (taxonomyMapping == null) { return null; } + String config = taxonomyMapping.getConfig(); + if (config != null) { + // + HashMap configMap = new HashMap(); + configMap = new Gson().fromJson(config, configMap.getClass()); + if (configMap == null) { return null; } + // + HashMap resultMap = new HashMap(); + this.setupBalanceMap(this.getAccountSql(startDate, endDate)); + for (Entry entry : configMap.entrySet()) { + BigDecimal value = this.processMappingString(entry.getValue()); + if (value != null) { + TaxonomyData taxonomy = readTaxonomyService.retrieveTaxonomyById(Long.parseLong(entry.getKey())); + resultMap.put(taxonomy, value); + } + + } + return resultMap; + } + return null; + } + + private String getAccountSql(Date startDate, Date endDate) { + String sql = "select debits.glcode as 'glcode', debits.name as 'name', (ifnull(debits.debitamount,0)-ifnull(credits.creditamount,0)) as 'balance' " + + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' " + + "from acc_gl_journal_entry,acc_gl_account " + + "where acc_gl_account.id = acc_gl_journal_entry.account_id " + + "and acc_gl_journal_entry.type_enum=2 " + "and acc_gl_journal_entry.entry_date <= " + + endDate + + " and acc_gl_journal_entry.entry_date > " + + startDate + + + // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) " + // + + " group by glcode " + + "order by glcode) debits " + + "LEFT OUTER JOIN " + + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' " + + "from acc_gl_journal_entry,acc_gl_account " + + "where acc_gl_account.id = acc_gl_journal_entry.account_id " + + "and acc_gl_journal_entry.type_enum=1 " + + "and acc_gl_journal_entry.entry_date <= " + + endDate + + " and acc_gl_journal_entry.entry_date > " + + startDate + + + // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) " + // + + " group by glcode " + + "order by glcode) credits " + + "on debits.glcode=credits.glcode " + + "union " + + "select credits.glcode as 'glcode', credits.name as 'name', (ifnull(debits.debitamount,0)-ifnull(credits.creditamount,0)) as 'balance' " + + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' " + + "from acc_gl_journal_entry,acc_gl_account " + + "where acc_gl_account.id = acc_gl_journal_entry.account_id " + + "and acc_gl_journal_entry.type_enum=2 " + + "and acc_gl_journal_entry.entry_date <= " + + endDate + + " and acc_gl_journal_entry.entry_date > " + + startDate + + + // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) " + // + + " group by glcode " + + "order by glcode) debits " + + "RIGHT OUTER JOIN " + + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' " + + "from acc_gl_journal_entry,acc_gl_account " + + "where acc_gl_account.id = acc_gl_journal_entry.account_id " + + "and acc_gl_journal_entry.type_enum=1 " + + "and acc_gl_journal_entry.entry_date <= " + + endDate + + " and acc_gl_journal_entry.entry_date > " + startDate + + // "and (acc_gl_journal_entry.office_id=${branch} or ${branch}=1) " + // + + " group by name " + "order by glcode) credits " + "on debits.glcode=credits.glcode;"; + return sql; + } + + private void setupBalanceMap(String sql) { + if (accountBalanceMap == null) { + accountBalanceMap = new HashMap(); + SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); + while (rs.next()) { + accountBalanceMap.put(rs.getString("glcode"), rs.getBigDecimal("balance")); + } + } + } + + // Calculate Taxonomy value from expression + BigDecimal processMappingString(String mappingString) { + ArrayList glCodes = this.getGLCodes(mappingString); + for (String glcode : glCodes) { + + BigDecimal balance = accountBalanceMap.get(glcode); + mappingString = mappingString.replaceAll("\\{" + glcode + "\\}", balance != null ? balance.toString() : "0"); + } + + // evaluate the expression + Float eval = 0f; + try { + Number value = (Number) SCRIPT_ENGINE.eval(mappingString); + if (value != null) { + eval = value.floatValue(); + } + } catch (ScriptException e) { + e.printStackTrace(); + throw new IllegalArgumentException(e.getMessage()); + } + + return new BigDecimal(eval); + } + + ArrayList getGLCodes(String template) { + + ArrayList placeholders = new ArrayList(); + + if (template != null) { + + Pattern p = Pattern.compile("\\{(.*?)\\}"); + Matcher m = p.matcher(template); + + while (m.find()) { // find next match + String match = m.group(); + String code = match.substring(1, match.length() - 1); + placeholders.add(code); + } + + } + return placeholders; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/api/TaxonomyApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/api/TaxonomyApiResource.java new file mode 100644 index 000000000..b08387b9f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/api/TaxonomyApiResource.java @@ -0,0 +1,58 @@ +package org.mifosplatform.xbrl.taxonomy.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; +import org.mifosplatform.xbrl.taxonomy.service.ReadTaxonomyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/mixtaxonomy") +@Component +@Scope("singleton") +public class TaxonomyApiResource { + + private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("taxonomyId", "name", "namespace", "dimension", + "description")); + + private final PlatformSecurityContext context; + private final ToApiJsonSerializer toApiJsonSerializer; + private final ReadTaxonomyService readTaxonomyService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + @Autowired + public TaxonomyApiResource(final PlatformSecurityContext context, final ToApiJsonSerializer toApiJsonSerializer, + final ReadTaxonomyService readTaxonomyService, final ApiRequestParameterHelper apiRequestParameterHelper) { + this.context = context; + this.toApiJsonSerializer = toApiJsonSerializer; + this.readTaxonomyService = readTaxonomyService; + this.apiRequestParameterHelper = apiRequestParameterHelper; + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTaxonomyList(@Context final UriInfo uriInfo) { + context.authenticatedUser(); + List taxonomyDatas = this.readTaxonomyService.retrieveAllTaxonomy(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, taxonomyDatas, RESPONSE_DATA_PARAMETERS); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/TaxonomyData.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/TaxonomyData.java new file mode 100644 index 000000000..e202b63f4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/TaxonomyData.java @@ -0,0 +1,55 @@ +package org.mifosplatform.xbrl.taxonomy.data; + +public class TaxonomyData { + + public static final Integer PORTFOLIO = 0; + public static final Integer BALANCESHEET = 1; + public static final Integer INCOME = 2; + public static final Integer EXPENSE = 3; + + private final Long id; + private final String name; + private final String namespace; + private final String dimension; + private final Integer type; + private final String description; + + public TaxonomyData(Long id, String name, String namespace, String dimension, Integer type, String description) { + + this.id = id; + this.name = name; + this.namespace = namespace; + this.dimension = dimension; + this.type = type; + this.description = description; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public String getNamespace() { + return this.namespace; + } + + public String getDimension() { + return this.dimension; + } + + public String getDescription() { + return this.description; + } + + public Integer getType() { + return this.type; + } + + public boolean isPortfolio() { + return this.type == 5; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/XBRLData.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/XBRLData.java new file mode 100644 index 000000000..fd95c0ad5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/data/XBRLData.java @@ -0,0 +1,37 @@ +package org.mifosplatform.xbrl.taxonomy.data; + +import java.math.BigDecimal; +import java.sql.Date; +import java.util.HashMap; + +public class XBRLData { + + private HashMap resultMap; + private Date startDate; + private Date endDate; + private String currency; + + public XBRLData(HashMap resultMap, Date startDate, Date endDate, String currency) { + this.resultMap = resultMap; + this.startDate = startDate; + this.endDate = endDate; + this.currency = currency; + } + + public HashMap getResultMap() { + return this.resultMap; + } + + public Date getStartDate() { + return this.startDate; + } + + public Date getEndDate() { + return this.endDate; + } + + public String getCurrency() { + return this.currency; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyService.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyService.java new file mode 100644 index 000000000..0650b515d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyService.java @@ -0,0 +1,12 @@ +package org.mifosplatform.xbrl.taxonomy.service; + +import java.util.List; + +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; + +public interface ReadTaxonomyService { + + List retrieveAllTaxonomy(); + + TaxonomyData retrieveTaxonomyById(Long id); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyServiceImpl.java new file mode 100644 index 000000000..7c02d19ca --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/xbrl/taxonomy/service/ReadTaxonomyServiceImpl.java @@ -0,0 +1,59 @@ +package org.mifosplatform.xbrl.taxonomy.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; +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 ReadTaxonomyServiceImpl implements ReadTaxonomyService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ReadTaxonomyServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + static final class TaxonomyMapper implements RowMapper { + + public String schema() { + return "tx.id as id, name, dimension, type, description, prefix " + + "from mix_taxonomy tx left join mix_xbrl_namespace xn on tx.namespace_id=xn.id"; + } + + @Override + public TaxonomyData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final long id = rs.getLong("id"); + final String name = rs.getString("name"); + final String namespace = rs.getString("prefix"); + + final String dimension = rs.getString("dimension"); + final Integer type = rs.getInt("type"); + final String desc = rs.getString("description"); + return new TaxonomyData(id, name, namespace, dimension, type, desc); + } + + } + + @Override + public List retrieveAllTaxonomy() { + final TaxonomyMapper rm = new TaxonomyMapper(); + String sql = "select " + rm.schema(); + return this.jdbcTemplate.query(sql, rm); + } + + @Override + public TaxonomyData retrieveTaxonomyById(Long id) { + final TaxonomyMapper rm = new TaxonomyMapper(); + String sql = "select " + rm.schema() + " where tx.id =" + id; + return this.jdbcTemplate.queryForObject(sql, rm); + } + +} diff --git a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml index 6ebf89449..e1e9111cf 100644 --- a/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml +++ b/mifosng-provider/src/main/resources/META-INF/spring/appContext.xml @@ -33,7 +33,8 @@ org.mifosplatform.portfolio.loanaccount.*, org.mifosplatform.portfolio.savingsaccount.*, org.mifosplatform.portfolio.*, - org.mifosplatform.useradministration.*"> + org.mifosplatform.useradministration.*, + org.mifosplatform.xbrl.*"> @@ -46,6 +47,8 @@ + + diff --git a/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/ReadTaxonomyMappingServiceImplTest.java b/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/ReadTaxonomyMappingServiceImplTest.java new file mode 100644 index 000000000..b6caf8d92 --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/ReadTaxonomyMappingServiceImplTest.java @@ -0,0 +1,33 @@ +package org.mifosplatform.xbrl.report.service; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ReadTaxonomyMappingServiceImplTest { + + XBRLResultServiceImpl readService; + + @Before + public void setUp() throws Exception { + RoutingDataSource dataSource = Mockito.mock(RoutingDataSource.class); + readService = new XBRLResultServiceImpl(dataSource, null, null); + + } + + @Test + public void shouldCorrectlyGetGLCode() { + ArrayList result = readService.getGLCodes("{12000}+{11000}"); + assertEquals("12000", result.get(0)); + assertEquals("11000", result.get(1)); + } + +} diff --git a/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/XBRLBuilderTest.java b/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/XBRLBuilderTest.java new file mode 100644 index 000000000..a6b5953cd --- /dev/null +++ b/mifosng-provider/src/test/java/org/mifosplatform/xbrl/report/service/XBRLBuilderTest.java @@ -0,0 +1,73 @@ +package org.mifosplatform.xbrl.report.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.sql.Date; +import java.util.HashMap; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mifosplatform.xbrl.report.data.NamespaceData; +import org.mifosplatform.xbrl.taxonomy.data.TaxonomyData; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +@RunWith(MockitoJUnitRunner.class) +public class XBRLBuilderTest { + + @Mock + private ReadNamespaceServiceImpl readNamespaceService; + @InjectMocks + private XBRLBuilder xbrlBuilder = new XBRLBuilder(); + + @Before + public void setUp() throws Exception { + + readNamespaceService = Mockito.mock(ReadNamespaceServiceImpl.class); + when(readNamespaceService.retrieveNamespaceByPrefix(Mockito.anyString())).thenReturn( + new NamespaceData(1l, "mockedprefix", "mockedurl")); + + } + + @Test + public void shouldCorrectlyBuildMap() { + + HashMap map = new HashMap(); + TaxonomyData data1 = Mockito.mock(TaxonomyData.class); + when(data1.getName()).thenReturn("Assets"); + map.put(data1, new BigDecimal(10000)); + String result = xbrlBuilder.build(map, Date.valueOf("2005-11-11"), Date.valueOf("2013-07-17"), "USD"); + System.out.println(result); + NodeList nodes = null; + try { + nodes = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(result.getBytes())) + .getElementsByTagName("Assets"); + } catch (SAXException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + assertNotNull(nodes); + assertNotNull(nodes.item(0)); + assertEquals("Assets", nodes.item(0).getNodeName()); + } + +}