Merge branch 'develop'
This commit is contained in:
+546
-38
@@ -11,7 +11,7 @@
|
||||
var result = {};
|
||||
if (window.location.search) {
|
||||
// split up the query string and store in an associative array
|
||||
var params = window.location.search.slice(1).split("&"); x
|
||||
var params = window.location.search.slice(1).split("&");
|
||||
for ( var i = 0; i < params.length; i++) {
|
||||
var tmp = params[i].split("=");
|
||||
result[tmp[0]] = unescape(tmp[1]);
|
||||
@@ -90,7 +90,7 @@
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="flybar">
|
||||
<div id="nav-logo">1.2.1.RELEASE API</div>
|
||||
<div id="nav-logo">1.3.0.RELEASE API</div>
|
||||
<div class="flybar-nav">
|
||||
<h2 class="flybar-button">Overview</h2>
|
||||
<div class="flybar-menu-overview">
|
||||
@@ -1063,11 +1063,19 @@
|
||||
<tr>
|
||||
<td><a href="#datatables">Data Table</a></td>
|
||||
<td>datatables</td>
|
||||
<td></td>
|
||||
<td><a href="#datatables_createTable">Create Data Table</a></td>
|
||||
<td><a href="#datatables_list">List Data Tables</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>datatables/{datatable}</td>
|
||||
<td></td>
|
||||
<td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
|
||||
<td><a href="#datatables_updateTable">Update Data Table</a></td>
|
||||
<td><a href="#datatables_deleteTable">Delete Data Table</a></td>
|
||||
</tr>
|
||||
<tr class="alt">
|
||||
<td></td>
|
||||
<td>datatables/register/{datatable}/{apptable}</td>
|
||||
@@ -3813,6 +3821,34 @@ GET https://DomainName/mifosng-provider/api/v1/groups
|
||||
<span>Example Values:</span> 2=Per month, 3=Per year
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestCharged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>interestChargedFromDate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Date - The date from with interest is to start being charged.</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt>
|
||||
<td>expectedDisbursementDate</td>
|
||||
</tr>
|
||||
@@ -4841,6 +4877,15 @@ Request Body:
|
||||
expectedDisbursementDate, submittedOnDate, loanType</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
@@ -6959,6 +7004,28 @@ Request Body:
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestCharged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt>
|
||||
<td>accountingRule</td>
|
||||
</tr>
|
||||
@@ -7516,13 +7583,14 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
<td>name, currencyCode, digitsAfterDecimal, principal,
|
||||
numberOfRepayments, repaymentEvery, repaymentFrequencyType,
|
||||
interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType,
|
||||
interestCalculationPeriodType, transactionProcessingStrategyId, accountingRule</td>
|
||||
interestCalculationPeriodType, transactionProcessingStrategyId,
|
||||
accountingRule</td>
|
||||
</tr>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>inArrearsTolerance, charges,paymentChannelToFundSourceMappings</td>
|
||||
<td>inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings</td>
|
||||
</tr>
|
||||
|
||||
<tr class="matrixHeadingBG">
|
||||
@@ -9426,6 +9494,30 @@ Content-Type: application/json
|
||||
<tr>
|
||||
<td class=fielddesc>Credit account for creating accounting rule</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>debitTags</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Debit tags for creating accounting rule, These tags fetches the all chart of accounts that are tagged with these tagIds</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>creditTags</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Credit tags for creating accounting rule, These tags fetches the all chart of accounts that are tagged with these tagIds</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>allowMultipleDebitEntries</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Allows to add multiple debits if the value is true, while creating journal entries with this rule</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>allowMultipleCreditEntries</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Allows to add multiple credits if the value is true, while creating journal entries with this rule</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -9537,6 +9629,64 @@ Request Body:
|
||||
{
|
||||
"officeId":1,
|
||||
"resourceId":1
|
||||
}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST accountingrules
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"name": "fordocs",
|
||||
"officeId": "1",
|
||||
"debitTags": ["10","11"],
|
||||
"allowMultipleDebitEntries": "true",
|
||||
"creditTags": ["12","13"],
|
||||
"allowMultipleCreditEntries": "true",
|
||||
"description": "for api docs"
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId":1,
|
||||
"resourceId":2
|
||||
}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST accountingrules
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"name": "test123",
|
||||
"officeId": "1",
|
||||
"debitAccountHead": "21",
|
||||
"creditTags": ["12", "13"],
|
||||
"allowMultipleCreditEntries": "false",
|
||||
"description": "Employee salary"
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId":1,
|
||||
"resourceId":3
|
||||
}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST accountingrules
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"name": "fordocstest",
|
||||
"officeId": "1",
|
||||
"debitTags": ["10", "11"],
|
||||
"allowMultipleDebitEntries": "false",
|
||||
"creditAccountHead": "9",
|
||||
"description": "for api docs"
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId":1,
|
||||
"resourceId":4
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
@@ -9734,6 +9884,23 @@ Content-Type: application/json
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
|
||||
e.g. 6 <b>Months</b></td></tr>
|
||||
|
||||
<tr class=alt><td>withdrawalFeeAmount</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>withdrawalFeeType</i> to indicate the amount to be charged on the account for withdrawing. e.g. <b>5</b> Flat or <b>5</b> % of Amount</td></tr>
|
||||
|
||||
<tr class=alt><td>withdrawalFeeType</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>withdrawalFeeAmount</i> to indicate the amount to be charged on the account for withdrawing. 1=Flat, 2=% of Amount, e.g. 5 <b>Flat</b> or 5 <b>% of Amount</b></td></tr>
|
||||
|
||||
<tr class=alt><td>annualFeeAmount</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>annualFeeOnMonthDay</i> to indicate the amount to be charged on the account annually on a given day of month. e.g. <b>10</b> on 10 May. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt><td>annualFeeOnMonthDay</td></tr>
|
||||
<tr>
|
||||
<td class=fielddesc><b>Optional</b>: If provided, used along with <i>annualFeeAmount</i> and <i>monthDayFormat</i> to indicate the amount to be charged on the account annually on a given day of month. e.g. 10 on <b>10 May</b>. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt><td>accountingRule</td></tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies if accounting is enabled for the particular
|
||||
@@ -10252,7 +10419,7 @@ Content-Type: application/json
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG"><td><div class="mifosXHeading2">Optional Fields</div></td></tr>
|
||||
<tr class=alt><td>minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, paymentChannelToFundSourceMappings</tr>
|
||||
<tr class=alt><td>minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, monthDayFormat, paymentChannelToFundSourceMappings</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
@@ -10558,6 +10725,23 @@ Content-Type: application/json
|
||||
<tr class=alt><td>lockinPeriodFrequencyType</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>lockinPeriodFrequency</i> to indicate the length of time that the savings account is 'locked in' and withdrawals are not allowed. 0=Days, 1=Weeks, 2=Months, 3=Years
|
||||
e.g. 6 <b>Months</b></td></tr>
|
||||
|
||||
<tr class=alt><td>withdrawalFeeAmount</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>withdrawalFeeType</i> to indicate the amount to be charged on the account for withdrawing. e.g. <b>5</b> Flat or <b>5</b> % of Amount</td></tr>
|
||||
|
||||
<tr class=alt><td>withdrawalFeeType</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>withdrawalFeeAmount</i> to indicate the amount to be charged on the account for withdrawing. 1=Flat, 2=% of Amount, e.g. 5 <b>Flat</b> or 5 <b>% of Amount</b></td></tr>
|
||||
|
||||
<tr class=alt><td>annualFeeAmount</td></tr>
|
||||
<tr><td class=fielddesc><b>Optional</b>: If provided, used along with <i>annualFeeOnMonthDay</i> to indicate the amount to be charged on the account annually on a given day of month. e.g. <b>10</b> on 10 May. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt><td>annualFeeOnMonthDay</td></tr>
|
||||
<tr>
|
||||
<td class=fielddesc><b>Optional</b>: If provided, used along with <i>annualFeeAmount</i> and <i>monthDayFormat</i> to indicate the amount to be charged on the account annually on a given day of month. e.g. 10 on <b>10 May</b>. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10758,7 +10942,9 @@ Content-Type: application/json
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG"><td><div class="mifosXHeading2">Inherited from Product (if not provided)</div></td></tr>
|
||||
<tr class=alt><td>nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType</td></tr>
|
||||
<tr class=alt><td>nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
|
||||
lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
@@ -11240,6 +11426,57 @@ No Request Body:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="datatables_createTable" name="datatables_createTable"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Create Data Table</h4>
|
||||
<p>Create a new data table and registers it with the Mifos X Core application
|
||||
table.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/datatables
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST https://DomainName/api/v1/datatables
|
||||
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"datatableName": "extra_client_details",
|
||||
"apptableName": "m_client",
|
||||
"columns": [
|
||||
{
|
||||
"name": "Gender",
|
||||
"type": "Dropdown",
|
||||
"code": "Gender"
|
||||
},
|
||||
{
|
||||
"name": "Some Decimal",
|
||||
"type": "Decimal",
|
||||
"mandatory": true
|
||||
},
|
||||
{
|
||||
"name": "Birth Date",
|
||||
"type": "Date"
|
||||
},
|
||||
{
|
||||
"name": "Question 2",
|
||||
"type": "String",
|
||||
"length": 100,
|
||||
"mandatory": false
|
||||
}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceIdentifier": "extra_client_details"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="datatables_list" name="datatables_list"
|
||||
class="old-syle-anchor"> </a>
|
||||
@@ -11267,25 +11504,191 @@ No Request Body:
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/datatables
|
||||
</code>
|
||||
<code class="method-response">
|
||||
<code class="method-response">
|
||||
[
|
||||
{
|
||||
"applicationTableName": "m_client",
|
||||
"registeredTableName": "Additional Client Fields Data"
|
||||
},
|
||||
{
|
||||
"applicationTableName": "m_client",
|
||||
"registeredTableName": "Extra Family Details Data"
|
||||
},
|
||||
{
|
||||
"applicationTableName": "m_loan",
|
||||
"registeredTableName": "Additional Loan Fields Data"
|
||||
}
|
||||
]
|
||||
{
|
||||
"applicationTableName": "m_client",
|
||||
"registeredTableName": "extra_client_details",
|
||||
"columnHeaderData": [
|
||||
{
|
||||
"columnName": "client_id",
|
||||
"columnType": "bigint",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "INTEGER",
|
||||
"isColumnNullable": false,
|
||||
"isColumnPrimaryKey": true,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Gender_cd_Question",
|
||||
"columnType": "int",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "INTEGER",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Some Decimal",
|
||||
"columnType": "decimal",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "DECIMAL",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Birth Date",
|
||||
"columnType": "date",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "DATE",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="datatables_getTable" name="datatables_getTable"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Retrieve Data Table Details</h4>
|
||||
<p>Lists a registered data table details and the Mifos X Core
|
||||
application table they are registered to.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/datatables/{datatable}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"applicationTableName": "m_client",
|
||||
"registeredTableName": "extra_client_details",
|
||||
"columnHeaderData": [
|
||||
{
|
||||
"columnName": "client_id",
|
||||
"columnType": "bigint",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "INTEGER",
|
||||
"isColumnNullable": false,
|
||||
"isColumnPrimaryKey": true,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Gender_cd_Question",
|
||||
"columnType": "int",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "INTEGER",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Some Decimal",
|
||||
"columnType": "decimal",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "DECIMAL",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
},
|
||||
{
|
||||
"columnName": "Birth Date",
|
||||
"columnType": "date",
|
||||
"columnLength": 0,
|
||||
"columnDisplayType": "DATE",
|
||||
"isColumnNullable": true,
|
||||
"isColumnPrimaryKey": false,
|
||||
"columnValues": []
|
||||
}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="datatables_updateTable" name="datatables_updateTable"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Update Data Table</h4>
|
||||
<p>Modifies fields of a data table. If the apptableName parameter is passed,
|
||||
data table is deregistered and registered with the new application table.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
PUT https://DomainName/api/v1/datatables/{datatables}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
PUT https://DomainName/api/v1/datatables/extra_client_details
|
||||
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"apptableName": "m_client",
|
||||
"columns": [
|
||||
{
|
||||
"name": "Gender",
|
||||
"type": "Dropdown",
|
||||
"code": "Gender"
|
||||
},
|
||||
{
|
||||
"name": "Some Decimal",
|
||||
"type": "Decimal",
|
||||
"mandatory": true
|
||||
},
|
||||
{
|
||||
"name": "Birth Date",
|
||||
"type": "Date"
|
||||
},
|
||||
{
|
||||
"name": "Question 2",
|
||||
"type": "String",
|
||||
"length": 100,
|
||||
"mandatory": false
|
||||
}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceIdentifier": "extra_client_details"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="datatables_deleteTable" name="datatables_deleteTable"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Delete Data Table</h4>
|
||||
<p>Deletes a data table and unregisters it from the Mifos X Core
|
||||
application table.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
DELETE https://DomainName/api/v1/datatables/{datatables}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
DELETE https://DomainName/api/v1/datatables/extra_client_details
|
||||
|
||||
Content-Type: application/json
|
||||
No Request Body
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceIdentifier": "extra_client_details"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a id="datatables_register" name="datatables_register"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
@@ -12720,8 +13123,7 @@ GET https://DomainName/api/v1/runreports/{reportName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="authenticate_request" name="authenticate_request"
|
||||
class="old-syle-anchor"> </a>
|
||||
<a id="authenticate_request" name="authenticate_request" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Verify authentication</h4>
|
||||
@@ -12736,24 +13138,60 @@ POST authentication?username=mifos&password=password
|
||||
Content-Type: application/json
|
||||
No Request Body
|
||||
</code>
|
||||
<p>Example response of autentication for user that is not linked with any staff.</p>
|
||||
<code class="method-response">
|
||||
{
|
||||
"username": "mifos",
|
||||
"userId": 1,
|
||||
"base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
|
||||
"authenticated": true,
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Super user",
|
||||
"description": "This role provides all application permissions."
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"ALL_FUNCTIONS"
|
||||
]
|
||||
"username": "mifos",
|
||||
"userId": 1,
|
||||
"base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
|
||||
"authenticated": true,
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office",
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Super user",
|
||||
"description": "This role provides all application permissions."
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"ALL_FUNCTIONS"
|
||||
]
|
||||
}
|
||||
</code>
|
||||
<p>Example response of autentication for user that is linked with a staff member and role.</p>
|
||||
<code class="method-response">
|
||||
{
|
||||
"username": "mifos",
|
||||
"userId": 1,
|
||||
"base64EncodedAuthenticationKey": "bWlmb3M6cGFzc3dvcmQ=",
|
||||
"authenticated": true,
|
||||
"officeId": 1,
|
||||
"officeName": "Head Office",
|
||||
"staffId": 1,
|
||||
"staffDisplayName": "Director, Program",
|
||||
"organisationalRole": {
|
||||
"id": 100,
|
||||
"code": "staffOrganisationalRoleType.programDirector",
|
||||
"value": "Program Director"
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Super user",
|
||||
"description": "This role provides all application permissions."
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"ALL_FUNCTIONS"
|
||||
]
|
||||
}
|
||||
</code>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code class="method-request">
|
||||
POST authentication?username=mifos&password=fail
|
||||
Content-Type: application/json
|
||||
@@ -14534,6 +14972,68 @@ Request Body:
|
||||
{
|
||||
"officeId":1,
|
||||
"transactionId":"RS9MCISID4WK1ZM"
|
||||
}
|
||||
</code>
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/journalentries
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST journalentries
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"locale": "en",
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"officeId": "1",
|
||||
"transactionDate": "23 May 2013",
|
||||
"referenceNumber": "547",
|
||||
"comments": "asdf",
|
||||
"accountingRule": "2",
|
||||
"credits":[{"glAccountId":1,
|
||||
"amount":5000},
|
||||
{"glAccountId":2,
|
||||
"amount":5000}
|
||||
],
|
||||
"debits":[{"glAccountId":3,
|
||||
"amount":5000},
|
||||
{"glAccountId":4,
|
||||
"amount":5000}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId":1,
|
||||
"transactionId":"RS9MCISID4WK1ZM"
|
||||
}
|
||||
</code>
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/journalentries
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST journalentries
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"locale": "en",
|
||||
"dateFormat": "dd MMMM yyyy",
|
||||
"officeId": "1",
|
||||
"transactionDate": "23 May 2013",
|
||||
"referenceNumber": "547",
|
||||
"comments": "asdf",
|
||||
"accountingRule": "3",
|
||||
"credits":[{"glAccountId":1,
|
||||
"amount":5000},
|
||||
{"glAccountId":2,
|
||||
"amount":5000}
|
||||
],
|
||||
"amount": "10000"
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"officeId":1,
|
||||
"transactionId":"RS9MCISID4WK1ZM"
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
@@ -15286,11 +15786,19 @@ GET https://DomainName/api/v1/search?query=000000111
|
||||
<tr class="alt">
|
||||
<td><a href="#datatables">Data Table</a></td>
|
||||
<td>datatables</td>
|
||||
<td></td>
|
||||
<td><a href="#datatables_createTable">Create Data Table</a></td>
|
||||
<td><a href="#datatables_list">List Data Tables</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>datatables/{datatable}</td>
|
||||
<td></td>
|
||||
<td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
|
||||
<td><a href="#datatables_updateTable">Update Data Table</a></td>
|
||||
<td><a href="#datatables_deleteTable">Delete Data Table</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>datatables/register/{datatable}/{apptable}</td>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
create table `acc_rule_tags` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`acc_rule_id` BIGINT(20) NOT NULL,
|
||||
`tag_id` INT(11) NOT NULL,
|
||||
`acc_type_enum` SMALLINT(5) NOT NULL,
|
||||
primary key(`id`),
|
||||
INDEX `FK_acc_accounting_rule_id` (`acc_rule_id`),
|
||||
INDEX `FK_m_code_value_id` (`tag_id`),
|
||||
CONSTRAINT `FK_acc_accounting_rule_id` FOREIGN KEY (`acc_rule_id`) REFERENCES `acc_accounting_rule` (`id`),
|
||||
CONSTRAINT `FK_m_code_value_id` FOREIGN KEY (`tag_id`) REFERENCES `m_code_value` (`id`)
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES
|
||||
('configuration', 'CREATE_DATATABLE', 'DATATABLE', 'CREATE', 0),
|
||||
('configuration', 'CREATE_DATATABLE_CHECKER', 'DATATABLE', 'CREATE', 0),
|
||||
('configuration', 'UPDATE_DATATABLE', 'DATATABLE', 'UPDATE', 0),
|
||||
('configuration', 'UPDATE_DATATABLE_CHECKER', 'DATATABLE', 'UPDATE', 0),
|
||||
('configuration', 'DELETE_DATATABLE', 'DATATABLE', 'DELETE', 0),
|
||||
('configuration', 'DELETE_DATATABLE_CHECKER', 'DATATABLE', 'DELETE', 0);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE `m_appuser`
|
||||
ADD COLUMN `staff_id` BIGINT(20) NULL DEFAULT NULL AFTER `office_id` ;
|
||||
|
||||
ALTER TABLE m_appuser
|
||||
ADD CONSTRAINT `fk_m_appuser_002`
|
||||
FOREIGN KEY (`staff_id`)
|
||||
REFERENCES m_staff (`id`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION
|
||||
,ADD INDEX `fk_m_appuser_002x` (`staff_id` ASC);
|
||||
|
||||
ALTER TABLE `m_staff`
|
||||
ADD COLUMN `organisational_role_enum` SMALLINT NULL DEFAULT NULL AFTER `external_id`,
|
||||
ADD COLUMN `organisational_role_parent_staff_id` BIGINT(20) NULL DEFAULT NULL AFTER `organisational_role_enum`;
|
||||
@@ -0,0 +1,78 @@
|
||||
INSERT INTO c_configuration (name,enabled) VALUES ('amazon-S3',0);
|
||||
|
||||
/*New table for storing details of external services used*/
|
||||
CREATE TABLE `c_external_service` (
|
||||
`name` VARCHAR(150) NOT NULL,
|
||||
`value` VARCHAR(250) NULL DEFAULT NULL,
|
||||
UNIQUE INDEX `name` (`name`)
|
||||
)
|
||||
COLLATE='utf8_general_ci'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO c_external_service (name) VALUES ('s3_bucket_name');
|
||||
INSERT INTO c_external_service (name) VALUES ('s3_access_key');
|
||||
INSERT INTO c_external_service (name) VALUES ('s3_secret_key');
|
||||
|
||||
|
||||
|
||||
/*Image tables stores details of all images*/
|
||||
CREATE TABLE IF NOT EXISTS `m_image`(
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`location` varchar(500),
|
||||
`storage_type_enum` SMALLINT(5),
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
COLLATE='utf8_general_ci'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
/*Client points to image table*/
|
||||
ALTER TABLE `m_client`
|
||||
ADD COLUMN `image_id` BIGINT(20) NULL DEFAULT NULL AFTER `display_name`,
|
||||
ADD CONSTRAINT `FK_m_client_m_image` FOREIGN KEY (`image_id`) REFERENCES `m_image` (`id`);
|
||||
|
||||
/*Move existing image locations to new table*/
|
||||
drop procedure if exists migrate_customer_image_data;
|
||||
|
||||
delimiter #
|
||||
create procedure migrate_customer_image_data()
|
||||
begin
|
||||
DECLARE v_counter INT DEFAULT 0;
|
||||
DECLARE num_of_clients INT DEFAULT 0;
|
||||
DECLARE curr_image INT DEFAULT 0;
|
||||
DECLARE prev_image INT DEFAULT 0;
|
||||
SELECT COUNT(*) FROM m_client INTO num_of_clients;
|
||||
SET @curr_client := 0;
|
||||
while v_counter < num_of_clients do
|
||||
SET @s = CONCAT('INSERT INTO m_image(`location`,`storage_type_enum`) select image_key, 1 FROM m_client where image_key is not null LIMIT ', v_counter , ', ', 1);
|
||||
PREPARE stmt1 FROM @s;
|
||||
EXECUTE stmt1;
|
||||
DEALLOCATE PREPARE stmt1;
|
||||
|
||||
select ifnull(max(`id`),0) from m_image INTO curr_image;
|
||||
SET @z = CONCAT('select id INTO @curr_client FROM m_client where image_key is not null limit ', v_counter , ', ', 1);
|
||||
PREPARE stmt2 FROM @z;
|
||||
EXECUTE stmt2;
|
||||
|
||||
IF (prev_image != curr_image) THEN UPDATE m_client set image_id = curr_image where id=@curr_client;
|
||||
END IF;
|
||||
|
||||
set prev_image=curr_image;
|
||||
set v_counter=v_counter+1;
|
||||
end while;
|
||||
delete from m_image where `location` is null;
|
||||
end#
|
||||
|
||||
delimiter ;
|
||||
call migrate_customer_image_data();
|
||||
|
||||
/*Now drop the procedure*/
|
||||
drop procedure if exists migrate_customer_image_data;
|
||||
|
||||
/*Delete image key from client*/
|
||||
ALTER TABLE `m_client` DROP COLUMN `image_key`;
|
||||
|
||||
|
||||
/*Add storage type for m_document table and update existing documents to file storage*/
|
||||
ALTER TABLE m_document ADD COLUMN storage_type_enum SMALLINT(5);
|
||||
UPDATE m_document set storage_type_enum=1;
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE `m_loan_charge_paid_by` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`loan_transaction_id` BIGINT(20) NOT NULL,
|
||||
`loan_charge_id` BIGINT(20) NOT NULL,
|
||||
`amount` DECIMAL(19,6) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `FK__m_loan_transaction` (`loan_transaction_id`),
|
||||
INDEX `FK__m_loan_charge` (`loan_charge_id`),
|
||||
CONSTRAINT `FK__m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
|
||||
CONSTRAINT `FK__m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
|
||||
)
|
||||
COLLATE='utf8_general_ci'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE `m_product_loan`
|
||||
ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `max_number_of_repayments`,
|
||||
ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
|
||||
ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
|
||||
|
||||
ALTER TABLE `m_loan`
|
||||
ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `number_of_repayments`,
|
||||
ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
|
||||
ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
ADD COLUMN `obligations_met_on_date` DATE NULL DEFAULT NULL AFTER `completed_derived`;
|
||||
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
CHANGE COLUMN `interest_waived_derived` `interest_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `interest_writtenoff_derived` ;
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE `acc_accounting_rule`
|
||||
ADD COLUMN `allow_multiple_debits` TINYINT(1) NOT NULL DEFAULT '0' AFTER `debit_account_id`,
|
||||
ADD COLUMN `allow_multiple_credits` TINYINT(1) NOT NULL DEFAULT '0' AFTER `credit_account_id`;
|
||||
|
||||
ALTER TABLE acc_rule_tags
|
||||
ADD UNIQUE KEY `UNIQUE_ACCOUNT_RULE_TAGS` (`acc_rule_id`,`tag_id`,`acc_type_enum`);
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
ADD COLUMN `total_paid_in_advance_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `penalty_charges_waived_derived`,
|
||||
ADD COLUMN `total_paid_late_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_paid_in_advance_derived`;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `acc_product_mapping`
|
||||
ADD COLUMN `charge_id` BIGINT(20) NULL DEFAULT NULL AFTER `payment_type`,
|
||||
ADD CONSTRAINT `FK_acc_product_mapping_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE `ref_loan_transaction_processing_strategy`
|
||||
DROP COLUMN `lastmodified_date` ,
|
||||
DROP COLUMN `lastmodifiedby_id` ,
|
||||
DROP COLUMN `created_date` ,
|
||||
DROP COLUMN `createdby_id` ;
|
||||
|
||||
|
||||
INSERT INTO `ref_loan_transaction_processing_strategy` (`id`, `code`, `name`) VALUES
|
||||
(5,'principal-interest-penalties-fees-order-strategy', 'Principal Interest Penalties Fees Order');
|
||||
|
||||
INSERT INTO `ref_loan_transaction_processing_strategy` (`id`,`code`, `name`)
|
||||
VALUES (6,'interest-principal-penalties-fees-order-strategy', 'Interest Principal Penalties Fees Order');
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `m_loan`
|
||||
ADD COLUMN `total_overpaid_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_outstanding_derived`;
|
||||
@@ -160,7 +160,8 @@ dependencies {
|
||||
[group: 'com.lowagie', name: 'itext-rtf', version: '2.1.7'],
|
||||
[group: 'org.mnode.ical4j', name: 'ical4j', version: '1.0.4'],
|
||||
[group: 'com.googlecode.flyway', name: 'flyway-maven-plugin', version: '2.1.1'],
|
||||
[group: 'org.quartz-scheduler', name: 'quartz', version: '2.1.7']
|
||||
[group: 'org.quartz-scheduler', name: 'quartz', version: '2.1.7'],
|
||||
[group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1']
|
||||
)
|
||||
|
||||
testCompile 'junit:junit:4.11',
|
||||
|
||||
@@ -1 +1 @@
|
||||
releaseVersion=1.2.1.RELEASE
|
||||
releaseVersion=1.3.0.RELEASE
|
||||
+30
-15
@@ -9,10 +9,10 @@ import java.util.HashMap;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mifosplatform.integrationtests.common.ClientHelper;
|
||||
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 org.mifosplatform.integrationtests.common.Utils;
|
||||
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||
import com.jayway.restassured.builder.ResponseSpecBuilder;
|
||||
@@ -30,6 +30,7 @@ public class ClientLoanIntegrationTest {
|
||||
private ResponseSpecification responseSpec;
|
||||
private RequestSpecification requestSpec;
|
||||
private LoanTransactionHelper loanTransactionHelper;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Utils.initializeRESTAssured();
|
||||
@@ -40,7 +41,7 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void checkClientLoanCreateAndDisburseFlow() {
|
||||
loanTransactionHelper = new LoanTransactionHelper(requestSpec,responseSpec);
|
||||
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||
|
||||
Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(requestSpec, responseSpec, clientID);
|
||||
@@ -53,21 +54,35 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
private Integer createLoanProduct() {
|
||||
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||
String loanProductJSON = new LoanProductTestBuilder().withPrincipal("12,000.00").withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
|
||||
.withinterestRatePerPeriod("1").build();
|
||||
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 clientID, 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").build(clientID.toString(), loanProductID.toString());
|
||||
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") //
|
||||
.build(clientID.toString(), loanProductID.toString());
|
||||
return loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
}
|
||||
|
||||
@@ -81,17 +96,17 @@ public class ClientLoanIntegrationTest {
|
||||
|
||||
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.72"), loanSchedule.get(2).get("principalDue"));
|
||||
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.11"), loanSchedule.get(3).get("principalDue"));
|
||||
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.68"), loanSchedule.get(4).get("principalDue"));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -31,7 +31,7 @@ public class LoanApplicationTestBuilder {
|
||||
private final String transactionProcessingID = MIFOS_STANDARD_STRATEGY;
|
||||
private String expectedDisbursmentDate = "";
|
||||
private String submittedOnDate = "";
|
||||
private String loanType = "individual"; //default it to individual
|
||||
private String loanType = "individual";
|
||||
|
||||
public String build(final String clientID, final String loanProductId) {
|
||||
|
||||
@@ -42,7 +42,7 @@ public class LoanApplicationTestBuilder {
|
||||
map.put("productId", loanProductId);
|
||||
map.put("principal", principal);
|
||||
map.put("loanTermFrequency", loanTermFrequency);
|
||||
map.put("loanTermFrequencyType", loanTermFrequencyType); // FIXME - Should this be using
|
||||
map.put("loanTermFrequencyType", loanTermFrequencyType);
|
||||
map.put("numberOfRepayments", numberOfRepayment);
|
||||
map.put("repaymentEvery", repaymentPeriod);
|
||||
map.put("repaymentFrequencyType", repaymentFrequencyType);
|
||||
@@ -53,7 +53,7 @@ public class LoanApplicationTestBuilder {
|
||||
map.put("transactionProcessingStrategyId", transactionProcessingID);
|
||||
map.put("expectedDisbursementDate", expectedDisbursmentDate);
|
||||
map.put("submittedOnDate", submittedOnDate);
|
||||
map.put("loanType", "individual");
|
||||
map.put("loanType", loanType);
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ public class AccountingConstants {
|
||||
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE(
|
||||
"receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE(
|
||||
"receivablePenaltyAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE(
|
||||
"paymentTypeId");
|
||||
"paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING(
|
||||
"penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
+32
-20
@@ -50,9 +50,10 @@ import org.springframework.stereotype.Component;
|
||||
public class GLAccountsApiResource {
|
||||
|
||||
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "name", "parentId", "glCode",
|
||||
"disabled", "manualEntriesAllowed", "type", "usage", "description", "assetHeaderAccountOptions", "liabilityHeaderAccountOptions",
|
||||
"equityHeaderAccountOptions", "incomeHeaderAccountOptions", "expenseHeaderAccountOptions", "nameDecorated", "tagId",
|
||||
"allowedAssetsTagOptions", "allowedLiabilitiesTagOptions", "allowedEquityTagOptions", "allowedIncomeTagOptions", "allowedExpensesTagOptions"));
|
||||
"disabled", "manualEntriesAllowed", "type", "usage", "description", "assetHeaderAccountOptions",
|
||||
"liabilityHeaderAccountOptions", "equityHeaderAccountOptions", "incomeHeaderAccountOptions", "expenseHeaderAccountOptions",
|
||||
"nameDecorated", "tagId", "allowedAssetsTagOptions", "allowedLiabilitiesTagOptions", "allowedEquityTagOptions",
|
||||
"allowedIncomeTagOptions", "allowedExpensesTagOptions", "creditAccounts", "debitAccounts"));
|
||||
|
||||
private final String resourceNameForPermission = "GLACCOUNT";
|
||||
|
||||
@@ -63,12 +64,13 @@ public class GLAccountsApiResource {
|
||||
private final PlatformSecurityContext context;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
||||
|
||||
|
||||
@Autowired
|
||||
public GLAccountsApiResource(final PlatformSecurityContext context, final GLAccountReadPlatformService glAccountReadPlatformService,
|
||||
final DefaultToApiJsonSerializer<GLAccountData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
|
||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
|
||||
final AccountingDropdownReadPlatformService dropdownReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService) {
|
||||
final AccountingDropdownReadPlatformService dropdownReadPlatformService,
|
||||
final CodeValueReadPlatformService codeValueReadPlatformService) {
|
||||
this.context = context;
|
||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
||||
@@ -82,7 +84,7 @@ public class GLAccountsApiResource {
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveNewAccountDetails(@Context final UriInfo uriInfo, @QueryParam("type") final Integer type) {
|
||||
public String retrieveNewAccountDetails(@Context final UriInfo uriInfo, @QueryParam("type") final Integer type) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
|
||||
|
||||
@@ -167,22 +169,32 @@ public class GLAccountsApiResource {
|
||||
private GLAccountData handleTemplate(final GLAccountData glAccountData) {
|
||||
final List<EnumOptionData> accountTypeOptions = this.dropdownReadPlatformService.retrieveGLAccountTypeOptions();
|
||||
final List<EnumOptionData> usageOptions = this.dropdownReadPlatformService.retrieveGLAccountUsageOptions();
|
||||
final List<GLAccountData> assetHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService.retrieveAllEnabledHeaderGLAccounts(GLAccountType.ASSET));
|
||||
final List<GLAccountData> liabilityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService.retrieveAllEnabledHeaderGLAccounts(GLAccountType.LIABILITY));
|
||||
final List<GLAccountData> equityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService.retrieveAllEnabledHeaderGLAccounts(GLAccountType.EQUITY));
|
||||
final List<GLAccountData> incomeHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService.retrieveAllEnabledHeaderGLAccounts(GLAccountType.INCOME));
|
||||
final List<GLAccountData> expenseHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService.retrieveAllEnabledHeaderGLAccounts(GLAccountType.EXPENSE));
|
||||
final Collection<CodeValueData> allowedAssetsTagOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedLiabilitiesTagOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedEquityTagOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedIncomeTagOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.INCOME_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedExpensesTagOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME);
|
||||
|
||||
final List<GLAccountData> assetHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
|
||||
.retrieveAllEnabledHeaderGLAccounts(GLAccountType.ASSET));
|
||||
final List<GLAccountData> liabilityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
|
||||
.retrieveAllEnabledHeaderGLAccounts(GLAccountType.LIABILITY));
|
||||
final List<GLAccountData> equityHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
|
||||
.retrieveAllEnabledHeaderGLAccounts(GLAccountType.EQUITY));
|
||||
final List<GLAccountData> incomeHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
|
||||
.retrieveAllEnabledHeaderGLAccounts(GLAccountType.INCOME));
|
||||
final List<GLAccountData> expenseHeaderAccountOptions = defaultIfEmpty(this.glAccountReadPlatformService
|
||||
.retrieveAllEnabledHeaderGLAccounts(GLAccountType.EXPENSE));
|
||||
final Collection<CodeValueData> allowedAssetsTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedLiabilitiesTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedEquityTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedIncomeTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.INCOME_TAG_OPTION_CODE_NAME);
|
||||
final Collection<CodeValueData> allowedExpensesTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME);
|
||||
|
||||
return new GLAccountData(glAccountData, accountTypeOptions, usageOptions, assetHeaderAccountOptions, liabilityHeaderAccountOptions,
|
||||
equityHeaderAccountOptions, incomeHeaderAccountOptions, expenseHeaderAccountOptions, allowedAssetsTagOptions,
|
||||
equityHeaderAccountOptions, incomeHeaderAccountOptions, expenseHeaderAccountOptions, allowedAssetsTagOptions,
|
||||
allowedLiabilitiesTagOptions, allowedEquityTagOptions, allowedIncomeTagOptions, allowedExpensesTagOptions);
|
||||
}
|
||||
|
||||
|
||||
private List<GLAccountData> defaultIfEmpty(final List<GLAccountData> list) {
|
||||
List<GLAccountData> returnList = null;
|
||||
if (list != null && !list.isEmpty()) {
|
||||
@@ -190,4 +202,4 @@ public class GLAccountsApiResource {
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-25
@@ -71,17 +71,17 @@ public class GLAccountData {
|
||||
this.expenseHeaderAccountOptions = null;
|
||||
this.allowedAssetsTagOptions = null;
|
||||
this.allowedLiabilitiesTagOptions = null;
|
||||
this.allowedEquityTagOptions=null;
|
||||
this.allowedIncomeTagOptions=null;
|
||||
this.allowedExpensesTagOptions=null;
|
||||
this.allowedEquityTagOptions = null;
|
||||
this.allowedIncomeTagOptions = null;
|
||||
this.allowedExpensesTagOptions = null;
|
||||
}
|
||||
|
||||
public GLAccountData(final GLAccountData accountData, final List<EnumOptionData> accountTypeOptions,
|
||||
final List<EnumOptionData> usageOptions, final List<GLAccountData> assetHeaderAccountOptions,
|
||||
final List<GLAccountData> liabilityHeaderAccountOptions, final List<GLAccountData> equityHeaderAccountOptions,
|
||||
final List<GLAccountData> incomeHeaderAccountOptions, final List<GLAccountData> expenseHeaderAccountOptions,
|
||||
final Collection<CodeValueData> allowedAssetsTagOptions, final Collection<CodeValueData> allowedLiabilitiesTagOptions,
|
||||
final Collection<CodeValueData> allowedEquityTagOptions, final Collection<CodeValueData> allowedIncomeTagOptions,
|
||||
final List<GLAccountData> incomeHeaderAccountOptions, final List<GLAccountData> expenseHeaderAccountOptions,
|
||||
final Collection<CodeValueData> allowedAssetsTagOptions, final Collection<CodeValueData> allowedLiabilitiesTagOptions,
|
||||
final Collection<CodeValueData> allowedEquityTagOptions, final Collection<CodeValueData> allowedIncomeTagOptions,
|
||||
final Collection<CodeValueData> allowedExpensesTagOptions) {
|
||||
this.id = accountData.id;
|
||||
this.name = accountData.name;
|
||||
@@ -116,19 +116,20 @@ public class GLAccountData {
|
||||
final boolean disabled = false;
|
||||
final boolean manualEntriesAllowed = true;
|
||||
final EnumOptionData type;
|
||||
if(glAccType != null && glAccType >= GLAccountType.getMinValue() && glAccType <= GLAccountType.getMaxValue())
|
||||
if (glAccType != null && glAccType >= GLAccountType.getMinValue() && glAccType <= GLAccountType.getMaxValue()) {
|
||||
type = AccountingEnumerations.gLAccountType(glAccType);
|
||||
else
|
||||
} else {
|
||||
type = AccountingEnumerations.gLAccountType(GLAccountType.ASSET);
|
||||
}
|
||||
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(GLAccountUsage.DETAIL);
|
||||
final String description = null;
|
||||
final String nameDecorated = null;
|
||||
final CodeValueData tagId = null;
|
||||
return new GLAccountData(id, name, parentId, glCode, disabled, manualEntriesAllowed, type, usage, description, nameDecorated, tagId);
|
||||
}
|
||||
|
||||
|
||||
public GLAccountData(final Long id, final String name, final String glCode) {
|
||||
this.id = id;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.parentId = null;
|
||||
this.glCode = glCode;
|
||||
@@ -148,23 +149,21 @@ public class GLAccountData {
|
||||
this.expenseHeaderAccountOptions = null;
|
||||
this.allowedAssetsTagOptions = null;
|
||||
this.allowedLiabilitiesTagOptions = null;
|
||||
this.allowedEquityTagOptions=null;
|
||||
this.allowedIncomeTagOptions=null;
|
||||
this.allowedExpensesTagOptions=null;
|
||||
}
|
||||
this.allowedEquityTagOptions = null;
|
||||
this.allowedIncomeTagOptions = null;
|
||||
this.allowedExpensesTagOptions = null;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getGlCode() {
|
||||
return this.glCode;
|
||||
}
|
||||
|
||||
|
||||
public String getGlCode() {
|
||||
return this.glCode;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.accounting.glaccount.data;
|
||||
|
||||
public class GLAccountDataForLookup {
|
||||
|
||||
private final Long id;
|
||||
@SuppressWarnings("unused")
|
||||
private final String name;
|
||||
@SuppressWarnings("unused")
|
||||
private final String glCode;
|
||||
|
||||
public GLAccountDataForLookup(final Long id, final String name, final String glCode) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.glCode = glCode;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-3
@@ -1,11 +1,17 @@
|
||||
/**
|
||||
* 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.accounting.glaccount.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class InvalidParentGLAccountHeadException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public InvalidParentGLAccountHeadException(Long glAccountId, Long parentId) {
|
||||
super("error.msg.glaccount.id.and.parentid.must.not.same", "parentId:"+parentId+", id"+glAccountId+" should not be same", glAccountId, parentId);
|
||||
}
|
||||
public InvalidParentGLAccountHeadException(final Long glAccountId, final Long parentId) {
|
||||
super("error.msg.glaccount.id.and.parentid.must.not.same", "parentId:" + parentId + ", id" + glAccountId + " should not be same",
|
||||
glAccountId, parentId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -8,6 +8,7 @@ package org.mifosplatform.accounting.glaccount.service;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccountType;
|
||||
|
||||
public interface GLAccountReadPlatformService {
|
||||
@@ -24,4 +25,6 @@ public interface GLAccountReadPlatformService {
|
||||
List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(GLAccountType accountType);
|
||||
|
||||
GLAccountData retrieveNewGLAccountDetails(final Integer type);
|
||||
|
||||
List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType);
|
||||
}
|
||||
+25
-1
@@ -13,6 +13,7 @@ import java.util.List;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.accounting.common.AccountingEnumerations;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccountType;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccountUsage;
|
||||
import org.mifosplatform.accounting.glaccount.exception.GLAccountInvalidClassificationException;
|
||||
@@ -65,7 +66,7 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
||||
final String nameDecorated = rs.getString("nameDecorated");
|
||||
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
|
||||
final String codeValue = rs.getString("codeValue");
|
||||
CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
||||
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
||||
return new GLAccountData(id, name, parentId, glCode, disabled, manualEntriesAllowed, accountType, usage, description,
|
||||
nameDecorated, tagId);
|
||||
}
|
||||
@@ -202,4 +203,27 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
||||
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
|
||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
|
||||
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
|
||||
final String sql = "Select " + mapper.schema() + " where rule.id=? and tags.acc_type_enum=?";
|
||||
return this.jdbcTemplate.query(sql, mapper, new Object[] { ruleId, transactionType });
|
||||
}
|
||||
|
||||
private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> {
|
||||
|
||||
public String schema() {
|
||||
return " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountDataForLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
final Long id = JdbcSupport.getLong(rs, "id");
|
||||
final String name = rs.getString("name");
|
||||
final String glCode = rs.getString("glCode");
|
||||
return new GLAccountDataForLookup(id, name, glCode);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+35
-39
@@ -30,8 +30,8 @@ public class JournalEntryCommand {
|
||||
private final SingleDebitOrCreditEntryCommand[] debits;
|
||||
|
||||
public JournalEntryCommand(final Long officeId, final LocalDate transactionDate, final String comments,
|
||||
final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits,
|
||||
final String referenceNumber, final Long accountingRuleId, final BigDecimal amount) {
|
||||
final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits, final String referenceNumber,
|
||||
final Long accountingRuleId, final BigDecimal amount) {
|
||||
this.officeId = officeId;
|
||||
this.transactionDate = transactionDate;
|
||||
this.comments = comments;
|
||||
@@ -53,46 +53,42 @@ public class JournalEntryCommand {
|
||||
baseDataValidator.reset().parameter("officeId").value(this.officeId).notNull().integerGreaterThanZero();
|
||||
|
||||
baseDataValidator.reset().parameter("comments").value(this.comments).ignoreIfNull().notExceedingLengthOf(500);
|
||||
|
||||
|
||||
baseDataValidator.reset().parameter("referenceNumber").value(this.referenceNumber).ignoreIfNull().notExceedingLengthOf(100);
|
||||
|
||||
|
||||
baseDataValidator.reset().parameter("accountingRule").value(this.accountingRuleId).ignoreIfNull().longGreaterThanZero();
|
||||
|
||||
if (this.accountingRuleId == null) {
|
||||
|
||||
baseDataValidator.reset().parameter("credits").value(this.credits).notNull();
|
||||
|
||||
baseDataValidator.reset().parameter("debits").value(this.debits).notNull();
|
||||
|
||||
// validation for credit array elements
|
||||
if (this.credits != null) {
|
||||
if (this.credits.length == 0) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
|
||||
} else {
|
||||
int i = 0;
|
||||
for (final SingleDebitOrCreditEntryCommand credit : this.credits) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", i, credit);
|
||||
i++;
|
||||
}
|
||||
|
||||
baseDataValidator.reset().parameter("credits").value(this.credits).ignoreIfNull();
|
||||
|
||||
baseDataValidator.reset().parameter("debits").value(this.debits).ignoreIfNull();
|
||||
|
||||
// validation for credit array elements
|
||||
if (this.credits != null) {
|
||||
if (this.credits.length == 0) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
|
||||
} else {
|
||||
int i = 0;
|
||||
for (final SingleDebitOrCreditEntryCommand credit : this.credits) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", i, credit);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// validation for debit array elements
|
||||
if (this.debits != null) {
|
||||
if (this.debits.length == 0) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
|
||||
} else {
|
||||
int i = 0;
|
||||
for (final SingleDebitOrCreditEntryCommand debit : this.debits) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "debits", i, debit);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
baseDataValidator.reset().parameter("amount").value(this.amount).notNull().zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
// validation for debit array elements
|
||||
if (this.debits != null) {
|
||||
if (this.debits.length == 0) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "credits", 0, new SingleDebitOrCreditEntryCommand(null, null, null, null));
|
||||
} else {
|
||||
int i = 0;
|
||||
for (final SingleDebitOrCreditEntryCommand debit : this.debits) {
|
||||
validateSingleDebitOrCredit(baseDataValidator, "debits", i, debit);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
baseDataValidator.reset().parameter("amount").value(this.amount).ignoreIfNull().zeroOrPositiveAmount();
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
@@ -134,8 +130,8 @@ public class JournalEntryCommand {
|
||||
return this.referenceNumber;
|
||||
}
|
||||
|
||||
public Long getAccountingRuleId() {
|
||||
return this.accountingRuleId;
|
||||
}
|
||||
public Long getAccountingRuleId() {
|
||||
return this.accountingRuleId;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
package org.mifosplatform.accounting.journalentry.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ChargePaymentDTO {
|
||||
|
||||
private final Long chargeId;
|
||||
private final BigDecimal amount;
|
||||
private final Long loanChargeId;
|
||||
|
||||
public ChargePaymentDTO(Long chargeId, Long loanChargeId, BigDecimal amount) {
|
||||
this.chargeId = chargeId;
|
||||
this.amount = amount;
|
||||
this.loanChargeId = loanChargeId;
|
||||
}
|
||||
|
||||
public Long getChargeId() {
|
||||
return this.chargeId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Long getLoanChargeId() {
|
||||
return this.loanChargeId;
|
||||
}
|
||||
|
||||
}
|
||||
+17
-1
@@ -7,6 +7,7 @@ package org.mifosplatform.accounting.journalentry.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
|
||||
@@ -28,9 +29,14 @@ public class LoanTransactionDTO {
|
||||
/*** Boolean values determines if the transaction is reversed ***/
|
||||
private final boolean reversed;
|
||||
|
||||
/** Breakdowns of fees and penalties this Transaction pays **/
|
||||
private final List<ChargePaymentDTO> penaltyPayments;
|
||||
private final List<ChargePaymentDTO> feePayments;
|
||||
|
||||
public LoanTransactionDTO(final Long paymentTypeId, final String transactionId, final Date transactionDate,
|
||||
final LoanTransactionEnumData transactionType, final BigDecimal amount, final BigDecimal principal, final BigDecimal interest,
|
||||
final BigDecimal fees, final BigDecimal penalties, final boolean reversed) {
|
||||
final BigDecimal fees, final BigDecimal penalties, final boolean reversed, final List<ChargePaymentDTO> feePayments,
|
||||
final List<ChargePaymentDTO> penaltyPayments) {
|
||||
this.paymentTypeId = paymentTypeId;
|
||||
this.transactionId = transactionId;
|
||||
this.transactionDate = transactionDate;
|
||||
@@ -41,6 +47,8 @@ public class LoanTransactionDTO {
|
||||
this.penalties = penalties;
|
||||
this.reversed = reversed;
|
||||
this.transactionType = transactionType;
|
||||
this.feePayments = feePayments;
|
||||
this.penaltyPayments = penaltyPayments;
|
||||
}
|
||||
|
||||
public String getTransactionId() {
|
||||
@@ -83,4 +91,12 @@ public class LoanTransactionDTO {
|
||||
return this.paymentTypeId;
|
||||
}
|
||||
|
||||
public List<ChargePaymentDTO> getPenaltyPayments() {
|
||||
return this.penaltyPayments;
|
||||
}
|
||||
|
||||
public List<ChargePaymentDTO> getFeePayments() {
|
||||
return this.feePayments;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -73,7 +73,7 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "entity_id")
|
||||
private Long entityId;
|
||||
|
||||
|
||||
@Column(name = "ref_num")
|
||||
private String referenceNumber;
|
||||
|
||||
@@ -104,7 +104,7 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
|
||||
this.entityType = entityType;
|
||||
this.entityId = entityId;
|
||||
this.referenceNumber = referenceNumber;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public boolean isDebitEntry() {
|
||||
@@ -139,8 +139,8 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
|
||||
this.reversed = reversed;
|
||||
}
|
||||
|
||||
public String getReferenceNumber() {
|
||||
return this.referenceNumber;
|
||||
}
|
||||
public String getReferenceNumber() {
|
||||
return this.referenceNumber;
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -45,4 +45,12 @@ public enum JournalEntryType {
|
||||
return name().toString();
|
||||
}
|
||||
|
||||
public boolean isDebitType() {
|
||||
return this.value.equals(JournalEntryType.DEBIT.getValue());
|
||||
}
|
||||
|
||||
public boolean isCreditType() {
|
||||
return this.value.equals(JournalEntryType.CREDIT.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,7 +16,7 @@ public class JournalEntryInvalidException extends AbstractPlatformDomainRuleExce
|
||||
|
||||
/*** enum of reasons for invalid Journal Entry **/
|
||||
public static enum GL_JOURNAL_ENTRY_INVALID_REASON {
|
||||
FUTURE_DATE, ACCOUNTING_CLOSED, NO_DEBITS_OR_CREDITS, DEBIT_CREDIT_SUM_MISMATCH, DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, GL_ACCOUNT_DISABLED, GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED;
|
||||
FUTURE_DATE, ACCOUNTING_CLOSED, NO_DEBITS_OR_CREDITS, DEBIT_CREDIT_SUM_MISMATCH_WITH_AMOUNT, DEBIT_CREDIT_SUM_MISMATCH, DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, GL_ACCOUNT_DISABLED, GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED;
|
||||
|
||||
public String errorMessage() {
|
||||
if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
|
||||
@@ -25,6 +25,8 @@ public class JournalEntryInvalidException extends AbstractPlatformDomainRuleExce
|
||||
return "Journal entry cannot be made prior to last account closing date for the branch";
|
||||
} else if (name().toString().equalsIgnoreCase("NO_DEBITS_OR_CREDITS")) {
|
||||
return "Journal Entry must have atleast one Debit and one Credit";
|
||||
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH_WITH_AMOUNT")) {
|
||||
return "Sum of All Debits OR Credits must equal the Amount for a Journal Entry";
|
||||
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH")) {
|
||||
return "Sum of All Debits must equal the sum of all Credits for a Journal Entry";
|
||||
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY")) {
|
||||
|
||||
+90
-1
@@ -8,6 +8,7 @@ package org.mifosplatform.accounting.journalentry.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -18,6 +19,7 @@ import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.SavingsDTO;
|
||||
@@ -30,6 +32,7 @@ import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidEx
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType;
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
import org.mifosplatform.organisation.office.domain.OfficeRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
@@ -77,8 +80,28 @@ public class AccountingProcessorHelper {
|
||||
final boolean reversed = (Boolean) map.get("reversed");
|
||||
final Long paymentTypeId = (Long) map.get("paymentTypeId");
|
||||
|
||||
List<ChargePaymentDTO> feePayments = new ArrayList<ChargePaymentDTO>();
|
||||
List<ChargePaymentDTO> penaltyPayments = new ArrayList<ChargePaymentDTO>();
|
||||
// extract charge payment details (if exists)
|
||||
if (map.containsKey("loanChargesPaid")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<Map<String, Object>> loanChargesPaidData = (List<Map<String, Object>>) map.get("loanChargesPaid");
|
||||
for (final Map<String, Object> loanChargePaid : loanChargesPaidData) {
|
||||
final Long chargeId = (Long) loanChargePaid.get("chargeId");
|
||||
final Long loanChargeId = (Long) loanChargePaid.get("chargeId");
|
||||
final boolean isPenalty = (Boolean) loanChargePaid.get("isPenalty");
|
||||
final BigDecimal chargeAmountPaid = (BigDecimal) loanChargePaid.get("amount");
|
||||
ChargePaymentDTO chargePaymentDTO = new ChargePaymentDTO(chargeId, loanChargeId, chargeAmountPaid);
|
||||
if (isPenalty) {
|
||||
penaltyPayments.add(chargePaymentDTO);
|
||||
} else {
|
||||
feePayments.add(chargePaymentDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final LoanTransactionDTO transaction = new LoanTransactionDTO(paymentTypeId, transactionId, transactionDate, transactionType,
|
||||
amount, principal, interest, fees, penalties, reversed);
|
||||
amount, principal, interest, fees, penalties, reversed, feePayments, penaltyPayments);
|
||||
|
||||
newLoanTransactions.add(transaction);
|
||||
|
||||
@@ -304,6 +327,48 @@ public class AccountingProcessorHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final int accountMappingTypeId,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate,
|
||||
final BigDecimal totalAmount, final Boolean isReversal, final List<ChargePaymentDTO> chargePaymentDTOs) {
|
||||
/***
|
||||
* Map to track each account and the net credit to be made for a
|
||||
* particular account
|
||||
***/
|
||||
Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<GLAccount, BigDecimal>();
|
||||
for (ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
|
||||
Long chargeId = chargePaymentDTO.getChargeId();
|
||||
GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
|
||||
BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount();
|
||||
|
||||
// adjust net credit amount if the account is already present in the
|
||||
// map
|
||||
if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
|
||||
BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
|
||||
chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
|
||||
}
|
||||
creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
|
||||
}
|
||||
|
||||
BigDecimal totalCreditedAmount = BigDecimal.ZERO;
|
||||
for (Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
|
||||
GLAccount account = entry.getKey();
|
||||
BigDecimal amount = entry.getValue();
|
||||
totalCreditedAmount = totalCreditedAmount.add(amount);
|
||||
if (isReversal) {
|
||||
createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
} else {
|
||||
createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Vishwas Temporary validation to be removed before moving to
|
||||
// release branch
|
||||
if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException(
|
||||
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
|
||||
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
|
||||
totalCreditedAmount, totalAmount); }
|
||||
}
|
||||
|
||||
private void createCreditJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId,
|
||||
final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
@@ -363,6 +428,30 @@ public class AccountingProcessorHelper {
|
||||
accountMapping = paymentChannelSpecificAccountMapping;
|
||||
}
|
||||
}
|
||||
|
||||
return accountMapping.getGlAccount();
|
||||
}
|
||||
|
||||
private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, final int accountMappingTypeId, final Long chargeId) {
|
||||
ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId,
|
||||
PortfolioProductType.LOAN.getValue(), accountMappingTypeId);
|
||||
/*****
|
||||
* Get more specific mappings for Charges and penalties (based on the
|
||||
* actual charge /penalty coupled with the loan product). Note the
|
||||
* income from fees and income from penalties placeholder ID would be
|
||||
* the same for both cash and accrual based accounts
|
||||
*****/
|
||||
|
||||
// Vishwas TODO: remove this condition as it should always be true
|
||||
if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()
|
||||
|| accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()) {
|
||||
ProductToGLAccountMapping chargeSpecificIncomeAccountMapping = this.accountMappingRepository
|
||||
.findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(loanProductId, PortfolioProductType.LOAN.getValue(),
|
||||
accountMappingTypeId, chargeId);
|
||||
if (chargeSpecificIncomeAccountMapping != null) {
|
||||
accountMapping = chargeSpecificIncomeAccountMapping;
|
||||
}
|
||||
}
|
||||
return accountMapping.getGlAccount();
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -134,14 +134,15 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES, loanProductId, paymentTypeId,
|
||||
loanId, transactionId, transactionDate, feesAmount, isReversal);
|
||||
helper.createCreditJournalEntryOrReversalForLoanCharges(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(),
|
||||
loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversal, loanTransactionDTO.getFeePayments());
|
||||
}
|
||||
|
||||
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal);
|
||||
helper.createCreditJournalEntryOrReversalForLoanCharges(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
|
||||
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal,
|
||||
loanTransactionDTO.getPenaltyPayments());
|
||||
}
|
||||
|
||||
/*** create a single debit entry (or reversal) for the entire amount **/
|
||||
|
||||
+130
-36
@@ -6,6 +6,7 @@
|
||||
package org.mifosplatform.accounting.journalentry.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -15,9 +16,11 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosureRepository;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository;
|
||||
import org.mifosplatform.accounting.glaccount.exception.GLAccountNotFoundException;
|
||||
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
|
||||
import org.mifosplatform.accounting.journalentry.api.JournalEntryJsonInputParams;
|
||||
import org.mifosplatform.accounting.journalentry.command.JournalEntryCommand;
|
||||
import org.mifosplatform.accounting.journalentry.command.SingleDebitOrCreditEntryCommand;
|
||||
@@ -61,6 +64,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
private final AccountingProcessorHelper helper;
|
||||
private final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final AccountingRuleRepository accountingRuleRepository;
|
||||
private final GLAccountReadPlatformService glAccountReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public JournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
|
||||
@@ -68,7 +72,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
final GLAccountRepository glAccountRepository, final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
final AccountingProcessorHelper accountingProcessorHelper, final AccountingRuleRepository accountingRuleRepository,
|
||||
final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory,
|
||||
final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory) {
|
||||
final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory,
|
||||
final GLAccountReadPlatformService glAccountReadPlatformService) {
|
||||
this.glClosureRepository = glClosureRepository;
|
||||
this.officeRepository = officeRepository;
|
||||
this.glJournalEntryRepository = glJournalEntryRepository;
|
||||
@@ -78,6 +83,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
this.accountingProcessorForSavingsFactory = accountingProcessorForSavingsFactory;
|
||||
this.helper = accountingProcessorHelper;
|
||||
this.accountingRuleRepository = accountingRuleRepository;
|
||||
this.glAccountReadPlatformService = glAccountReadPlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -99,32 +105,65 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
/** Set a transaction Id and save these Journal entries **/
|
||||
final Date transactionDate = command.DateValueOfParameterNamed(JournalEntryJsonInputParams.TRANSACTION_DATE.getValue());
|
||||
final String transactionId = generateTransactionId();
|
||||
final String referenceNumber = journalEntryCommand.getReferenceNumber();
|
||||
final String referenceNumber = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.REFERENCE_NUMBER.getValue());
|
||||
final boolean manualEntry = true;
|
||||
|
||||
if (accountRuleId != null) {
|
||||
|
||||
final Long accountingRuleId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue());
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed(JournalEntryJsonInputParams.AMOUNT.getValue());
|
||||
final AccountingRule accountingRule = this.accountingRuleRepository.findOne(accountingRuleId);
|
||||
if (accountingRule == null) { throw new AccountingRuleNotFoundException(accountingRuleId); }
|
||||
final GLAccount creditAccountHead = accountingRule.getAccountToCredit();
|
||||
validateGLAccountForTransaction(creditAccountHead);
|
||||
final GLAccount debitAccountHead = accountingRule.getAccountToDebit();
|
||||
validateGLAccountForTransaction(debitAccountHead);
|
||||
String description = accountingRule.getDescription();
|
||||
|
||||
String description = null;
|
||||
if (StringUtils.isNotBlank(journalEntryCommand.getComments())) {
|
||||
description = journalEntryCommand.getComments();
|
||||
}
|
||||
|
||||
JournalEntry creditEntry = JournalEntry.createNew(office, creditAccountHead, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.CREDIT, amount, description, null, null, referenceNumber);
|
||||
this.glJournalEntryRepository.saveAndFlush(creditEntry);
|
||||
final AccountingRule accountingRule = this.accountingRuleRepository.findOne(accountRuleId);
|
||||
if (accountingRule == null) { throw new AccountingRuleNotFoundException(accountRuleId); }
|
||||
|
||||
JournalEntry debitEntry = JournalEntry.createNew(office, debitAccountHead, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.DEBIT, amount, description, null, null, referenceNumber);
|
||||
this.glJournalEntryRepository.saveAndFlush(debitEntry);
|
||||
if (accountingRule.getAccountToCredit() == null) {
|
||||
if (journalEntryCommand.getCredits() == null) { throw new RuntimeException(
|
||||
"You have to pass atleast one value for credits"); }
|
||||
if (journalEntryCommand.getDebits() != null) {
|
||||
checkDebitOrCreditAccountsAreValid(accountingRule, journalEntryCommand.getCredits(),
|
||||
journalEntryCommand.getDebits());
|
||||
checkDebitAndCreditAmounts(journalEntryCommand.getCredits(), journalEntryCommand.getDebits());
|
||||
} else {
|
||||
checkDebitAndCreditAmounts(journalEntryCommand.getCredits(), amount);
|
||||
}
|
||||
|
||||
saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getCredits(),
|
||||
transactionId, JournalEntryType.CREDIT, referenceNumber);
|
||||
} else {
|
||||
if (journalEntryCommand.getCredits() != null) { throw new RuntimeException(
|
||||
"You can not pass credits for this rule please verify your rule"); }
|
||||
final GLAccount creditAccountHead = accountingRule.getAccountToCredit();
|
||||
validateGLAccountForTransaction(creditAccountHead);
|
||||
final JournalEntry creditEntry = JournalEntry.createNew(office, creditAccountHead, transactionId, manualEntry,
|
||||
transactionDate, JournalEntryType.CREDIT, amount, description, null, null, referenceNumber);
|
||||
this.glJournalEntryRepository.saveAndFlush(creditEntry);
|
||||
}
|
||||
|
||||
if (accountingRule.getAccountToDebit() == null) {
|
||||
if (journalEntryCommand.getDebits() == null) { throw new RuntimeException(
|
||||
"You have to pass atleast one parameter for debits"); }
|
||||
if (journalEntryCommand.getCredits() != null) {
|
||||
checkDebitOrCreditAccountsAreValid(accountingRule, journalEntryCommand.getCredits(),
|
||||
journalEntryCommand.getDebits());
|
||||
checkDebitAndCreditAmounts(journalEntryCommand.getCredits(), journalEntryCommand.getDebits());
|
||||
} else {
|
||||
checkDebitAndCreditAmounts(journalEntryCommand.getDebits(), amount);
|
||||
}
|
||||
|
||||
saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getDebits(),
|
||||
transactionId, JournalEntryType.DEBIT, referenceNumber);
|
||||
} else {
|
||||
if (journalEntryCommand.getDebits() != null) { throw new RuntimeException(
|
||||
"You can not pass debits for this rule please verify your rule"); }
|
||||
final GLAccount debitAccountHead = accountingRule.getAccountToDebit();
|
||||
validateGLAccountForTransaction(debitAccountHead);
|
||||
final JournalEntry debitEntry = JournalEntry.createNew(office, debitAccountHead, transactionId, manualEntry,
|
||||
transactionDate, JournalEntryType.DEBIT, amount, description, null, null, referenceNumber);
|
||||
this.glJournalEntryRepository.saveAndFlush(debitEntry);
|
||||
}
|
||||
} else {
|
||||
|
||||
saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getDebits(), transactionId,
|
||||
@@ -143,7 +182,76 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
}
|
||||
}
|
||||
|
||||
private void validateGLAccountForTransaction(GLAccount creditOrDebitAccountHead) {
|
||||
@SuppressWarnings("null")
|
||||
private void checkDebitOrCreditAccountsAreValid(final AccountingRule accountingRule, final SingleDebitOrCreditEntryCommand[] credits,
|
||||
final SingleDebitOrCreditEntryCommand[] debits) {
|
||||
// Validate the debit and credit arrays are appropriate accounts
|
||||
List<GLAccountDataForLookup> allowedCreditGLAccounts = new ArrayList<GLAccountDataForLookup>();
|
||||
List<GLAccountDataForLookup> allowedDebitGLAccounts = new ArrayList<GLAccountDataForLookup>();
|
||||
final SingleDebitOrCreditEntryCommand[] validCredits = new SingleDebitOrCreditEntryCommand[credits.length];
|
||||
final SingleDebitOrCreditEntryCommand[] validDebits = new SingleDebitOrCreditEntryCommand[debits.length];
|
||||
|
||||
if (credits != null && credits.length > 0) {
|
||||
allowedCreditGLAccounts = this.glAccountReadPlatformService.retrieveAccountsByTagId(accountingRule.getId(),
|
||||
JournalEntryType.CREDIT.getValue());
|
||||
for (final GLAccountDataForLookup accountDataForLookup : allowedCreditGLAccounts) {
|
||||
for (int i = 0; i < credits.length; i++) {
|
||||
final SingleDebitOrCreditEntryCommand credit = credits[i];
|
||||
if (credit.getGlAccountId().equals(accountDataForLookup.getId())) {
|
||||
validCredits[i] = credit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (credits.length != validCredits.length) { throw new RuntimeException("Invalid credits"); }
|
||||
}
|
||||
|
||||
if (debits != null && debits.length > 0) {
|
||||
allowedDebitGLAccounts = this.glAccountReadPlatformService.retrieveAccountsByTagId(accountingRule.getId(),
|
||||
JournalEntryType.DEBIT.getValue());
|
||||
for (final GLAccountDataForLookup accountDataForLookup : allowedDebitGLAccounts) {
|
||||
for (int i = 0; i < debits.length; i++) {
|
||||
final SingleDebitOrCreditEntryCommand debit = debits[i];
|
||||
if (debit.getGlAccountId().equals(accountDataForLookup.getId())) {
|
||||
validDebits[i] = debit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debits.length != validDebits.length) { throw new RuntimeException("Invalid debits"); }
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits) {
|
||||
// sum of all debits must be = sum of all credits
|
||||
BigDecimal creditsSum = BigDecimal.ZERO;
|
||||
BigDecimal debitsSum = BigDecimal.ZERO;
|
||||
for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) {
|
||||
if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
|
||||
creditsSum = creditsSum.add(creditEntryCommand.getAmount());
|
||||
}
|
||||
for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) {
|
||||
if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
|
||||
debitsSum = debitsSum.add(debitEntryCommand.getAmount());
|
||||
}
|
||||
if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); }
|
||||
}
|
||||
|
||||
private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] creditOrDebits, final BigDecimal amount) {
|
||||
if (amount == null) { throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH_WITH_AMOUNT,
|
||||
null, null, null); }
|
||||
BigDecimal creditOrDebitSum = BigDecimal.ZERO;
|
||||
for (final SingleDebitOrCreditEntryCommand creditEntryCommand : creditOrDebits) {
|
||||
if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
|
||||
creditOrDebitSum = creditOrDebitSum.add(creditEntryCommand.getAmount());
|
||||
}
|
||||
if (creditOrDebitSum.compareTo(amount) != 0) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); }
|
||||
}
|
||||
|
||||
private void validateGLAccountForTransaction(final GLAccount creditOrDebitAccountHead) {
|
||||
/***
|
||||
* validate that the account allows manual adjustments and is not
|
||||
* disabled
|
||||
@@ -199,7 +307,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
final boolean accrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("accrualBasedAccountingEnabled");
|
||||
|
||||
if (cashBasedAccountingEnabled || accrualBasedAccountingEnabled) {
|
||||
final LoanDTO loanDTO = helper.populateLoanDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
|
||||
final LoanDTO loanDTO = this.helper.populateLoanDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
|
||||
accrualBasedAccountingEnabled);
|
||||
final AccountingProcessorForLoan accountingProcessorForLoan = this.accountingProcessorForLoanFactory
|
||||
.determineProcessor(loanDTO);
|
||||
@@ -209,13 +317,13 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void createJournalEntriesForSavings(Map<String, Object> accountingBridgeData) {
|
||||
public void createJournalEntriesForSavings(final Map<String, Object> accountingBridgeData) {
|
||||
|
||||
final boolean cashBasedAccountingEnabled = (Boolean) accountingBridgeData.get("cashBasedAccountingEnabled");
|
||||
final boolean accrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("accrualBasedAccountingEnabled");
|
||||
|
||||
if (cashBasedAccountingEnabled || accrualBasedAccountingEnabled) {
|
||||
final SavingsDTO savingsDTO = helper.populateSavingsDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
|
||||
final SavingsDTO savingsDTO = this.helper.populateSavingsDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled,
|
||||
accrualBasedAccountingEnabled);
|
||||
final AccountingProcessorForSavings accountingProcessorForSavings = this.accountingProcessorForSavingsFactory
|
||||
.determineProcessor(savingsDTO);
|
||||
@@ -247,21 +355,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); }
|
||||
|
||||
// sum of all debits must be = sum of all credits
|
||||
BigDecimal creditsSum = BigDecimal.ZERO;
|
||||
BigDecimal debitsSum = BigDecimal.ZERO;
|
||||
for (final SingleDebitOrCreditEntryCommand creditEntryCommand : credits) {
|
||||
if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
|
||||
creditsSum = creditsSum.add(creditEntryCommand.getAmount());
|
||||
}
|
||||
for (final SingleDebitOrCreditEntryCommand debitEntryCommand : debits) {
|
||||
if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, null); }
|
||||
debitsSum = debitsSum.add(debitEntryCommand.getAmount());
|
||||
}
|
||||
if (creditsSum.compareTo(debitsSum) != 0) { throw new JournalEntryInvalidException(
|
||||
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null, null, null); }
|
||||
checkDebitAndCreditAmounts(credits, debits);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -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.accounting.producttoaccountmapping.data;
|
||||
|
||||
public class ChargeToGLAccountMapper {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final long chargeId;
|
||||
@SuppressWarnings("unused")
|
||||
private final long incomeAccountId;
|
||||
|
||||
public ChargeToGLAccountMapper(long chargeId, long incomeAccountId) {
|
||||
this.chargeId = chargeId;
|
||||
this.incomeAccountId = incomeAccountId;
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 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.accounting.producttoaccountmapping.data;
|
||||
|
||||
public class PaymentTypeToGLAccountMapper {
|
||||
|
||||
+25
-1
@@ -15,6 +15,7 @@ import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@@ -33,6 +34,10 @@ public class ProductToGLAccountMapping extends AbstractPersistable<Long> {
|
||||
@JoinColumn(name = "payment_type", nullable = true)
|
||||
private CodeValue paymentType;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "charge_id", nullable = true)
|
||||
private Charge charge;
|
||||
|
||||
@Column(name = "product_type", nullable = false)
|
||||
private int productType;
|
||||
|
||||
@@ -49,16 +54,27 @@ public class ProductToGLAccountMapping extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType, final int financialAccountType) {
|
||||
this(glAccount, productId, productType, financialAccountType, null);
|
||||
this(glAccount, productId, productType, financialAccountType, null, null);
|
||||
}
|
||||
|
||||
public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
|
||||
final int financialAccountType, final Charge charge) {
|
||||
this(glAccount, productId, productType, financialAccountType, null, charge);
|
||||
}
|
||||
|
||||
public ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
|
||||
final int financialAccountType, final CodeValue paymentType) {
|
||||
this(glAccount, productId, productType, financialAccountType, paymentType, null);
|
||||
}
|
||||
|
||||
private ProductToGLAccountMapping(final GLAccount glAccount, final Long productId, final int productType,
|
||||
final int financialAccountType, final CodeValue paymentType, final Charge charge) {
|
||||
this.glAccount = glAccount;
|
||||
this.productId = productId;
|
||||
this.productType = productType;
|
||||
this.financialAccountType = financialAccountType;
|
||||
this.paymentType = paymentType;
|
||||
this.charge = charge;
|
||||
}
|
||||
|
||||
public GLAccount getGlAccount() {
|
||||
@@ -101,4 +117,12 @@ public class ProductToGLAccountMapping extends AbstractPersistable<Long> {
|
||||
this.paymentType = paymentType;
|
||||
}
|
||||
|
||||
public Charge getCharge() {
|
||||
return this.charge;
|
||||
}
|
||||
|
||||
public void setCharge(Charge charge) {
|
||||
this.charge = charge;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-1
@@ -18,7 +18,10 @@ public interface ProductToGLAccountMappingRepository extends JpaRepository<Produ
|
||||
ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(Long productId, int productType,
|
||||
int financialAccountType, Long paymentType);
|
||||
|
||||
@Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL")
|
||||
ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountTypeAndChargeId(Long productId, int productType,
|
||||
int financialAccountType, Long chargeId);
|
||||
|
||||
@Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL")
|
||||
ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType,
|
||||
@Param("financialAccountType") int financialAccountType);
|
||||
|
||||
@@ -27,5 +30,15 @@ public interface ProductToGLAccountMappingRepository extends JpaRepository<Produ
|
||||
List<ProductToGLAccountMapping> findAllPaymentTypeToFundSourceMappings(@Param("productId") Long productId,
|
||||
@Param("productType") int productType);
|
||||
|
||||
/*** The financial Account Type for income from interest will always be 4 ***/
|
||||
@Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=4 and mapping.charge is not NULL")
|
||||
List<ProductToGLAccountMapping> findAllFeeToIncomeAccountMappings(@Param("productId") Long productId,
|
||||
@Param("productType") int productType);
|
||||
|
||||
/*** The financial Account Type for income from interest will always be 5 ***/
|
||||
@Query("from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=5 and mapping.charge is not NULL")
|
||||
List<ProductToGLAccountMapping> findAllPenaltyToIncomeAccountMappings(@Param("productId") Long productId,
|
||||
@Param("productType") int productType);
|
||||
|
||||
List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType);
|
||||
}
|
||||
|
||||
+27
-7
@@ -1,12 +1,17 @@
|
||||
/**
|
||||
* 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.accounting.producttoaccountmapping.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
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.LOAN_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;
|
||||
@@ -14,6 +19,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAc
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -23,10 +29,10 @@ import com.google.gson.JsonElement;
|
||||
public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
|
||||
|
||||
@Autowired
|
||||
public LoanProductToGLAccountMappingHelper(GLAccountRepository glAccountRepository,
|
||||
ProductToGLAccountMappingRepository glAccountMappingRepository, FromJsonHelper fromApiJsonHelper,
|
||||
CodeValueRepositoryWrapper codeValueRepositoryWrapper) {
|
||||
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper);
|
||||
public LoanProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
|
||||
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
|
||||
final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final ChargeRepositoryWrapper chargeRepositoryWrapper) {
|
||||
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper, chargeRepositoryWrapper);
|
||||
}
|
||||
|
||||
/*** Set of abstractions for saving Loan Products to GL Account Mappings ***/
|
||||
@@ -68,15 +74,29 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
|
||||
/*** Abstractions for payments channel related to loan products ***/
|
||||
|
||||
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
Map<String, Object> changes) {
|
||||
final Map<String, Object> changes) {
|
||||
savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
|
||||
}
|
||||
|
||||
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
Map<String, Object> changes) {
|
||||
final Map<String, Object> changes) {
|
||||
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN);
|
||||
}
|
||||
|
||||
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
final Map<String, Object> changes) {
|
||||
// save both fee and penalty charges
|
||||
saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
|
||||
saveChargesToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
|
||||
}
|
||||
|
||||
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
final Map<String, Object> changes) {
|
||||
// update both fee and penalty charges
|
||||
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, true);
|
||||
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN, false);
|
||||
}
|
||||
|
||||
public Map<String, Object> populateChangesForNewLoanProductToGLAccountMappingCreation(final JsonElement element,
|
||||
final AccountingRuleType accountingRuleType) {
|
||||
final Map<String, Object> changes = new HashMap<String, Object>();
|
||||
|
||||
+160
-1
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 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.accounting.producttoaccountmapping.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -21,6 +26,8 @@ import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -36,15 +43,17 @@ public class ProductToGLAccountMappingHelper {
|
||||
protected final ProductToGLAccountMappingRepository accountMappingRepository;
|
||||
protected final FromJsonHelper fromApiJsonHelper;
|
||||
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
|
||||
private final ChargeRepositoryWrapper chargeRepositoryWrapper;
|
||||
|
||||
@Autowired
|
||||
public ProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
|
||||
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
|
||||
final CodeValueRepositoryWrapper codeValueRepositoryWrapper) {
|
||||
final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final ChargeRepositoryWrapper chargeRepositoryWrapper) {
|
||||
this.accountRepository = glAccountRepository;
|
||||
this.accountMappingRepository = glAccountMappingRepository;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
|
||||
this.chargeRepositoryWrapper = chargeRepositoryWrapper;
|
||||
|
||||
}
|
||||
|
||||
@@ -105,6 +114,128 @@ public class ProductToGLAccountMappingHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Charge to Income account mappings for a particular
|
||||
* product/product type (also populates the changes array if passed in)
|
||||
*
|
||||
* @param command
|
||||
* @param element
|
||||
* @param productId
|
||||
* @param changes
|
||||
*/
|
||||
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
Map<String, Object> changes, final PortfolioProductType portfolioProductType, final boolean isPenalty) {
|
||||
String arrayName;
|
||||
if (isPenalty) {
|
||||
arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
|
||||
} else {
|
||||
arrayName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
|
||||
}
|
||||
|
||||
JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
|
||||
if (chargeToIncomeAccountMappingArray != null) {
|
||||
if (changes != null) {
|
||||
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
|
||||
command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue()));
|
||||
}
|
||||
for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
|
||||
final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
|
||||
final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong();
|
||||
final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong();
|
||||
saveChargeToFundSourceMapping(productId, chargeId, incomeAccountId, portfolioProductType, isPenalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command
|
||||
* @param element
|
||||
* @param productId
|
||||
* @param changes
|
||||
*/
|
||||
public void updateChargeToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
|
||||
Map<String, Object> changes, final PortfolioProductType portfolioProductType, boolean isPenalty) {
|
||||
// find all existing payment Channel to Fund source Mappings
|
||||
List<ProductToGLAccountMapping> existingChargeToIncomeAccountMappings;
|
||||
String arrayFragmentName;
|
||||
|
||||
if (isPenalty) {
|
||||
existingChargeToIncomeAccountMappings = accountMappingRepository.findAllPenaltyToIncomeAccountMappings(productId,
|
||||
portfolioProductType.getValue());
|
||||
arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
|
||||
} else {
|
||||
existingChargeToIncomeAccountMappings = accountMappingRepository.findAllFeeToIncomeAccountMappings(productId,
|
||||
portfolioProductType.getValue());
|
||||
arrayFragmentName = LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue();
|
||||
}
|
||||
|
||||
JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
|
||||
/**
|
||||
* Variable stores a map representation of charges (key) and their
|
||||
* associated income Id's (value) extracted from the passed in
|
||||
* Jsoncommand
|
||||
**/
|
||||
Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<Long, Long>();
|
||||
/***
|
||||
* Variable stores all charges which have already been mapped to Income
|
||||
* Accounts in the system
|
||||
**/
|
||||
Set<Long> existingCharges = new HashSet<Long>();
|
||||
if (chargeToIncomeAccountMappingArray != null) {
|
||||
if (changes != null) {
|
||||
changes.put(arrayFragmentName, command.jsonFragment(arrayFragmentName));
|
||||
}
|
||||
|
||||
for (int i = 0; i < chargeToIncomeAccountMappingArray.size(); i++) {
|
||||
final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
|
||||
final Long chargeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.CHARGE_ID.getValue()).getAsLong();
|
||||
final Long incomeAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue()).getAsLong();
|
||||
inputChargeToIncomeAccountMap.put(chargeId, incomeAccountId);
|
||||
}
|
||||
|
||||
// If input map is empty, delete all existing mappings
|
||||
if (inputChargeToIncomeAccountMap.size() == 0) {
|
||||
accountMappingRepository.deleteInBatch(existingChargeToIncomeAccountMappings);
|
||||
}/**
|
||||
* Else, <br/>
|
||||
* update existing mappings OR <br/>
|
||||
* delete old mappings (which are already present, but not passed in
|
||||
* as a part of Jsoncommand)<br/>
|
||||
* Create new mappings for charges that are passed in as a part of
|
||||
* the Jsoncommand but not already present
|
||||
*
|
||||
**/
|
||||
else {
|
||||
for (ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
|
||||
Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
|
||||
existingCharges.add(currentCharge);
|
||||
// update existing mappings (if required)
|
||||
if (inputChargeToIncomeAccountMap.containsKey(currentCharge)) {
|
||||
Long newGLAccountId = inputChargeToIncomeAccountMap.get(currentCharge);
|
||||
if (newGLAccountId != chargeToIncomeAccountMapping.getGlAccount().getId()) {
|
||||
final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(),
|
||||
GLAccountType.INCOME, newGLAccountId);
|
||||
chargeToIncomeAccountMapping.setGlAccount(glAccount);
|
||||
accountMappingRepository.save(chargeToIncomeAccountMapping);
|
||||
}
|
||||
}// deleted payment type
|
||||
else {
|
||||
accountMappingRepository.delete(chargeToIncomeAccountMapping);
|
||||
}
|
||||
}
|
||||
// create new mappings
|
||||
Set<Long> incomingCharges = inputChargeToIncomeAccountMap.keySet();
|
||||
incomingCharges.removeAll(existingCharges);
|
||||
// incomingPaymentTypes now only contains the newly added
|
||||
// payment Type mappings
|
||||
for (Long newCharge : incomingCharges) {
|
||||
Long newGLAccountId = inputChargeToIncomeAccountMap.get(newCharge);
|
||||
saveChargeToFundSourceMapping(productId, newCharge, newGLAccountId, portfolioProductType, isPenalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command
|
||||
* @param element
|
||||
@@ -199,6 +330,34 @@ public class ProductToGLAccountMappingHelper {
|
||||
this.accountMappingRepository.save(accountMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param productId
|
||||
* @param jsonObject
|
||||
*/
|
||||
private void saveChargeToFundSourceMapping(final Long productId, final Long chargeId, final Long incomeAccountId,
|
||||
final PortfolioProductType portfolioProductType, final boolean isPenalty) {
|
||||
Charge charge = chargeRepositoryWrapper.findOneWithNotFoundDetection(chargeId);
|
||||
|
||||
// TODO Vishwas: Need to validate if given charge is fee or Penalty
|
||||
// based on input condition
|
||||
|
||||
final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_ACCOUNT_ID.getValue(),
|
||||
GLAccountType.INCOME, incomeAccountId);
|
||||
/**
|
||||
* Both CASH and Accrual placeholders have the same value for income
|
||||
* from Interest and penalties
|
||||
**/
|
||||
CASH_ACCOUNTS_FOR_LOAN placeHolderAccountType;
|
||||
if (isPenalty) {
|
||||
placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES;
|
||||
} else {
|
||||
placeHolderAccountType = CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES;
|
||||
}
|
||||
final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId,
|
||||
portfolioProductType.getValue(), placeHolderAccountType.getValue(), charge);
|
||||
this.accountMappingRepository.save(accountMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches account with a particular Id and throws and Exception it is not
|
||||
* of the expected Account Category ('ASSET','liability' etc)
|
||||
|
||||
+6
@@ -8,6 +8,7 @@ package org.mifosplatform.accounting.producttoaccountmapping.service;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||
|
||||
public interface ProductToGLAccountMappingReadPlatformService {
|
||||
@@ -16,7 +17,12 @@ public interface ProductToGLAccountMappingReadPlatformService {
|
||||
|
||||
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId);
|
||||
|
||||
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForLoanProduct(final Long loanProductId);
|
||||
|
||||
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(final Long loanProductId);
|
||||
|
||||
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType);
|
||||
|
||||
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId);
|
||||
|
||||
}
|
||||
+50
-10
@@ -18,8 +18,10 @@ import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR
|
||||
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.data.ChargeToGLAccountMapper;
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
|
||||
import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType;
|
||||
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
|
||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@@ -39,8 +41,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
private static final class ProductToGLAccountMappingMapper implements RowMapper<Map<String, Object>> {
|
||||
|
||||
public String schema() {
|
||||
return " id as id, gl_account_id as glAccountId,product_id as productId,product_type as productType,financial_account_type as financialAccountType,"
|
||||
+ " payment_type as paymentTypeId from acc_product_mapping " + " where product_type= ? ";
|
||||
return " mapping.id as id, mapping.gl_account_id as glAccountId, mapping.product_id as productId, "
|
||||
+ " mapping.product_type as productType,mapping.financial_account_type as financialAccountType, "
|
||||
+ " mapping.payment_type as paymentTypeId, mapping.charge_id as chargeId, charge.is_penalty as penalty "
|
||||
+ " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id "
|
||||
+ " where mapping.product_type= ? ";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,7 +54,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
final Long id = rs.getLong("id");
|
||||
final Long glAccountId = rs.getLong("glAccountId");
|
||||
final Long productId = rs.getLong("productId");
|
||||
final Long paymentTypeId = rs.getLong("paymentTypeId");
|
||||
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId");
|
||||
final Long chargeId = rs.getLong("chargeId");
|
||||
final Integer productType = rs.getInt("productType");
|
||||
final Integer financialAccountType = rs.getInt("financialAccountType");
|
||||
|
||||
@@ -60,6 +66,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
loanProductToGLAccountMap.put("productType", productType);
|
||||
loanProductToGLAccountMap.put("financialAccountType", financialAccountType);
|
||||
loanProductToGLAccountMap.put("paymentTypeId", paymentTypeId);
|
||||
loanProductToGLAccountMap.put("chargeId", chargeId);
|
||||
|
||||
return loanProductToGLAccountMap;
|
||||
}
|
||||
@@ -71,7 +78,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
final Map<String, Object> accountMappingDetails = new LinkedHashMap<String, Object>(8);
|
||||
|
||||
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
||||
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null ";
|
||||
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null";
|
||||
|
||||
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] {
|
||||
PortfolioProductType.LOAN.getValue(), loanProductId });
|
||||
@@ -138,7 +145,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
final Map<String, Object> accountMappingDetails = new LinkedHashMap<String, Object>(8);
|
||||
|
||||
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
||||
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null ";
|
||||
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null ";
|
||||
|
||||
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] {
|
||||
PortfolioProductType.SAVING.getValue(), savingsProductId });
|
||||
@@ -168,14 +175,12 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
|
||||
@Override
|
||||
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForLoanProduct(Long loanProductId) {
|
||||
List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null;
|
||||
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId, paymentTypeToGLAccountMappers);
|
||||
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappingsForSavingsProduct(Long savingsProductId) {
|
||||
List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null;
|
||||
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId, paymentTypeToGLAccountMappers);
|
||||
return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,13 +189,14 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
* @return
|
||||
*/
|
||||
private List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappings(PortfolioProductType portfolioProductType,
|
||||
Long loanProductId, List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers) {
|
||||
Long loanProductId) {
|
||||
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
||||
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null";
|
||||
|
||||
final List<Map<String, Object>> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] {
|
||||
portfolioProductType.getValue(), loanProductId });
|
||||
|
||||
List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null;
|
||||
for (final Map<String, Object> productToGLAccountMap : paymentTypeToFundSourceMappingsList) {
|
||||
if (paymentTypeToGLAccountMappers == null) {
|
||||
paymentTypeToGLAccountMappers = new ArrayList<PaymentTypeToGLAccountMapper>();
|
||||
@@ -203,4 +209,38 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
|
||||
return paymentTypeToGLAccountMappers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChargeToGLAccountMapper> fetchFeeToIncomeAccountMappingsForLoanProduct(Long loanProductId) {
|
||||
return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChargeToGLAccountMapper> fetchPenaltyToIncomeAccountMappingsForLoanProduct(Long loanProductId) {
|
||||
return fetchChargeToIncomeAccountMappings(PortfolioProductType.LOAN, loanProductId, true);
|
||||
}
|
||||
|
||||
private List<ChargeToGLAccountMapper> fetchChargeToIncomeAccountMappings(PortfolioProductType portfolioProductType, Long loanProductId,
|
||||
boolean penalty) {
|
||||
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
|
||||
String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_id is not null and charge.is_penalty=";
|
||||
if (penalty) {
|
||||
sql = sql + " 1";
|
||||
} else {
|
||||
sql = sql + " 0";
|
||||
}
|
||||
|
||||
final List<Map<String, Object>> chargeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] {
|
||||
portfolioProductType.getValue(), loanProductId });
|
||||
List<ChargeToGLAccountMapper> chargeToGLAccountMappers = null;
|
||||
for (final Map<String, Object> chargeToIncomeAccountMap : chargeToFundSourceMappingsList) {
|
||||
if (chargeToGLAccountMappers == null) {
|
||||
chargeToGLAccountMappers = new ArrayList<ChargeToGLAccountMapper>();
|
||||
}
|
||||
final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId");
|
||||
final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId");
|
||||
ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper(chargeId, glAccountId);
|
||||
chargeToGLAccountMappers.add(chargeToGLAccountMapper);
|
||||
}
|
||||
return chargeToGLAccountMappers;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -81,6 +81,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
|
||||
// advanced accounting mappings
|
||||
loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
||||
loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
||||
break;
|
||||
case ACCRUAL_BASED:
|
||||
|
||||
@@ -119,6 +120,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
|
||||
// advanced accounting mappings
|
||||
loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null);
|
||||
loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, loanProductId, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -187,12 +189,12 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
createLoanProductToGLAccountMapping(loanProductId, command);
|
||||
changes = loanProductToGLAccountMappingHelper.populateChangesForNewLoanProductToGLAccountMappingCreation(element,
|
||||
accountingRuleType);
|
||||
loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, changes);
|
||||
}/*** else examine and update individual changes ***/
|
||||
else {
|
||||
loanProductToGLAccountMappingHelper.handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element,
|
||||
accountingRuleType);
|
||||
loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes);
|
||||
loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command, element, loanProductId, changes);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
@@ -218,7 +220,6 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
|
||||
createSavingProductToGLAccountMapping(savingsProductId, command);
|
||||
changes = savingsProductToGLAccountMappingHelper.populateChangesForNewSavingsProductToGLAccountMappingCreation(element,
|
||||
accountingRuleType);
|
||||
savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingsProductId, changes);
|
||||
}/*** else examine and update individual changes ***/
|
||||
else {
|
||||
savingsProductToGLAccountMappingHelper.handleChangesToSavingsProductToGLAccountMappings(savingsProductId, changes, element,
|
||||
|
||||
+8
-2
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 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.accounting.producttoaccountmapping.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -13,6 +18,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAc
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -24,8 +30,8 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
|
||||
@Autowired
|
||||
public SavingsProductToGLAccountMappingHelper(GLAccountRepository glAccountRepository,
|
||||
ProductToGLAccountMappingRepository glAccountMappingRepository, FromJsonHelper fromApiJsonHelper,
|
||||
CodeValueRepositoryWrapper codeValueRepositoryWrapper) {
|
||||
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper);
|
||||
CodeValueRepositoryWrapper codeValueRepositoryWrapper, ChargeRepositoryWrapper chargeRepositoryWrapper) {
|
||||
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper, chargeRepositoryWrapper);
|
||||
}
|
||||
|
||||
/*** Set of abstractions for saving Saving Products to GL Account Mappings ***/
|
||||
|
||||
+97
-30
@@ -5,9 +5,13 @@
|
||||
*/
|
||||
package org.mifosplatform.accounting.rule.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
@@ -18,18 +22,22 @@ 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.accounting.common.AccountingConstants;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
||||
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
|
||||
import org.mifosplatform.accounting.rule.data.AccountingRuleData;
|
||||
import org.mifosplatform.accounting.rule.data.AccountingTagRuleData;
|
||||
import org.mifosplatform.accounting.rule.service.AccountingRuleReadPlatformService;
|
||||
import org.mifosplatform.commands.domain.CommandWrapper;
|
||||
import org.mifosplatform.commands.service.CommandWrapperBuilder;
|
||||
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||
import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
@@ -47,7 +55,9 @@ import org.springframework.stereotype.Component;
|
||||
public class AccountingRuleApiResource {
|
||||
|
||||
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "officeName",
|
||||
"accountToDebitId", "accountToCreditId", "name", "description", "systemDefined"));
|
||||
"accountToDebitId", "accountToCreditId", "name", "description", "systemDefined", "allowedCreditTagOptions",
|
||||
"allowedDebitTagOptions", "debitTags", "creditTags", "creditAccounts", "debitAccounts", "allowMultipleCreditEntries",
|
||||
"allowMultipleDebitEntries", "tag"));
|
||||
|
||||
private final String resourceNameForPermission = "ACCOUNTINGRULE";
|
||||
|
||||
@@ -58,6 +68,7 @@ public class AccountingRuleApiResource {
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final PlatformSecurityContext context;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public AccountingRuleApiResource(final PlatformSecurityContext context,
|
||||
@@ -65,7 +76,7 @@ public class AccountingRuleApiResource {
|
||||
final DefaultToApiJsonSerializer<AccountingRuleData> toApiJsonSerializer,
|
||||
final ApiRequestParameterHelper apiRequestParameterHelper, final GLAccountReadPlatformService accountReadPlatformService,
|
||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
|
||||
final OfficeReadPlatformService officeReadPlatformService) {
|
||||
final OfficeReadPlatformService officeReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService) {
|
||||
this.context = context;
|
||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
||||
@@ -73,30 +84,40 @@ public class AccountingRuleApiResource {
|
||||
this.accountingRuleReadPlatformService = accountingRuleReadPlatformService;
|
||||
this.officeReadPlatformService = officeReadPlatformService;
|
||||
this.accountReadPlatformService = accountReadPlatformService;
|
||||
this.codeValueReadPlatformService = codeValueReadPlatformService;
|
||||
}
|
||||
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveTemplate(@Context final UriInfo uriInfo) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(resourceNameForPermission);
|
||||
|
||||
AccountingRuleData accountingRuleData = null;
|
||||
accountingRuleData = handleTemplate(accountingRuleData);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.apiJsonSerializerService.serialize(settings, accountingRuleData, RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveTemplate(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
|
||||
|
||||
AccountingRuleData accountingRuleData = null;
|
||||
accountingRuleData = handleTemplate(accountingRuleData);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.apiJsonSerializerService.serialize(settings, accountingRuleData, RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveAllAccountingRules(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId) {
|
||||
public String retrieveAllAccountingRules(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
|
||||
final List<AccountingRuleData> accountingRuleDatas = this.accountingRuleReadPlatformService.retrieveAllAccountingRules(officeId);
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
final Long officeId = this.context.authenticatedUser().getOffice().getId();
|
||||
boolean isAssociationParametersExists = false;
|
||||
if (!associationParameters.isEmpty()) {
|
||||
if (associationParameters.contains("all")) {
|
||||
isAssociationParametersExists = true; // If true, retrieve additional fields for journal entry form.
|
||||
}
|
||||
}
|
||||
final List<AccountingRuleData> accountingRuleDatas = this.accountingRuleReadPlatformService.retrieveAllAccountingRules(officeId,
|
||||
isAssociationParametersExists);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.apiJsonSerializerService.serialize(settings, accountingRuleDatas, RESPONSE_DATA_PARAMETERS);
|
||||
@@ -109,12 +130,11 @@ public class AccountingRuleApiResource {
|
||||
public String retreiveAccountingRule(@PathParam("accountingRuleId") final Long accountingRuleId, @Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermission);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
|
||||
AccountingRuleData accountingRuleData = this.accountingRuleReadPlatformService.retrieveAccountingRuleById(accountingRuleId);
|
||||
if (settings.isTemplate()) {
|
||||
accountingRuleData = handleTemplate(accountingRuleData);
|
||||
accountingRuleData = handleTemplate(accountingRuleData);
|
||||
}
|
||||
|
||||
return this.apiJsonSerializerService.serialize(settings, accountingRuleData, RESPONSE_DATA_PARAMETERS);
|
||||
@@ -157,13 +177,60 @@ public class AccountingRuleApiResource {
|
||||
|
||||
return this.apiJsonSerializerService.serialize(result);
|
||||
}
|
||||
|
||||
private AccountingRuleData handleTemplate(AccountingRuleData accountingRuleData) {
|
||||
List<GLAccountData> allowedAccounts = this.accountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
|
||||
List<OfficeData> allowedOffices = (List<OfficeData>) this.officeReadPlatformService.retrieveAllOfficesForDropdown();
|
||||
if (accountingRuleData == null) {
|
||||
return new AccountingRuleData(allowedAccounts, allowedOffices);
|
||||
}
|
||||
return new AccountingRuleData(accountingRuleData, allowedAccounts, allowedOffices);
|
||||
}
|
||||
|
||||
private AccountingRuleData handleTemplate(AccountingRuleData accountingRuleData) {
|
||||
final List<GLAccountData> allowedAccounts = this.accountReadPlatformService.retrieveAllEnabledDetailGLAccounts();
|
||||
final List<OfficeData> allowedOffices = (List<OfficeData>) this.officeReadPlatformService.retrieveAllOfficesForDropdown();
|
||||
final Collection<CodeValueData> allowedTagOptions = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME);
|
||||
|
||||
allowedTagOptions.addAll(this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME));
|
||||
allowedTagOptions.addAll(this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME));
|
||||
allowedTagOptions.addAll(this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.INCOME_TAG_OPTION_CODE_NAME));
|
||||
allowedTagOptions.addAll(this.codeValueReadPlatformService.retrieveCodeValuesByCode(AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME));
|
||||
|
||||
if (accountingRuleData == null) {
|
||||
|
||||
final Collection<CodeValueData> allowedCreditTagOptions = allowedTagOptions;
|
||||
final Collection<CodeValueData> allowedDebitTagOptions = allowedTagOptions;
|
||||
|
||||
accountingRuleData = new AccountingRuleData(allowedAccounts, allowedOffices, allowedCreditTagOptions, allowedDebitTagOptions);
|
||||
|
||||
} else {
|
||||
|
||||
final Collection<CodeValueData> allowedCreditTagOptions;
|
||||
final Collection<CodeValueData> allowedDebitTagOptions;
|
||||
|
||||
if (accountingRuleData.getCreditTags() != null) {
|
||||
allowedCreditTagOptions = retrieveSelectedTags(allowedTagOptions, accountingRuleData.getCreditTags());
|
||||
} else {
|
||||
allowedCreditTagOptions = allowedTagOptions;
|
||||
}
|
||||
|
||||
if (accountingRuleData.getDebitTags() != null) {
|
||||
allowedDebitTagOptions = retrieveSelectedTags(allowedTagOptions, accountingRuleData.getDebitTags());
|
||||
} else {
|
||||
allowedDebitTagOptions = allowedTagOptions;
|
||||
}
|
||||
|
||||
accountingRuleData = new AccountingRuleData(accountingRuleData, allowedAccounts, allowedOffices, allowedCreditTagOptions,
|
||||
allowedDebitTagOptions);
|
||||
}
|
||||
return accountingRuleData;
|
||||
}
|
||||
|
||||
private Collection<CodeValueData> retrieveSelectedTags(final Collection<CodeValueData> allowedTagOptions,
|
||||
final List<AccountingTagRuleData> existedTags) {
|
||||
final Collection<CodeValueData> tempOptions = new ArrayList<CodeValueData>(allowedTagOptions);
|
||||
final Map<Long, CodeValueData> selectedTags = new HashMap<Long, CodeValueData>();
|
||||
for (final AccountingTagRuleData accountingTagRuleData : existedTags) {
|
||||
for (final CodeValueData codeValueData : tempOptions) {
|
||||
if (codeValueData.getId().equals(accountingTagRuleData.getTag().getId())) {
|
||||
selectedTags.put(codeValueData.getId(), codeValueData);
|
||||
}
|
||||
}
|
||||
}
|
||||
tempOptions.removeAll(selectedTags.values());
|
||||
return tempOptions;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -13,7 +13,8 @@ import java.util.Set;
|
||||
***/
|
||||
public enum AccountingRuleJsonInputParams {
|
||||
ID("id"), OFFICE_ID("officeId"), ACCOUNT_TO_DEBIT("debitAccountHead"), ACCOUNT_TO_CREDIT("creditAccountHead"), NAME("name"), DESCRIPTION(
|
||||
"description"), SYSTEM_DEFINED("systemDefined");
|
||||
"description"), SYSTEM_DEFINED("systemDefined"), DEBIT_ACCOUNT_TAGS("debitTags"), CREDIT_ACCOUNT_TAGS("creditTags"), ALLOW_MULTIPLE_CREDIT_ENTRIES(
|
||||
"allowMultipleCreditEntries"), ALLOW_MULTIPLE_DEBIT_ENTRIES("allowMultipleDebitEntries");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* 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.accounting.rule.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.accounting.rule.api.AccountingRuleJsonInputParams;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
|
||||
/**
|
||||
* Immutable command for adding an accounting closure
|
||||
*/
|
||||
public class AccountingRuleCommand {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
private final Long officeId;
|
||||
private final Long accountToDebitId;
|
||||
private final Long accountToCreditId;
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
public AccountingRuleCommand(Long id, Long officeId, Long accountToDebitId, Long accountToCreditId, String name, String description) {
|
||||
this.id = id;
|
||||
this.officeId = officeId;
|
||||
this.accountToDebitId = accountToDebitId;
|
||||
this.accountToCreditId = accountToCreditId;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void validateForCreate() {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("AccountingRule");
|
||||
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(this.accountToDebitId)
|
||||
.notNull().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(this.accountToCreditId)
|
||||
.notNull().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(this.officeId).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(this.name).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
|
||||
.notExceedingLengthOf(500);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
public void validateForUpdate() {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("AccountingRule");
|
||||
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(this.accountToDebitId)
|
||||
.ignoreIfNull().notBlank().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(this.accountToCreditId)
|
||||
.ignoreIfNull().notBlank().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(this.officeId).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(this.name).ignoreIfNull().notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(this.description).ignoreIfNull()
|
||||
.notExceedingLengthOf(500);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
}
|
||||
+131
-74
@@ -6,9 +6,12 @@
|
||||
package org.mifosplatform.accounting.rule.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||
import org.mifosplatform.organisation.office.data.OfficeData;
|
||||
|
||||
/**
|
||||
@@ -19,85 +22,139 @@ import org.mifosplatform.organisation.office.data.OfficeData;
|
||||
*/
|
||||
public class AccountingRuleData {
|
||||
|
||||
private final Long id;
|
||||
private final Long officeId;
|
||||
private final String officeName;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final boolean systemDefined;
|
||||
private final GLAccountData debitAccountHead;
|
||||
private final GLAccountData creditAccountHead;
|
||||
private final Long id;
|
||||
private final Long officeId;
|
||||
private final String officeName;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final boolean systemDefined;
|
||||
private final boolean allowMultipleDebitEntries;
|
||||
private final boolean allowMultipleCreditEntries;
|
||||
private final GLAccountData debitAccountHead;
|
||||
private final GLAccountData creditAccountHead;
|
||||
private final List<AccountingTagRuleData> creditTags;
|
||||
private final List<AccountingTagRuleData> debitTags;
|
||||
|
||||
// template
|
||||
@SuppressWarnings("unused")
|
||||
private List<OfficeData> allowedOffices = new ArrayList<OfficeData>();
|
||||
@SuppressWarnings("unused")
|
||||
private List<GLAccountData> allowedAccounts = new ArrayList<GLAccountData>();
|
||||
// template
|
||||
private List<OfficeData> allowedOffices = new ArrayList<OfficeData>();
|
||||
private List<GLAccountData> allowedAccounts = new ArrayList<GLAccountData>();
|
||||
private final Collection<CodeValueData> allowedCreditTagOptions;
|
||||
private final Collection<CodeValueData> allowedDebitTagOptions;
|
||||
private final List<GLAccountDataForLookup> creditAccounts;
|
||||
private final List<GLAccountDataForLookup> debitAccounts;
|
||||
|
||||
/*public AccountingRuleData(final Long id, final Long accountToDebitId, final Long accountToCreditId) {
|
||||
this(id, null, null, accountToDebitId, accountToCreditId, null, null, true, null, null, null, null, null, null);
|
||||
}*/
|
||||
public AccountingRuleData(final AccountingRuleData accountingRuleData, final List<GLAccountData> allowedAccounts,
|
||||
final List<OfficeData> allowedOffices, final Collection<CodeValueData> allowedCreditTagOptions,
|
||||
final Collection<CodeValueData> allowedDebitTagOptions) {
|
||||
this.id = accountingRuleData.id;
|
||||
this.officeId = accountingRuleData.officeId;
|
||||
this.officeName = accountingRuleData.officeName;
|
||||
this.name = accountingRuleData.name;
|
||||
this.description = accountingRuleData.description;
|
||||
this.systemDefined = accountingRuleData.systemDefined;
|
||||
this.allowMultipleDebitEntries = accountingRuleData.allowMultipleDebitEntries;
|
||||
this.allowMultipleCreditEntries = accountingRuleData.allowMultipleCreditEntries;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.allowedAccounts = allowedAccounts;
|
||||
this.debitAccountHead = accountingRuleData.debitAccountHead;
|
||||
this.creditAccountHead = accountingRuleData.creditAccountHead;
|
||||
this.allowedCreditTagOptions = allowedCreditTagOptions;
|
||||
this.allowedDebitTagOptions = allowedDebitTagOptions;
|
||||
this.creditTags = accountingRuleData.creditTags;
|
||||
this.debitTags = accountingRuleData.debitTags;
|
||||
this.creditAccounts = accountingRuleData.creditAccounts;
|
||||
this.debitAccounts = accountingRuleData.debitAccounts;
|
||||
}
|
||||
|
||||
/*public AccountingRuleData(final Long id, final Long officeId, final String officeName, final Long accountToDebitId,
|
||||
final Long accountToCreditId, final String name, final String description, final boolean systemDefined,
|
||||
final String debitAccountName, final String creditAccountName, final String debitAccountGLCode,
|
||||
final String creditAccountGLCode) {
|
||||
this(id, officeId, officeName, accountToDebitId, accountToCreditId, name, description, systemDefined, debitAccountName,
|
||||
creditAccountName, debitAccountGLCode, creditAccountGLCode, null, null);
|
||||
}*/
|
||||
public AccountingRuleData(final List<GLAccountData> allowedAccounts, final List<OfficeData> allowedOffices,
|
||||
final Collection<CodeValueData> allowedCreditTagOptions, final Collection<CodeValueData> allowedDebitTagOptions) {
|
||||
this.id = null;
|
||||
this.officeId = null;
|
||||
this.officeName = null;
|
||||
this.name = null;
|
||||
this.description = null;
|
||||
this.systemDefined = false;
|
||||
this.allowMultipleDebitEntries = false;
|
||||
this.allowMultipleCreditEntries = false;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.allowedAccounts = allowedAccounts;
|
||||
this.debitAccountHead = null;
|
||||
this.creditAccountHead = null;
|
||||
this.allowedCreditTagOptions = allowedCreditTagOptions;
|
||||
this.allowedDebitTagOptions = allowedDebitTagOptions;
|
||||
this.creditTags = null;
|
||||
this.debitTags = null;
|
||||
this.creditAccounts = null;
|
||||
this.debitAccounts = null;
|
||||
}
|
||||
|
||||
public AccountingRuleData(final Long id, final Long officeId, final String officeName, final String name, final String description,
|
||||
final boolean systemDefined, final List<OfficeData> allowedOffices, final List<GLAccountData> allowedAccounts) {
|
||||
this.id = id;
|
||||
this.officeId = officeId;
|
||||
this.officeName = officeName;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.systemDefined = systemDefined;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.allowedAccounts = allowedAccounts;
|
||||
this.debitAccountHead = null;
|
||||
this.creditAccountHead = null;
|
||||
}
|
||||
public AccountingRuleData(final Long id, final Long officeId, final String officeName, final String name, final String description,
|
||||
final boolean systemDefined, final boolean allowMultipleDebitEntries, final boolean allowMultipleCreditEntries,
|
||||
final GLAccountData debitAccountData, final GLAccountData creditAccountData) {
|
||||
this.id = id;
|
||||
this.officeId = officeId;
|
||||
this.officeName = officeName;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.systemDefined = systemDefined;
|
||||
this.allowMultipleDebitEntries = allowMultipleDebitEntries;
|
||||
this.allowMultipleCreditEntries = allowMultipleCreditEntries;
|
||||
this.allowedOffices = null;
|
||||
this.allowedAccounts = null;
|
||||
this.debitAccountHead = debitAccountData;
|
||||
this.creditAccountHead = creditAccountData;
|
||||
this.allowedCreditTagOptions = null;
|
||||
this.allowedDebitTagOptions = null;
|
||||
this.creditTags = null;
|
||||
this.debitTags = null;
|
||||
this.creditAccounts = new ArrayList<GLAccountDataForLookup>();
|
||||
this.debitAccounts = new ArrayList<GLAccountDataForLookup>();
|
||||
}
|
||||
|
||||
public AccountingRuleData(final AccountingRuleData accountingRuleData,
|
||||
final List<GLAccountData> allowedAccounts, final List<OfficeData> allowedOffices) {
|
||||
this.id = accountingRuleData.id;
|
||||
this.officeId = accountingRuleData.officeId;
|
||||
this.officeName = accountingRuleData.officeName;
|
||||
this.name = accountingRuleData.name;
|
||||
this.description = accountingRuleData.description;
|
||||
this.systemDefined = accountingRuleData.systemDefined;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.allowedAccounts = allowedAccounts;
|
||||
this.debitAccountHead = accountingRuleData.debitAccountHead;
|
||||
this.creditAccountHead = accountingRuleData.creditAccountHead;
|
||||
}
|
||||
public AccountingRuleData(final AccountingRuleData accountingRuleData, final List<AccountingTagRuleData> creditTags,
|
||||
final List<GLAccountDataForLookup> creditAccounts, final List<AccountingTagRuleData> debitTags,
|
||||
final List<GLAccountDataForLookup> debitAccounts) {
|
||||
this.id = accountingRuleData.id;
|
||||
this.officeId = accountingRuleData.officeId;
|
||||
this.officeName = accountingRuleData.officeName;
|
||||
this.name = accountingRuleData.name;
|
||||
this.description = accountingRuleData.description;
|
||||
this.systemDefined = accountingRuleData.systemDefined;
|
||||
this.allowMultipleDebitEntries = accountingRuleData.allowMultipleDebitEntries;
|
||||
this.allowMultipleCreditEntries = accountingRuleData.allowMultipleCreditEntries;
|
||||
this.allowedOffices = accountingRuleData.allowedOffices;
|
||||
this.allowedAccounts = accountingRuleData.allowedAccounts;
|
||||
this.debitAccountHead = accountingRuleData.debitAccountHead;
|
||||
this.creditAccountHead = accountingRuleData.creditAccountHead;
|
||||
this.allowedCreditTagOptions = accountingRuleData.allowedCreditTagOptions;
|
||||
this.allowedDebitTagOptions = accountingRuleData.allowedDebitTagOptions;
|
||||
this.creditTags = creditTags;
|
||||
this.creditAccounts = creditAccounts;
|
||||
this.debitTags = debitTags;
|
||||
this.debitAccounts = debitAccounts;
|
||||
}
|
||||
|
||||
public AccountingRuleData(final List<GLAccountData> allowedAccounts, final List<OfficeData> allowedOffices) {
|
||||
this.id = null;
|
||||
this.officeId = null;
|
||||
this.officeName = null;
|
||||
this.name = null;
|
||||
this.description = null;
|
||||
this.systemDefined = false;
|
||||
this.allowedOffices = allowedOffices;
|
||||
this.allowedAccounts = allowedAccounts;
|
||||
this.debitAccountHead = null;
|
||||
this.creditAccountHead = null;
|
||||
}
|
||||
public GLAccountData getDebitAccountHead() {
|
||||
return this.debitAccountHead;
|
||||
}
|
||||
|
||||
public AccountingRuleData(Long id, Long officeId, String officeName, String name, String description,
|
||||
boolean systemDefined, GLAccountData debitAccountData, GLAccountData creditAccountData) {
|
||||
this.id = id;
|
||||
this.officeId = officeId;
|
||||
this.officeName = officeName;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.systemDefined = systemDefined;
|
||||
this.allowedOffices = null;
|
||||
this.allowedAccounts = null;
|
||||
this.debitAccountHead = debitAccountData;
|
||||
this.creditAccountHead = creditAccountData;
|
||||
}
|
||||
public GLAccountData getCreditAccountHead() {
|
||||
return this.creditAccountHead;
|
||||
}
|
||||
|
||||
public List<AccountingTagRuleData> getCreditTags() {
|
||||
return this.creditTags;
|
||||
}
|
||||
|
||||
public List<AccountingTagRuleData> getDebitTags() {
|
||||
return this.debitTags;
|
||||
}
|
||||
|
||||
public List<GLAccountDataForLookup> getCreditAccounts() {
|
||||
return this.creditAccounts;
|
||||
}
|
||||
|
||||
public List<GLAccountDataForLookup> getDebitAccounts() {
|
||||
return this.debitAccounts;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.accounting.rule.data;
|
||||
|
||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
|
||||
public class AccountingTagRuleData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
private final CodeValueData tag;
|
||||
@SuppressWarnings("unused")
|
||||
private final EnumOptionData transactionType;
|
||||
|
||||
public AccountingTagRuleData(final Long id, final CodeValueData tag, final EnumOptionData transactionType) {
|
||||
this.id = id;
|
||||
this.tag = tag;
|
||||
this.transactionType = transactionType;
|
||||
}
|
||||
|
||||
public CodeValueData getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
}
|
||||
+113
-13
@@ -5,18 +5,27 @@
|
||||
*/
|
||||
package org.mifosplatform.accounting.rule.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.mifosplatform.accounting.rule.api.AccountingRuleJsonInputParams;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
@@ -34,11 +43,11 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
private Office office;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "debit_account_id", nullable = false)
|
||||
@JoinColumn(name = "debit_account_id", nullable = true)
|
||||
private GLAccount accountToDebit;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "credit_account_id", nullable = false)
|
||||
@JoinColumn(name = "credit_account_id", nullable = true)
|
||||
private GLAccount accountToCredit;
|
||||
|
||||
@Column(name = "description", nullable = true, length = 500)
|
||||
@@ -47,10 +56,21 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
@Column(name = "system_defined", nullable = false)
|
||||
private Boolean systemDefined;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "accountingRule", orphanRemoval = true)
|
||||
private final List<AccountingTagRule> accountingTagRules = new ArrayList<AccountingTagRule>();
|
||||
|
||||
@Column(name = "allow_multiple_credits", nullable = false)
|
||||
private boolean allowMultipleCreditEntries;
|
||||
|
||||
@Column(name = "allow_multiple_debits", nullable = false)
|
||||
private boolean allowMultipleDebitEntries;
|
||||
|
||||
protected AccountingRule() {}
|
||||
|
||||
private AccountingRule(Office office, GLAccount accountToDebit, GLAccount accountToCredit, String name, String description,
|
||||
boolean systemDefined) {
|
||||
private AccountingRule(final Office office, final GLAccount accountToDebit, final GLAccount accountToCredit, final String name,
|
||||
final String description, final boolean systemDefined, final boolean allowMultipleCreditEntries,
|
||||
final boolean allowMultipleDebitEntries) {
|
||||
this.accountToDebit = accountToDebit;
|
||||
this.accountToCredit = accountToCredit;
|
||||
this.name = name;
|
||||
@@ -60,24 +80,34 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
this.description = this.description.trim();
|
||||
}
|
||||
this.systemDefined = systemDefined;
|
||||
this.allowMultipleCreditEntries = allowMultipleCreditEntries;
|
||||
this.allowMultipleDebitEntries = allowMultipleDebitEntries;
|
||||
}
|
||||
|
||||
public static AccountingRule fromJson(final Office office, final GLAccount accountToDebit, final GLAccount accountToCredit,
|
||||
JsonCommand command) {
|
||||
final JsonCommand command, final boolean allowMultipleCreditEntries, final boolean allowMultipleDebitEntries) {
|
||||
final String name = command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue());
|
||||
final String description = command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue());
|
||||
final boolean systemDefined = false;
|
||||
return new AccountingRule(office, accountToDebit, accountToCredit, name, description, systemDefined);
|
||||
return new AccountingRule(office, accountToDebit, accountToCredit, name, description, systemDefined, allowMultipleCreditEntries,
|
||||
allowMultipleDebitEntries);
|
||||
}
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(5);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.OFFICE_ID.getValue(), 0L);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(), 0L);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(), 0L);
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(10);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.OFFICE_ID.getValue(), this.office == null ? 0L
|
||||
: this.office.getId());
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
|
||||
this.accountToDebit == null ? 0L : this.accountToDebit.getId());
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
|
||||
this.accountToCredit == null ? 0L : this.accountToCredit.getId());
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.NAME.getValue(), this.name);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.DESCRIPTION.getValue(), this.description);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.SYSTEM_DEFINED.getValue(), this.systemDefined);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(),
|
||||
this.allowMultipleCreditEntries);
|
||||
handlePropertyUpdate(command, actualChanges, AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(),
|
||||
this.allowMultipleDebitEntries);
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
@@ -103,6 +133,14 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
// now update actual property
|
||||
if (paramName.equals(AccountingRuleJsonInputParams.SYSTEM_DEFINED.getValue())) {
|
||||
this.systemDefined = newValue;
|
||||
} else if (paramName.equals(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())) {
|
||||
if (this.accountToCredit == null) {
|
||||
this.allowMultipleCreditEntries = newValue;
|
||||
}
|
||||
} else if (paramName.equals(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())) {
|
||||
if (this.accountToDebit == null) {
|
||||
this.allowMultipleDebitEntries = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +161,7 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setOffice(Office office) {
|
||||
public void setOffice(final Office office) {
|
||||
this.office = office;
|
||||
}
|
||||
|
||||
@@ -139,11 +177,11 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
return this.accountToCredit;
|
||||
}
|
||||
|
||||
public void setAccountToDebit(GLAccount accountToDebit) {
|
||||
public void setAccountToDebit(final GLAccount accountToDebit) {
|
||||
this.accountToDebit = accountToDebit;
|
||||
}
|
||||
|
||||
public void setAccountToCredit(GLAccount accountToCredit) {
|
||||
public void setAccountToCredit(final GLAccount accountToCredit) {
|
||||
this.accountToCredit = accountToCredit;
|
||||
}
|
||||
|
||||
@@ -151,4 +189,66 @@ public class AccountingRule extends AbstractPersistable<Long> {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public List<AccountingTagRule> getAccountingTagRules() {
|
||||
return this.accountingTagRules;
|
||||
}
|
||||
|
||||
public void updateAccountingRuleForTags(final List<AccountingTagRule> debitAccountingTagRules) {
|
||||
for (final AccountingTagRule accountingTagRule : debitAccountingTagRules) {
|
||||
accountingTagRule.updateAccountingTagRule(this);
|
||||
this.accountingTagRules.add(accountingTagRule);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDebitAccount(final GLAccount accountToDebit) {
|
||||
this.accountToDebit = accountToDebit;
|
||||
this.allowMultipleDebitEntries = false;
|
||||
}
|
||||
|
||||
public void updateCreditAccount(final GLAccount accountToCredit) {
|
||||
this.accountToCredit = accountToCredit;
|
||||
this.allowMultipleCreditEntries = false;
|
||||
}
|
||||
|
||||
public void updateAllowMultipleCreditEntries(final boolean allowMultipleCreditEntries) {
|
||||
this.allowMultipleCreditEntries = allowMultipleCreditEntries;
|
||||
}
|
||||
|
||||
public void updateAllowMultipleDebitEntries(final boolean allowMultipleDebitEntries) {
|
||||
this.allowMultipleDebitEntries = allowMultipleDebitEntries;
|
||||
}
|
||||
|
||||
public void updateTags(final JournalEntryType type) {
|
||||
final Set<AccountingTagRule> existedCreditTags = new HashSet<AccountingTagRule>();
|
||||
final Set<AccountingTagRule> existedDebitTags = new HashSet<AccountingTagRule>();
|
||||
for (final AccountingTagRule accountingTagRule : this.accountingTagRules) {
|
||||
if (accountingTagRule.isCreditAccount()) {
|
||||
existedCreditTags.add(accountingTagRule);
|
||||
} else if (accountingTagRule.isDebitAccount()) {
|
||||
existedDebitTags.add(accountingTagRule);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.isCreditType()) {
|
||||
this.accountingTagRules.retainAll(existedCreditTags);
|
||||
} else if (type.isDebitType()) {
|
||||
this.accountingTagRules.retainAll(existedDebitTags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Set<AccountingTagRule> getAccountingTagRulesByType(final JournalEntryType type) {
|
||||
final Set<AccountingTagRule> existedTags = new HashSet<AccountingTagRule>();
|
||||
for (final AccountingTagRule accountingTagRule : this.accountingTagRules) {
|
||||
if (accountingTagRule.getAccountType().equals(type.getValue())) {
|
||||
existedTags.add(accountingTagRule);
|
||||
}
|
||||
}
|
||||
return existedTags;
|
||||
}
|
||||
|
||||
public void removeOldTags(final List<AccountingTagRule> accountsToRemove) {
|
||||
this.accountingTagRules.removeAll(accountsToRemove);
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.accounting.rule.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.mifosplatform.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@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;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "tag_id", nullable = false)
|
||||
private CodeValue tagId;
|
||||
|
||||
@Column(name = "acc_type_enum", nullable = false)
|
||||
private Integer accountType;
|
||||
|
||||
public static AccountingTagRule create(final CodeValue tagId, final Integer accountType) {
|
||||
return new AccountingTagRule(tagId, accountType);
|
||||
}
|
||||
|
||||
public AccountingTagRule(final CodeValue tagId, final Integer accountType) {
|
||||
this.tagId = tagId;
|
||||
this.accountType = accountType;
|
||||
}
|
||||
|
||||
public void updateAccountingTagRule(final AccountingRule accountingRule) {
|
||||
this.accountingRule = accountingRule;
|
||||
}
|
||||
|
||||
public AccountingTagRule() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public Integer getAccountType() {
|
||||
return this.accountType;
|
||||
}
|
||||
|
||||
public boolean isDebitAccount() {
|
||||
return JournalEntryType.fromInt(this.accountType).isDebitType();
|
||||
}
|
||||
|
||||
public boolean isCreditAccount() {
|
||||
return JournalEntryType.fromInt(this.accountType).isCreditType();
|
||||
}
|
||||
|
||||
public Long getTagId() {
|
||||
return this.tagId.getId();
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.mifosplatform.accounting.rule.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class AccountingRuleDataException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public AccountingRuleDataException(final String debitOrCreditAccount, final String debitOrCreditTags) {
|
||||
super("error.msg.accounting.rule." + debitOrCreditAccount + ".or." + debitOrCreditTags + ".required", debitOrCreditAccount + " or "
|
||||
+ debitOrCreditTags + " required", debitOrCreditAccount, debitOrCreditTags);
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -16,5 +16,9 @@ public class AccountingRuleDuplicateException extends AbstractPlatformDomainRule
|
||||
public AccountingRuleDuplicateException(String name) {
|
||||
super("error.msg.accounting.rule.duplicate", "An accounting rule with the name " + name + " already exists" + name);
|
||||
}
|
||||
|
||||
public AccountingRuleDuplicateException() {
|
||||
super("error.msg.accounting.rule.tag.duplicate", "The accounting rule already have the tags which you defined");
|
||||
}
|
||||
|
||||
}
|
||||
+169
-19
@@ -6,56 +6,206 @@
|
||||
package org.mifosplatform.accounting.rule.serialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.accounting.rule.api.AccountingRuleJsonInputParams;
|
||||
import org.mifosplatform.accounting.rule.command.AccountingRuleCommand;
|
||||
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.serialization.AbstractFromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.portfolio.loanaccount.guarantor.command.GuarantorCommand;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Implementation of {@link FromApiJsonDeserializer} for
|
||||
* {@link GuarantorCommand}'s.
|
||||
*/
|
||||
@Component
|
||||
public final class AccountingRuleCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<AccountingRuleCommand> {
|
||||
public class AccountingRuleCommandFromApiJsonDeserializer {
|
||||
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
*/
|
||||
private final Set<String> supportedParameters = AccountingRuleJsonInputParams.getAllValues();
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AccountingRuleCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonfromApiJsonHelper;
|
||||
public AccountingRuleCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingRuleCommand commandFromApiJson(final String json) {
|
||||
public void validateForCreate(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
final Set<String> supportedParameters = AccountingRuleJsonInputParams.getAllValues();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("AccountingRule");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final Long id = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ID.getValue(), element);
|
||||
final Long officeId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue(), element);
|
||||
final Long accountToDebitId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(accountToDebitId)
|
||||
.ignoreIfNull().integerGreaterThanZero();
|
||||
|
||||
final Long accountToCreditId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(accountToCreditId)
|
||||
.ignoreIfNull().integerGreaterThanZero();
|
||||
|
||||
final Long officeId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(officeId).notNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.NAME.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(name).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String description = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(description).ignoreIfNull()
|
||||
.notExceedingLengthOf(500);
|
||||
|
||||
final String[] creditTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()).value(creditTags).ignoreIfNull()
|
||||
.arrayNotEmpty();
|
||||
validateCreditOrDebitTagArray(creditTags, baseDataValidator, AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
|
||||
|
||||
final String[] debitTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()).value(debitTags).ignoreIfNull()
|
||||
.arrayNotEmpty();
|
||||
validateCreditOrDebitTagArray(debitTags, baseDataValidator, AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
|
||||
|
||||
if (creditTags == null && accountToCreditId == null) {
|
||||
final String creditTag = AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue();
|
||||
final String creditAccount = AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue();
|
||||
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(creditAccount).append(".or.")
|
||||
.append(creditTag).append(".required");
|
||||
final StringBuilder defaultUserMessage = new StringBuilder("The parameter ").append(creditAccount).append(" or")
|
||||
.append(creditTag).append(" required");
|
||||
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultUserMessage.toString(),
|
||||
creditAccount + "," + creditTag, new Object[] { creditAccount, creditTag });
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (debitTags == null && accountToDebitId == null) {
|
||||
final String debitTag = AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue();
|
||||
final String debitAccount = AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue();
|
||||
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(debitAccount).append(".or.")
|
||||
.append(debitTag).append(".required");
|
||||
final StringBuilder defaultUserMessage = new StringBuilder("The parameter ").append(debitAccount).append(" or")
|
||||
.append(debitTag).append(" required");
|
||||
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultUserMessage.toString(),
|
||||
debitAccount + "," + debitTag, new Object[] { debitAccount, debitTag });
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
final String allowMultipleCredits = this.fromApiJsonHelper.extractStringNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
|
||||
.value(allowMultipleCredits).ignoreIfNull().notBlank();
|
||||
final Boolean allowMultipleCreditEntries = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
|
||||
.value(allowMultipleCreditEntries).ignoreIfNull();
|
||||
final String allowMultipleDebits = this.fromApiJsonHelper.extractStringNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
|
||||
.value(allowMultipleDebits).ignoreIfNull().notBlank();
|
||||
final Boolean allowMultipleDebitEntries = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
|
||||
.value(allowMultipleDebitEntries).ignoreIfNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
public void validateCreditOrDebitTagArray(final String[] creditOrDebitTagArray, final DataValidatorBuilder baseDataValidator,
|
||||
final String parameter) {
|
||||
if (creditOrDebitTagArray != null && !ObjectUtils.isEmpty(creditOrDebitTagArray)) {
|
||||
for (final String tag : creditOrDebitTagArray) {
|
||||
baseDataValidator.reset().parameter(parameter).value(tag).ignoreIfNull().notBlank().longGreaterThanZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("AccountingRule");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final Long accountToDebitId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue()).value(accountToDebitId)
|
||||
.ignoreIfNull().notBlank().integerGreaterThanZero();
|
||||
|
||||
final Long accountToCreditId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue()).value(accountToCreditId)
|
||||
.ignoreIfNull().notBlank().integerGreaterThanZero();
|
||||
|
||||
final Long officeId = this.fromApiJsonHelper.extractLongNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.OFFICE_ID.getValue()).value(officeId).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.NAME.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.NAME.getValue()).value(name).ignoreIfNull().notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
final String description = this.fromApiJsonHelper.extractStringNamed(AccountingRuleJsonInputParams.DESCRIPTION.getValue(), element);
|
||||
|
||||
return new AccountingRuleCommand(id, officeId, accountToDebitId, accountToCreditId, name, description);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DESCRIPTION.getValue()).value(description).ignoreIfNull()
|
||||
.notExceedingLengthOf(500);
|
||||
|
||||
final String[] creditTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()).value(creditTags).ignoreIfNull()
|
||||
.arrayNotEmpty();
|
||||
validateCreditOrDebitTagArray(creditTags, baseDataValidator, AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
|
||||
|
||||
final String[] debitTags = this.fromApiJsonHelper.extractArrayNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()).value(debitTags).ignoreIfNull()
|
||||
.arrayNotEmpty();
|
||||
validateCreditOrDebitTagArray(debitTags, baseDataValidator, AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
|
||||
|
||||
final String allowMultipleCredits = this.fromApiJsonHelper.extractStringNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
|
||||
.value(allowMultipleCredits).ignoreIfNull().notBlank();
|
||||
final Boolean allowMultipleCreditEntries = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())
|
||||
.value(allowMultipleCreditEntries).ignoreIfNull();
|
||||
final String allowMultipleDebits = this.fromApiJsonHelper.extractStringNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
|
||||
.value(allowMultipleDebits).ignoreIfNull().notBlank();
|
||||
final Boolean allowMultipleDebitEntries = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue(), element);
|
||||
baseDataValidator.reset().parameter(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())
|
||||
.value(allowMultipleDebitEntries).ignoreIfNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ import org.mifosplatform.accounting.rule.data.AccountingRuleData;
|
||||
|
||||
public interface AccountingRuleReadPlatformService {
|
||||
|
||||
List<AccountingRuleData> retrieveAllAccountingRules(Long OfficeId);
|
||||
List<AccountingRuleData> retrieveAllAccountingRules(Long OfficeId, boolean isAssociationParametersExists);
|
||||
|
||||
AccountingRuleData retrieveAccountingRuleById(long accountingRuleId);
|
||||
AccountingRuleData retrieveAccountingRuleById(Long accountingRuleId);
|
||||
|
||||
}
|
||||
|
||||
+125
-45
@@ -7,16 +7,28 @@ package org.mifosplatform.accounting.rule.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mifosplatform.accounting.common.AccountingEnumerations;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
|
||||
import org.mifosplatform.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
|
||||
import org.mifosplatform.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.mifosplatform.accounting.rule.data.AccountingRuleData;
|
||||
import org.mifosplatform.accounting.rule.data.AccountingTagRuleData;
|
||||
import org.mifosplatform.accounting.rule.exception.AccountingRuleNotFoundException;
|
||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
|
||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -24,29 +36,37 @@ import org.springframework.stereotype.Service;
|
||||
public class AccountingRuleReadPlatformServiceImpl implements AccountingRuleReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final GLAccountReadPlatformService glAccountReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public AccountingRuleReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
|
||||
public AccountingRuleReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource,
|
||||
final GLAccountReadPlatformService glAccountReadPlatformService) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.glAccountReadPlatformService = glAccountReadPlatformService;
|
||||
}
|
||||
|
||||
private static final class AccountingRuleMapper implements RowMapper<AccountingRuleData> {
|
||||
private static final class AccountingRuleDataExtractor implements ResultSetExtractor<Map<Long, AccountingRuleData>> {
|
||||
|
||||
private final String schemaSql;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final GLAccountReadPlatformService glAccountReadPlatformService;
|
||||
private final boolean isAssociationParametersExists;
|
||||
|
||||
public AccountingRuleMapper() {
|
||||
public AccountingRuleDataExtractor(final JdbcTemplate jdbcTemplate,
|
||||
final GLAccountReadPlatformService glAccountReadPlatformService, final boolean isAssociationParametersExists) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.glAccountReadPlatformService = glAccountReadPlatformService;
|
||||
this.isAssociationParametersExists = isAssociationParametersExists;
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("distinct rule.id as id,rule.name as name, rule.office_id as officeId,office.name as officeName,")
|
||||
.append(" rule.debit_account_id as debitAccountId,rule.credit_account_id as creditAccountId,")
|
||||
.append(" rule.description as description, rule.system_defined as systemDefined, ")
|
||||
.append("debitAccount.glName as debitAccountName, debitAccount.glCode as debitAccountGLCode,")
|
||||
.append("creditAccount.glName as creditAccountName, creditAccount.glCode as creditAccountGLCode ")
|
||||
.append("from (Select gl.id as id,gl.name as glName, gl.gl_code as glCode from acc_gl_account gl,acc_accounting_rule rule ")
|
||||
.append("where gl.id=rule.debit_account_id ) as debitAccount,")
|
||||
.append("(Select gl.id as id,gl.name as glName, gl.gl_code as glCode from acc_gl_account gl,acc_accounting_rule rule ")
|
||||
.append("where gl.id=rule.credit_account_id ) as creditAccount,")
|
||||
.append("acc_accounting_rule rule left join m_office office on rule.office_id=office.id ")
|
||||
.append("where debitAccount.id=rule.debit_account_id and creditAccount.id = rule.credit_account_id");
|
||||
sqlBuilder
|
||||
.append(" rule.id as id,rule.name as name, rule.office_id as officeId,office.name as officeName,")
|
||||
.append(" rule.description as description, rule.system_defined as systemDefined, rule.allow_multiple_debits as allowMultipleDebitEntries, rule.allow_multiple_credits as allowMultipleCreditEntries, ")
|
||||
.append("debitAccount.id AS debitAccountId, debitAccount.name as debitAccountName, debitAccount.gl_code as debitAccountGLCode, ")
|
||||
.append("creditAccount.id AS creditAccountId, creditAccount.name as creditAccountName, creditAccount.gl_code as creditAccountGLCode")
|
||||
.append(" from m_office AS office, acc_accounting_rule AS rule ")
|
||||
.append(" LEFT JOIN acc_gl_account AS creditAccount ON rule.credit_account_id = creditAccount.id ")
|
||||
.append(" LEFT JOIN acc_gl_account AS debitAccount ON rule.debit_account_id = debitAccount.id ")
|
||||
.append("WHERE office.id=rule.office_id ");
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
@@ -55,52 +75,112 @@ public class AccountingRuleReadPlatformServiceImpl implements AccountingRuleRead
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingRuleData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
public Map<Long, AccountingRuleData> extractData(final ResultSet rs) throws SQLException, DataAccessException {
|
||||
final Map<Long, AccountingRuleData> extractedData = new HashMap<Long, AccountingRuleData>();
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final Long officeId = JdbcSupport.getLong(rs, "officeId");
|
||||
final String officeName = rs.getString("officeName");
|
||||
final String name = rs.getString("name");
|
||||
final String description = rs.getString("description");
|
||||
final Long accountToDebitId = rs.getLong("debitAccountId");
|
||||
final Long accountToCreditId = rs.getLong("creditAccountId");
|
||||
final boolean systemDefined = rs.getBoolean("systemDefined");
|
||||
final String debitAccountName = rs.getString("debitAccountName");
|
||||
final String creditAccountName = rs.getString("creditAccountName");
|
||||
final String debitAccountGLCode = rs.getString("debitAccountGLCode");
|
||||
final String creditAccountGLCode = rs.getString("creditAccountGLCode");
|
||||
|
||||
final GLAccountData debitAccountData = new GLAccountData(accountToDebitId, debitAccountName, debitAccountGLCode);
|
||||
final GLAccountData creditAccountData = new GLAccountData(accountToCreditId, creditAccountName, creditAccountGLCode);
|
||||
while (rs.next()) {
|
||||
final Long id = rs.getLong("id");
|
||||
AccountingRuleData accountingRuleData = extractedData.get(id);
|
||||
if (accountingRuleData == null) {
|
||||
final Long officeId = JdbcSupport.getLong(rs, "officeId");
|
||||
final String officeName = rs.getString("officeName");
|
||||
final String name = rs.getString("name");
|
||||
final String description = rs.getString("description");
|
||||
final Long accountToDebitId = JdbcSupport.getLong(rs, "debitAccountId");
|
||||
final Long accountToCreditId = JdbcSupport.getLong(rs, "creditAccountId");
|
||||
final boolean systemDefined = rs.getBoolean("systemDefined");
|
||||
final boolean allowMultipleDebitEntries = rs.getBoolean("allowMultipleDebitEntries");
|
||||
final boolean allowMultipleCreditEntries = rs.getBoolean("allowMultipleCreditEntries");
|
||||
final String debitAccountName = rs.getString("debitAccountName");
|
||||
final String creditAccountName = rs.getString("creditAccountName");
|
||||
final String debitAccountGLCode = rs.getString("debitAccountGLCode");
|
||||
final String creditAccountGLCode = rs.getString("creditAccountGLCode");
|
||||
|
||||
return new AccountingRuleData(id, officeId, officeName, name, description, systemDefined, debitAccountData, creditAccountData);
|
||||
final GLAccountData debitAccountData = accountToDebitId == null ? null : new GLAccountData(accountToDebitId,
|
||||
debitAccountName, debitAccountGLCode);
|
||||
final GLAccountData creditAccountData = accountToCreditId == null ? null : new GLAccountData(accountToCreditId,
|
||||
creditAccountName, creditAccountGLCode);
|
||||
accountingRuleData = new AccountingRuleData(id, officeId, officeName, name, description, systemDefined,
|
||||
allowMultipleDebitEntries, allowMultipleCreditEntries, debitAccountData, creditAccountData);
|
||||
}
|
||||
if (accountingRuleData.getCreditAccountHead() == null) {
|
||||
final List<AccountingTagRuleData> creditTags = !this.isAssociationParametersExists ? getCreditOrDebitTags(id,
|
||||
JournalEntryType.CREDIT.getValue()) : null;
|
||||
final List<GLAccountDataForLookup> creditAccounts = this.isAssociationParametersExists ? this.glAccountReadPlatformService
|
||||
.retrieveAccountsByTagId(id, JournalEntryType.CREDIT.getValue()) : null;
|
||||
accountingRuleData = new AccountingRuleData(accountingRuleData, creditTags, creditAccounts,
|
||||
accountingRuleData.getDebitTags(), accountingRuleData.getDebitAccounts());
|
||||
}
|
||||
if (accountingRuleData.getDebitAccountHead() == null) {
|
||||
final List<AccountingTagRuleData> debitTags = !this.isAssociationParametersExists ? getCreditOrDebitTags(id,
|
||||
JournalEntryType.DEBIT.getValue()) : null;
|
||||
final List<GLAccountDataForLookup> debitAccounts = this.isAssociationParametersExists ? this.glAccountReadPlatformService
|
||||
.retrieveAccountsByTagId(id, JournalEntryType.DEBIT.getValue()) : null;
|
||||
accountingRuleData = new AccountingRuleData(accountingRuleData, accountingRuleData.getCreditTags(),
|
||||
accountingRuleData.getCreditAccounts(), debitTags, debitAccounts);
|
||||
}
|
||||
extractedData.put(id, accountingRuleData);
|
||||
}
|
||||
return extractedData;
|
||||
}
|
||||
|
||||
private List<AccountingTagRuleData> getCreditOrDebitTags(final Long creditOrDebitAccount, final Integer transactionType) {
|
||||
final AccountingTagRuleDataMapper mapper = new AccountingTagRuleDataMapper();
|
||||
final String taggedAccountsSchema = "Select " + mapper.taggedAccountSchema() + " where rule.id = ? and tag.acc_type_enum=?";
|
||||
return this.jdbcTemplate.query(taggedAccountsSchema, mapper, new Object[] { creditOrDebitAccount, transactionType });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountingRuleData> retrieveAllAccountingRules(Long officeId) {
|
||||
final AccountingRuleMapper rm = new AccountingRuleMapper();
|
||||
Object[] arguments = new Object[]{};
|
||||
String sql = "select " + rm.schema() +" and system_defined=0 ";
|
||||
if(officeId != null) {
|
||||
sql = sql +" and office.id = ?";
|
||||
arguments = new Object[] { officeId };
|
||||
public List<AccountingRuleData> retrieveAllAccountingRules(final Long officeId, final boolean isAssociationParametersExists) {
|
||||
final AccountingRuleDataExtractor resultSetExtractor = new AccountingRuleDataExtractor(this.jdbcTemplate,
|
||||
this.glAccountReadPlatformService, isAssociationParametersExists);
|
||||
Object[] arguments = new Object[] {};
|
||||
String sql = "select " + resultSetExtractor.schema() + " and system_defined=0 ";
|
||||
if (officeId != null) {
|
||||
sql = sql + " and office.id = ?";
|
||||
arguments = new Object[] { officeId };
|
||||
}
|
||||
sql = sql + " order by rule.id asc";
|
||||
return this.jdbcTemplate.query(sql, rm, arguments);
|
||||
sql = sql + " order by rule.id asc";
|
||||
final Map<Long, AccountingRuleData> extractedData = this.jdbcTemplate.query(sql, resultSetExtractor, arguments);
|
||||
return new ArrayList<AccountingRuleData>(extractedData.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingRuleData retrieveAccountingRuleById(long accountingRuleId) {
|
||||
public AccountingRuleData retrieveAccountingRuleById(final Long accountingRuleId) {
|
||||
try {
|
||||
final AccountingRuleDataExtractor resultSetExtractor = new AccountingRuleDataExtractor(this.jdbcTemplate,
|
||||
this.glAccountReadPlatformService, false);
|
||||
final String sql = "select " + resultSetExtractor.schema() + " and rule.id = ?";
|
||||
|
||||
final AccountingRuleMapper rm = new AccountingRuleMapper();
|
||||
final String sql = "select " + rm.schema() + " and rule.id = ?";
|
||||
|
||||
final AccountingRuleData accountingRuleData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { accountingRuleId });
|
||||
final Map<Long, AccountingRuleData> extractedData = this.jdbcTemplate.query(sql, resultSetExtractor,
|
||||
new Object[] { accountingRuleId });
|
||||
final AccountingRuleData accountingRuleData = extractedData.get(accountingRuleId);
|
||||
if (accountingRuleData == null) { throw new AccountingRuleNotFoundException(accountingRuleId); }
|
||||
return accountingRuleData;
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountingRuleNotFoundException(accountingRuleId);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AccountingTagRuleDataMapper implements RowMapper<AccountingTagRuleData> {
|
||||
|
||||
public String taggedAccountSchema() {
|
||||
return " tag.id as id,tag.tag_id as tagId, tag.acc_type_enum as transactionType, cv.code_value as tagName from m_code_value cv join acc_rule_tags tag on tag.tag_id=cv.id "
|
||||
+ "join acc_accounting_rule rule on tag.acc_rule_id=rule.id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountingTagRuleData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
final Long id = rs.getLong("id");
|
||||
final Long tagId = rs.getLong("tagId");
|
||||
final Integer transactionType = JdbcSupport.getInteger(rs, "transactionType");
|
||||
final String tagName = rs.getString("tagName");
|
||||
final CodeValueData tag = CodeValueData.instance(tagId, tagName);
|
||||
final EnumOptionData transactionTypeEnum = AccountingEnumerations.journalEntryType(transactionType);
|
||||
return new AccountingTagRuleData(id, tag, transactionTypeEnum);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+236
-57
@@ -5,18 +5,30 @@
|
||||
*/
|
||||
package org.mifosplatform.accounting.rule.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.accounting.closure.api.GLClosureJsonInputParams;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccountRepositoryWrapper;
|
||||
import org.mifosplatform.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.mifosplatform.accounting.rule.api.AccountingRuleJsonInputParams;
|
||||
import org.mifosplatform.accounting.rule.command.AccountingRuleCommand;
|
||||
import org.mifosplatform.accounting.rule.domain.AccountingRule;
|
||||
import org.mifosplatform.accounting.rule.domain.AccountingRuleRepository;
|
||||
import org.mifosplatform.accounting.rule.domain.AccountingRuleRepositoryWrapper;
|
||||
import org.mifosplatform.accounting.rule.domain.AccountingTagRule;
|
||||
import org.mifosplatform.accounting.rule.exception.AccountingRuleDataException;
|
||||
import org.mifosplatform.accounting.rule.exception.AccountingRuleDuplicateException;
|
||||
import org.mifosplatform.accounting.rule.serialization.AccountingRuleCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
|
||||
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepository;
|
||||
import org.mifosplatform.infrastructure.codes.exception.CodeValueNotFoundException;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
@@ -41,16 +53,19 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
|
||||
private final GLAccountRepositoryWrapper accountRepositoryWrapper;
|
||||
private final OfficeRepository officeRepository;
|
||||
private final AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final CodeValueRepository codeValueRepository;
|
||||
|
||||
@Autowired
|
||||
public AccountingRuleWritePlatformServiceJpaRepositoryImpl(final AccountingRuleRepositoryWrapper accountingRuleRepositoryWrapper,
|
||||
final GLAccountRepositoryWrapper accountRepositoryWrapper, final OfficeRepository officeRepository,
|
||||
final AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer, final AccountingRuleRepository ruleRepository) {
|
||||
final AccountingRuleRepository ruleRepository, final AccountingRuleCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
final CodeValueRepository codeValueRepository) {
|
||||
this.accountRepositoryWrapper = accountRepositoryWrapper;
|
||||
this.officeRepository = officeRepository;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
this.accountingRuleRepository = ruleRepository;
|
||||
this.accountingRuleRepositoryWrapper = accountingRuleRepositoryWrapper;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
this.codeValueRepository = codeValueRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +74,9 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
|
||||
*/
|
||||
private void handleAccountingRuleIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
|
||||
final Throwable realCause = dve.getMostSpecificCause();
|
||||
if (realCause.getMessage().contains("accounting_rule_name_unique")) { throw new AccountingRuleDuplicateException(
|
||||
command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue())); }
|
||||
if (realCause.getMessage().contains("accounting_rule_name_unique")) {
|
||||
throw new AccountingRuleDuplicateException(command.stringValueOfParameterNamed(AccountingRuleJsonInputParams.NAME.getValue()));
|
||||
} else if (realCause.getMessage().contains("UNIQUE_ACCOUNT_RULE_TAGS")) { throw new AccountingRuleDuplicateException(); }
|
||||
|
||||
logger.error(dve.getMessage(), dve);
|
||||
throw new PlatformDataIntegrityException("error.msg.accounting.rule.unknown.data.integrity.issue",
|
||||
@@ -69,10 +85,10 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createAccountingRule(JsonCommand command) {
|
||||
public CommandProcessingResult createAccountingRule(final JsonCommand command) {
|
||||
try {
|
||||
final AccountingRuleCommand accountingRuleCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
|
||||
accountingRuleCommand.validateForCreate();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
// check office is valid
|
||||
final Long officeId = command.longValueOfParameterNamed(GLClosureJsonInputParams.OFFICE_ID.getValue());
|
||||
@@ -82,15 +98,8 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
|
||||
if (office == null) { throw new OfficeNotFoundException(officeId); }
|
||||
}
|
||||
|
||||
// get the GL Accounts to Debit and Credit
|
||||
final Long accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
|
||||
final Long accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
|
||||
final GLAccount accountToDebit = accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
|
||||
final GLAccount accountToCredit = accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
|
||||
|
||||
final AccountingRule accountingRule = AccountingRule.fromJson(office, accountToDebit, accountToCredit, command);
|
||||
final AccountingRule accountingRule = assembleAccountingRuleAndTags(office, command);
|
||||
this.accountingRuleRepository.saveAndFlush(accountingRule);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId)
|
||||
.withEntityId(accountingRule.getId()).build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
@@ -99,59 +108,229 @@ public class AccountingRuleWritePlatformServiceJpaRepositoryImpl implements Acco
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateAccountingRule(Long accountingRuleId, JsonCommand command) {
|
||||
final AccountingRuleCommand accountingRuleCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json());
|
||||
accountingRuleCommand.validateForUpdate();
|
||||
private AccountingRule assembleAccountingRuleAndTags(final Office office, final JsonCommand command) {
|
||||
// get the GL Accounts or tags to Debit and Credit
|
||||
final String[] debitTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
|
||||
final String[] creditTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
|
||||
final Set<String> incomingDebitTags = debitTags == null ? new HashSet<String>(): new HashSet<String>(Arrays.asList(debitTags));
|
||||
final Set<String> incomingCreditTags = creditTags == null ? new HashSet<String>() : new HashSet<String>(Arrays.asList(creditTags));
|
||||
final Long accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
|
||||
final Long accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
|
||||
|
||||
Long officeId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
|
||||
officeId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue());
|
||||
boolean allowMultipleCreditEntries = false;
|
||||
boolean allowMultipleDebitEntries = false;
|
||||
GLAccount debitAccount = null;
|
||||
GLAccount creditAccount = null;
|
||||
List<AccountingTagRule> accountingTagRules = new ArrayList<AccountingTagRule>();
|
||||
|
||||
if ((accountToDebitId != null && debitTags != null) || (accountToDebitId == null && debitTags == null)) {
|
||||
throw new AccountingRuleDataException(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(),
|
||||
AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
|
||||
} else if (accountToDebitId != null) {
|
||||
debitAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
|
||||
} else if (debitTags != null) {
|
||||
accountingTagRules = saveDebitOrCreditTags(incomingDebitTags, JournalEntryType.DEBIT, accountingTagRules);
|
||||
allowMultipleDebitEntries = command
|
||||
.booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue());
|
||||
}
|
||||
|
||||
Long accountToDebitId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
|
||||
accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
|
||||
if ((accountToCreditId != null && creditTags != null) || (accountToCreditId == null && creditTags == null)) {
|
||||
throw new AccountingRuleDataException(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
|
||||
AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
|
||||
} else if (accountToCreditId != null) {
|
||||
creditAccount = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
|
||||
} else if (creditTags != null) {
|
||||
accountingTagRules = saveDebitOrCreditTags(incomingCreditTags, JournalEntryType.CREDIT, accountingTagRules);
|
||||
allowMultipleCreditEntries = command
|
||||
.booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue());
|
||||
}
|
||||
|
||||
Long accountToCreditId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
|
||||
accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
|
||||
}
|
||||
final AccountingRule accountingRule = AccountingRule.fromJson(office, debitAccount, creditAccount, command,
|
||||
allowMultipleCreditEntries, allowMultipleDebitEntries);
|
||||
accountingRule.updateAccountingRuleForTags(accountingTagRules);
|
||||
|
||||
final AccountingRule accountingRule = this.accountingRuleRepositoryWrapper.findOneWithNotFoundDetection(accountingRuleId);
|
||||
final Map<String, Object> changesOnly = accountingRule.update(command);
|
||||
|
||||
if (accountToDebitId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
|
||||
final GLAccount accountToDebit = accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
|
||||
accountingRule.setAccountToDebit(accountToDebit);
|
||||
}
|
||||
|
||||
if (accountToCreditId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
|
||||
final GLAccount accountToCredit = accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
|
||||
accountingRule.setAccountToCredit(accountToCredit);
|
||||
}
|
||||
|
||||
if (officeId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
|
||||
final Office userOffice = this.officeRepository.findOne(officeId);
|
||||
if (userOffice == null) { throw new OfficeNotFoundException(officeId); }
|
||||
accountingRule.setOffice(userOffice);
|
||||
}
|
||||
|
||||
if (!changesOnly.isEmpty()) {
|
||||
this.accountingRuleRepository.saveAndFlush(accountingRule);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(accountingRule.getId())
|
||||
.with(changesOnly).build();
|
||||
return accountingRule;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult deleteAccountingRule(Long accountingRuleId) {
|
||||
public CommandProcessingResult updateAccountingRule(final Long accountingRuleId, final JsonCommand command) {
|
||||
|
||||
try {
|
||||
|
||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
Long officeId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
|
||||
officeId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.OFFICE_ID.getValue());
|
||||
}
|
||||
|
||||
Long accountToDebitId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
|
||||
accountToDebitId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue());
|
||||
}
|
||||
|
||||
Long accountToCreditId = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
|
||||
accountToCreditId = command.longValueOfParameterNamed(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue());
|
||||
}
|
||||
|
||||
String[] debitTags = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue())) {
|
||||
debitTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue());
|
||||
}
|
||||
|
||||
String[] creditTags = null;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue())) {
|
||||
creditTags = command.arrayValueOfParameterNamed(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue());
|
||||
}
|
||||
|
||||
if (accountToDebitId != null && debitTags != null) { throw new AccountingRuleDataException(
|
||||
AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue(), AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue()); }
|
||||
|
||||
if (accountToCreditId != null && creditTags != null) { throw new AccountingRuleDataException(
|
||||
AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue(),
|
||||
AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue()); }
|
||||
|
||||
boolean allowMultipleCreditEntries = false;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue())) {
|
||||
allowMultipleCreditEntries = command
|
||||
.booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_CREDIT_ENTRIES.getValue());
|
||||
}
|
||||
|
||||
boolean allowMultipleDebitEntries = false;
|
||||
if (command.parameterExists(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue())) {
|
||||
allowMultipleDebitEntries = command
|
||||
.booleanPrimitiveValueOfParameterNamed(AccountingRuleJsonInputParams.ALLOW_MULTIPLE_DEBIT_ENTRIES.getValue());
|
||||
}
|
||||
|
||||
final AccountingRule accountingRule = this.accountingRuleRepositoryWrapper.findOneWithNotFoundDetection(accountingRuleId);
|
||||
final Map<String, Object> changesOnly = accountingRule.update(command);
|
||||
|
||||
if (accountToDebitId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_DEBIT.getValue())) {
|
||||
final GLAccount accountToDebit = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToDebitId);
|
||||
accountingRule.updateDebitAccount(accountToDebit);
|
||||
accountingRule.updateTags(JournalEntryType.CREDIT);
|
||||
}
|
||||
|
||||
if (accountToCreditId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.ACCOUNT_TO_CREDIT.getValue())) {
|
||||
final GLAccount accountToCredit = this.accountRepositoryWrapper.findOneWithNotFoundDetection(accountToCreditId);
|
||||
accountingRule.updateCreditAccount(accountToCredit);
|
||||
accountingRule.updateTags(JournalEntryType.DEBIT);
|
||||
}
|
||||
|
||||
if (creditTags != null && creditTags.length > 0
|
||||
&& command.parameterExists(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue())) {
|
||||
|
||||
final Set<String> creditTagsToAdd = determineCreditTagToAddAndRemoveOldTags(creditTags, JournalEntryType.CREDIT,
|
||||
accountingRule);
|
||||
|
||||
if (!creditTagsToAdd.isEmpty()) {
|
||||
List<AccountingTagRule> accountingTagRules = new ArrayList<AccountingTagRule>();
|
||||
accountingTagRules = saveDebitOrCreditTags(creditTagsToAdd, JournalEntryType.CREDIT, accountingTagRules);
|
||||
accountingRule.updateAccountingRuleForTags(accountingTagRules);
|
||||
accountingRule.updateCreditAccount(null);
|
||||
if (allowMultipleCreditEntries) {
|
||||
accountingRule.updateAllowMultipleCreditEntries(allowMultipleCreditEntries);
|
||||
}
|
||||
changesOnly.put(AccountingRuleJsonInputParams.CREDIT_ACCOUNT_TAGS.getValue(), creditTagsToAdd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (debitTags != null && debitTags.length > 0
|
||||
&& command.parameterExists(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue())) {
|
||||
final Set<String> debitTagsToAdd = determineCreditTagToAddAndRemoveOldTags(debitTags, JournalEntryType.DEBIT,
|
||||
accountingRule);
|
||||
if (!debitTagsToAdd.isEmpty()) {
|
||||
List<AccountingTagRule> accountingTagRules = new ArrayList<AccountingTagRule>();
|
||||
accountingTagRules = saveDebitOrCreditTags(debitTagsToAdd, JournalEntryType.DEBIT, accountingTagRules);
|
||||
accountingRule.updateAccountingRuleForTags(accountingTagRules);
|
||||
accountingRule.updateDebitAccount(null);
|
||||
if (allowMultipleDebitEntries) {
|
||||
accountingRule.updateAllowMultipleDebitEntries(allowMultipleDebitEntries);
|
||||
}
|
||||
changesOnly.put(AccountingRuleJsonInputParams.DEBIT_ACCOUNT_TAGS.getValue(), debitTagsToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
if (officeId != null && changesOnly.containsKey(AccountingRuleJsonInputParams.OFFICE_ID.getValue())) {
|
||||
final Office userOffice = this.officeRepository.findOne(officeId);
|
||||
if (userOffice == null) { throw new OfficeNotFoundException(officeId); }
|
||||
accountingRule.setOffice(userOffice);
|
||||
}
|
||||
|
||||
if (!changesOnly.isEmpty()) {
|
||||
this.accountingRuleRepository.saveAndFlush(accountingRule);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(accountingRule.getId())
|
||||
.with(changesOnly).build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
handleAccountingRuleIntegrityIssues(command, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Set<String> determineCreditTagToAddAndRemoveOldTags(final String[] creditOrDebitTags, final JournalEntryType type,
|
||||
final AccountingRule accountingRule) {
|
||||
|
||||
final Set<String> incomingTags = new HashSet<String>(Arrays.asList(creditOrDebitTags));
|
||||
final Set<AccountingTagRule> existingTags = accountingRule.getAccountingTagRulesByType(type);
|
||||
final Set<String> existingTagIds = retrieveExistingTagIds(existingTags);
|
||||
final Set<String> tagsToAdd = new HashSet<String>();
|
||||
final Set<String> tagsToRemove = existingTagIds;
|
||||
final Map<Long, AccountingTagRule> accountsToRemove = new HashMap<Long, AccountingTagRule>();
|
||||
|
||||
for (final String tagId : incomingTags) {
|
||||
if (existingTagIds.contains(tagId)) {
|
||||
tagsToRemove.remove(tagId);
|
||||
} else {
|
||||
tagsToAdd.add(tagId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tagsToRemove.isEmpty()) {
|
||||
for (final String tagId : tagsToRemove) {
|
||||
for (final AccountingTagRule accountingTagRule : existingTags) {
|
||||
if (tagId.equals(accountingTagRule.getTagId().toString())) {
|
||||
accountsToRemove.put(accountingTagRule.getId(), accountingTagRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
accountingRule.removeOldTags(new ArrayList<AccountingTagRule>(accountsToRemove.values()));
|
||||
}
|
||||
return tagsToAdd;
|
||||
}
|
||||
|
||||
private Set<String> retrieveExistingTagIds(final Set<AccountingTagRule> existingCreditTags) {
|
||||
final Set<String> existingCreditTagIds = new HashSet<String>();
|
||||
for (final AccountingTagRule accountingTagRule : existingCreditTags) {
|
||||
existingCreditTagIds.add(accountingTagRule.getTagId().toString());
|
||||
}
|
||||
return existingCreditTagIds;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult deleteAccountingRule(final Long accountingRuleId) {
|
||||
final AccountingRule accountingRule = this.accountingRuleRepositoryWrapper.findOneWithNotFoundDetection(accountingRuleId);
|
||||
this.accountingRuleRepository.delete(accountingRule);
|
||||
return new CommandProcessingResultBuilder().withEntityId(accountingRule.getId()).build();
|
||||
}
|
||||
|
||||
private List<AccountingTagRule> saveDebitOrCreditTags(final Set<String> creditOrDebitTagArray, final JournalEntryType transactionType,
|
||||
final List<AccountingTagRule> accountingTagRules) {
|
||||
for (final String creditOrDebitTag : creditOrDebitTagArray) {
|
||||
if (creditOrDebitTag != null && StringUtils.isNotBlank(creditOrDebitTag)) {
|
||||
final Long creditOrDebitTagIdLongValue = Long.valueOf(creditOrDebitTag);
|
||||
final CodeValue creditOrDebitAccount = this.codeValueRepository.findOne(creditOrDebitTagIdLongValue);
|
||||
if (creditOrDebitAccount == null) { throw new CodeValueNotFoundException(creditOrDebitTagIdLongValue); }
|
||||
final AccountingTagRule accountingTagRule = AccountingTagRule.create(creditOrDebitAccount, transactionType.getValue());
|
||||
accountingTagRules.add(accountingTagRule);
|
||||
}
|
||||
}
|
||||
return accountingTagRules;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,21 @@ public class CommandWrapper {
|
||||
return this.actionName.equalsIgnoreCase("CREATE");
|
||||
}
|
||||
|
||||
public boolean isCreateDatatable() {
|
||||
return (this.actionName.equalsIgnoreCase("CREATE") && this.href.startsWith("/datatables/")
|
||||
&& this.entityId == null);
|
||||
}
|
||||
|
||||
public boolean isDeleteDatatable() {
|
||||
return (this.actionName.equalsIgnoreCase("DELETE") && this.href.startsWith("/datatables/")
|
||||
&& this.entityId == null);
|
||||
}
|
||||
|
||||
public boolean isUpdateDatatable() {
|
||||
return (this.actionName.equalsIgnoreCase("UPDATE") && this.href.startsWith("/datatables/")
|
||||
&& this.entityId == null);
|
||||
}
|
||||
|
||||
public String getTaskPermissionName() {
|
||||
return this.taskPermissionName;
|
||||
}
|
||||
|
||||
+27
@@ -356,6 +356,33 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createDBDatatable(final String json) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
|
||||
this.actionName = "CREATE";
|
||||
commonDatatableSettings(datatable, apptableId, datatableId);
|
||||
|
||||
+2
-2
@@ -63,7 +63,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
||||
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
|
||||
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
|
||||
wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(),
|
||||
wrapper.getTransactionId());
|
||||
wrapper.getTransactionId(), wrapper.getHref());
|
||||
|
||||
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
||||
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
|
||||
@@ -86,7 +86,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
|
||||
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
|
||||
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
||||
commandSourceInput.subresourceId());
|
||||
commandSourceInput.subresourceId(), commandSourceInput.getResourceGetUrl());
|
||||
|
||||
final boolean makerCheckerApproval = true;
|
||||
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
|
||||
|
||||
+8
-2
@@ -78,7 +78,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
commandSourceResult.updateJsonTo(changesOnlyJson);
|
||||
}
|
||||
|
||||
if (!result.hasChanges() && wrapper.isUpdateOperation()) {
|
||||
if (!result.hasChanges() && wrapper.isUpdateOperation() && !wrapper.isUpdateDatatable()) {
|
||||
commandSourceResult.updateJsonTo(null);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,13 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
if (wrapper.isConfigurationResource()) {
|
||||
handler = applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isDatatableResource()) {
|
||||
if (wrapper.isCreate()) {
|
||||
if (wrapper.isCreateDatatable()) {
|
||||
handler = applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isDeleteDatatable()) {
|
||||
handler = applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isUpdateDatatable()) {
|
||||
handler = applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isCreate()) {
|
||||
handler = applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isUpdateMultiple()) {
|
||||
handler = applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
||||
|
||||
+4
-1
@@ -10,7 +10,6 @@ package org.mifosplatform.infrastructure.codes.data;
|
||||
*/
|
||||
public class CodeValueData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -32,4 +31,8 @@ public class CodeValueData {
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.mifosplatform.infrastructure.configuration.data;
|
||||
|
||||
public class S3CredentialsData {
|
||||
|
||||
private final String bucketName;
|
||||
private final String accessKey;
|
||||
private final String secretKey;
|
||||
|
||||
public S3CredentialsData(String bucketName, String accessKey, String secretKey) {
|
||||
this.bucketName = bucketName;
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public String getBucketName() {
|
||||
return this.bucketName;
|
||||
}
|
||||
|
||||
public String getAccessKey() {
|
||||
return this.accessKey;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return this.secretKey;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -8,4 +8,5 @@ package org.mifosplatform.infrastructure.configuration.domain;
|
||||
public interface ConfigurationDomainService {
|
||||
|
||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||
boolean isAmazonS3Enabled();
|
||||
}
|
||||
+5
@@ -39,4 +39,9 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
|
||||
return thisTask.hasMakerCheckerEnabled() && property.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAmazonS3Enabled(){
|
||||
return this.globalConfigurationRepository.findOneByName("amazon-S3").isEnabled();
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.mifosplatform.infrastructure.configuration.domain;
|
||||
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@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;
|
||||
|
||||
@Column(name = "value", nullable = false)
|
||||
private String value;
|
||||
|
||||
public ExternalService(final String name, final String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
protected ExternalService() {}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.mifosplatform.infrastructure.configuration.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface ExternalServiceRepository extends JpaRepository<ExternalService, Long>, JpaSpecificationExecutor<ExternalService> {
|
||||
|
||||
ExternalService findOneByName(String name);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.mifosplatform.infrastructure.configuration.service;
|
||||
|
||||
public class ExternalServicesConstants {
|
||||
|
||||
public static final String S3_BUCKET_NAME = "s3_bucket_name";
|
||||
public static final String S3_ACCESS_KEY = "s3_access_key";
|
||||
public static final String S3_SECRET_KEY = "s3_secret_key";
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.mifosplatform.infrastructure.configuration.service;
|
||||
|
||||
import org.mifosplatform.infrastructure.configuration.data.S3CredentialsData;
|
||||
|
||||
public interface ExternalServicesReadPlatformService {
|
||||
|
||||
S3CredentialsData getS3Credentials();
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.mifosplatform.infrastructure.configuration.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.mifosplatform.infrastructure.configuration.data.S3CredentialsData;
|
||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ExternalServicesReadPlatformServiceImpl implements ExternalServicesReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public ExternalServicesReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
private static final class S3CredentialsDataExtractor implements ResultSetExtractor<S3CredentialsData> {
|
||||
|
||||
@Override
|
||||
public S3CredentialsData extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
String accessKey = null;
|
||||
String bucketName = null;
|
||||
String secretKey = null;
|
||||
while (rs.next()) {
|
||||
if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_ACCESS_KEY)) {
|
||||
accessKey = rs.getString("value");
|
||||
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_BUCKET_NAME)) {
|
||||
bucketName = rs.getString("value");
|
||||
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.S3_SECRET_KEY)) {
|
||||
secretKey = rs.getString("value");
|
||||
}
|
||||
}
|
||||
return new S3CredentialsData(bucketName, accessKey, secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public S3CredentialsData getS3Credentials() {
|
||||
final ResultSetExtractor<S3CredentialsData> resultSetExtractor = new S3CredentialsDataExtractor();
|
||||
final String sql = "SELECT es.name, es.value FROM c_external_service es where es.name in('s3_bucket_name','s3_access_key','s3_secret_key')";
|
||||
final S3CredentialsData s3CredentialsData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
|
||||
return s3CredentialsData;
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* 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.infrastructure.core.api;
|
||||
|
||||
public class ApiConstants {
|
||||
|
||||
// TODO:Vishwas Need to move these settings to the Database
|
||||
public static final Integer MAX_FILE_UPLOAD_SIZE_IN_MB = 5;
|
||||
|
||||
// TODO:Vishwas Need to move these settings to the Database
|
||||
public static final Integer MAX_IMAGE_UPLOAD_SIZE_IN_MB = 1;
|
||||
}
|
||||
+13
-5
@@ -45,24 +45,27 @@ public final class JsonCommand {
|
||||
private final String supportedEntityType;
|
||||
private final Long supportedEntityId;
|
||||
private final String transactionId;
|
||||
private final String url;
|
||||
public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper,
|
||||
final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId,
|
||||
final Long loanId, final Long savingsId, final Long codeId, final String supportedEntityType, final Long supportedEntityId,
|
||||
final String transactionId) {
|
||||
final String transactionId, final String url) {
|
||||
return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
|
||||
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId);
|
||||
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId, url);
|
||||
}
|
||||
|
||||
public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
||||
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId) {
|
||||
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
|
||||
final String url) {
|
||||
return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
|
||||
null, null, null, null, null, null);
|
||||
null, null, null, null, null, null, url);
|
||||
}
|
||||
|
||||
public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
||||
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
|
||||
final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId,
|
||||
final String supportedEntityType, final Long supportedEntityId, final String transactionId) {
|
||||
final String supportedEntityType, final Long supportedEntityId, final String transactionId,
|
||||
final String url) {
|
||||
this.commandId = commandId;
|
||||
this.jsonCommand = jsonCommand;
|
||||
this.parsedCommand = parsedCommand;
|
||||
@@ -78,6 +81,7 @@ public final class JsonCommand {
|
||||
this.supportedEntityType = supportedEntityType;
|
||||
this.supportedEntityId = supportedEntityId;
|
||||
this.transactionId = transactionId;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String json() {
|
||||
@@ -144,6 +148,10 @@ public final class JsonCommand {
|
||||
public String getTransactionId() {
|
||||
return this.transactionId;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
|
||||
boolean differenceExists = false;
|
||||
|
||||
+62
-2
@@ -16,6 +16,8 @@ import net.fortuna.ical4j.model.property.RRule;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
public class DataValidatorBuilder {
|
||||
|
||||
private final List<ApiParameterError> dataValidationErrors;
|
||||
@@ -206,12 +208,35 @@ public class DataValidatorBuilder {
|
||||
|
||||
if (value != null) {
|
||||
List<Object> valuesList = Arrays.asList(values);
|
||||
String valuesListStr = StringUtils.join(valuesList, ", ");
|
||||
|
||||
if (!valuesList.contains(this.value)) {
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
.append(".is.not.one.of.expected.enumerations");
|
||||
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be one of [ ")
|
||||
.append(values).append(" ] ").append(".");
|
||||
.append(valuesListStr).append(" ] ").append(".");
|
||||
|
||||
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
|
||||
defaultEnglishMessage.toString(), parameter, this.value, values);
|
||||
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) {
|
||||
if (value == null && ignoreNullValue) { return this; }
|
||||
|
||||
if (value != null) {
|
||||
List<Object> valuesList = Arrays.asList(values);
|
||||
String valuesListStr = StringUtils.join(valuesList, ", ");
|
||||
|
||||
if (valuesList.contains(this.value)) {
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
.append(".is.one.of.unwanted.enumerations");
|
||||
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must not be any of [ ")
|
||||
.append(valuesListStr).append(" ] ").append(".");
|
||||
|
||||
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
|
||||
defaultEnglishMessage.toString(), parameter, this.value, values);
|
||||
@@ -309,6 +334,22 @@ public class DataValidatorBuilder {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataValidatorBuilder jsonArrayNotEmpty() {
|
||||
if (value == null && ignoreNullValue) { return this; }
|
||||
|
||||
JsonArray array = (JsonArray)value;
|
||||
if (value != null && !array.iterator().hasNext()) {
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
.append(".cannot.be.empty");
|
||||
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(
|
||||
" cannot be empty. You must select at least one.");
|
||||
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
|
||||
parameter);
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void expectedArrayButIsNot() {
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
@@ -385,7 +426,7 @@ public class DataValidatorBuilder {
|
||||
}
|
||||
|
||||
public DataValidatorBuilder cantBeBlankWhenParameterProvidedIs(final String parameterName, final Object parameterValue) {
|
||||
if (StringUtils.isNotBlank(value.toString())) { return this; }
|
||||
if (value != null && StringUtils.isNotBlank(value.toString())) { return this; }
|
||||
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
.append(".must.be.provided.when.").append(parameterName).append(".is.").append(parameterValue);
|
||||
@@ -533,4 +574,23 @@ public class DataValidatorBuilder {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataValidatorBuilder matchesRegularExpression(final String expression) {
|
||||
if (value == null && ignoreNullValue) { return this; }
|
||||
|
||||
if (value != null && !value.toString().matches(expression)) {
|
||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||
.append(".does.not.match.regexp");
|
||||
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter)
|
||||
.append(" must match the provided regular expression [ ")
|
||||
.append(expression).append(" ] ").append(".");
|
||||
|
||||
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
|
||||
defaultEnglishMessage.toString(), parameter, this.value, expression);
|
||||
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -13,12 +13,13 @@ import org.springframework.security.authentication.dao.SaltSource;
|
||||
import org.springframework.security.authentication.encoding.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Service(value = "applicationPasswordEncoder")
|
||||
@Scope("singleton")
|
||||
public class DefaultPlatformPasswordEncoder implements PlatformPasswordEncoder {
|
||||
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final SaltSource saltSource;
|
||||
private final SaltSource saltSource;
|
||||
|
||||
@Autowired
|
||||
public DefaultPlatformPasswordEncoder(final PasswordEncoder passwordEncoder, final SaltSource saltSource) {
|
||||
|
||||
+13
@@ -47,6 +47,19 @@ public class JdbcSupport {
|
||||
return (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
|
||||
}
|
||||
|
||||
public static Integer getIntegerDefaultToNullIfZero(final ResultSet rs, final String columnName) throws SQLException {
|
||||
Integer value = (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
|
||||
return defaultToNullIfZero(value);
|
||||
}
|
||||
|
||||
private static Integer defaultToNullIfZero(final Integer value) {
|
||||
Integer result = value;
|
||||
if (result != null && Integer.valueOf(0).equals(value)) {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BigDecimal getBigDecimalDefaultToZeroIfNull(final ResultSet rs, final String columnName) throws SQLException {
|
||||
final BigDecimal value = rs.getBigDecimal(columnName);
|
||||
return defaultToZeroIfNull(value);
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.domain;
|
||||
package org.mifosplatform.infrastructure.core.domain;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 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.infrastructure.core.serialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
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.exception.PlatformDataIntegrityException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Component
|
||||
public class DatatableCommandFromApiJsonDeserializer {
|
||||
|
||||
private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
|
||||
private final static String DATATABLE_COLUMN_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,}[a-zA-Z0-9]$";
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
*/
|
||||
private final Set<String> supportedParametersForCreate = new HashSet<String>(Arrays.asList(
|
||||
"datatableName", "apptableName", "multiRow", "columns"));
|
||||
private final Set<String> supportedParametersForCreateColumns = new HashSet<String>(Arrays.asList(
|
||||
"name", "type", "length", "mandatory", "code"));
|
||||
private final Set<String> supportedParametersForUpdate = new HashSet<String>(Arrays.asList(
|
||||
"apptableName", "changeColumns", "addColumns", "dropColumns"));
|
||||
private final Set<String> supportedParametersForAddColumns = new HashSet<String>(Arrays.asList(
|
||||
"name", "type", "length", "mandatory", "after", "code"));
|
||||
private final Set<String> supportedParametersForChangeColumns = new HashSet<String>(Arrays.asList(
|
||||
"name", "newName", "mandatory", "after", "code", "newCode"));
|
||||
private final Set<String> supportedParametersForDropColumns = new HashSet<String>(Arrays.asList(
|
||||
"name"));
|
||||
private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" };
|
||||
private final Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_office" };
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public DatatableCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) {
|
||||
final String type = fromApiJsonHelper.extractStringNamed("type", column);
|
||||
baseDataValidator.reset().parameter("type").value(type).notBlank()
|
||||
.isOneOfTheseValues(supportedColumnTypes);
|
||||
|
||||
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column);
|
||||
if (type != null && type.equals("String")) {
|
||||
if (length != null) {
|
||||
baseDataValidator.reset().parameter("length").value(length).notBlank()
|
||||
.zeroOrPositiveAmount();
|
||||
} else {
|
||||
baseDataValidator.reset().parameter("length").value(length)
|
||||
.cantBeBlankWhenParameterProvidedIs("type", type);
|
||||
}
|
||||
} else {
|
||||
baseDataValidator.reset().parameter("length").value(length)
|
||||
.mustBeBlankWhenParameterProvidedIs("type", type);
|
||||
}
|
||||
|
||||
final String code = fromApiJsonHelper.extractStringNamed("code", column);
|
||||
|
||||
if (type != null && type.equals("Dropdown")) {
|
||||
baseDataValidator.reset().parameter("code").value(code).notBlank()
|
||||
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||
} else {
|
||||
baseDataValidator.reset().parameter("code").value(code).mustBeBlankWhenParameterProvided("type", type);
|
||||
}
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForCreate);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
|
||||
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element);
|
||||
baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50)
|
||||
.isOneOfTheseValues(supportedApptableNames);
|
||||
final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
|
||||
|
||||
final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element);
|
||||
baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50)
|
||||
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||
|
||||
final Boolean multiRow = fromApiJsonHelper.extractBooleanNamed("multiRow", element);
|
||||
baseDataValidator.reset().parameter("multiRow").value(multiRow).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
|
||||
final JsonArray columns = fromApiJsonHelper.extractJsonArrayNamed("columns", element);
|
||||
baseDataValidator.reset().parameter("columns").value(columns).notNull().jsonArrayNotEmpty();
|
||||
|
||||
if (columns != null) {
|
||||
for (JsonElement column : columns) {
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForCreateColumns);
|
||||
|
||||
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
|
||||
final String type = fromApiJsonHelper.extractStringNamed("type", column);
|
||||
baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(supportedColumnTypes);
|
||||
|
||||
validateType(baseDataValidator, column);
|
||||
|
||||
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
// Because all parameters are optional, a check to see if at least one parameter
|
||||
// has been specified is necessary in order to avoid JSON requests with no parameters
|
||||
if (!json.matches("(?s)\\A\\{.*?(\\\".*?\\\"\\s*?:\\s*?)+.*?\\}\\z")) {
|
||||
throw new PlatformDataIntegrityException("error.msg.invalid.request.body.no.parameters",
|
||||
"Provided JSON request body does not have any parameters.");
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
|
||||
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element);
|
||||
baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(supportedApptableNames);
|
||||
final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
|
||||
|
||||
final JsonArray changeColumns = fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element);
|
||||
baseDataValidator.reset().parameter("changeColumns").value(changeColumns).ignoreIfNull().jsonArrayNotEmpty();
|
||||
|
||||
if (changeColumns != null) {
|
||||
for (JsonElement column : changeColumns) {
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForChangeColumns);
|
||||
|
||||
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
|
||||
final String newName = fromApiJsonHelper.extractStringNamed("newName", column);
|
||||
baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank()
|
||||
.notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName)
|
||||
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||
|
||||
final String code = fromApiJsonHelper.extractStringNamed("code", column);
|
||||
baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank()
|
||||
.notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
|
||||
final String newCode = fromApiJsonHelper.extractStringNamed("newCode", column);
|
||||
baseDataValidator.reset().parameter("newCode").value(newCode).ignoreIfNull().notBlank()
|
||||
.notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
|
||||
if (StringUtils.isBlank(code) && StringUtils.isNotBlank(newCode)) {
|
||||
baseDataValidator.reset().parameter("code").value(code)
|
||||
.cantBeBlankWhenParameterProvidedIs("newCode", newCode);
|
||||
}
|
||||
|
||||
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
|
||||
final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column);
|
||||
baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
final JsonArray addColumns = fromApiJsonHelper.extractJsonArrayNamed("addColumns", element);
|
||||
baseDataValidator.reset().parameter("addColumns").value(addColumns).ignoreIfNull().jsonArrayNotEmpty();
|
||||
|
||||
if (addColumns != null) {
|
||||
for (JsonElement column : addColumns) {
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForAddColumns);
|
||||
|
||||
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
|
||||
validateType(baseDataValidator, column);
|
||||
|
||||
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
|
||||
final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column);
|
||||
baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank()
|
||||
.isOneOfTheseValues(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
final JsonArray dropColumns = fromApiJsonHelper.extractJsonArrayNamed("dropColumns", element);
|
||||
baseDataValidator.reset().parameter("dropColumns").value(dropColumns).ignoreIfNull().jsonArrayNotEmpty();
|
||||
|
||||
if (dropColumns != null) {
|
||||
for (JsonElement column : dropColumns) {
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForDropColumns);
|
||||
|
||||
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
}
|
||||
+17
@@ -7,11 +7,13 @@ package org.mifosplatform.infrastructure.core.serialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -78,6 +80,21 @@ public class FromJsonHelper {
|
||||
|
||||
if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
|
||||
}
|
||||
|
||||
public void checkForUnsupportedParameters(final JsonObject object, final Set<String> supportedParams) {
|
||||
if (object == null) { throw new InvalidParameterException(); }
|
||||
|
||||
Set<Entry<String, JsonElement>> entries = object.entrySet();
|
||||
List<String> unsupportedParameterList = new ArrayList<String>();
|
||||
|
||||
for (Entry<String, JsonElement> providedParameter : entries) {
|
||||
if (!supportedParams.contains(providedParameter.getKey())) {
|
||||
unsupportedParameterList.add(providedParameter.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
|
||||
}
|
||||
|
||||
public JsonElement parse(final String json) {
|
||||
return parser.parse(json);
|
||||
|
||||
+52
@@ -70,6 +70,46 @@ public class DatatablesApiResource {
|
||||
return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String createDatatable(final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.createDBDatatable(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{datatableName}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String updateDatatable(@PathParam("datatableName") final String datatableName,
|
||||
final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.updateDBDatatable(datatableName, apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{datatableName}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteDatatable(@PathParam("datatableName") final String datatableName,
|
||||
final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.deleteDBDatatable(datatableName, apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("register/{datatable}/{apptable}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -95,6 +135,18 @@ public class DatatablesApiResource {
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{datatable}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String getDatatable(@PathParam("datatable") final String datatable, @Context final UriInfo uriInfo) {
|
||||
|
||||
final DatatableData result = this.readWriteNonCoreDataService.retrieveDatatable(datatable);
|
||||
|
||||
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{datatable}/{apptableId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
|
||||
+11
-4
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.dataqueries.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Immutable data object representing datatable data.
|
||||
*/
|
||||
@@ -14,13 +16,18 @@ public class DatatableData {
|
||||
private final String applicationTableName;
|
||||
@SuppressWarnings("unused")
|
||||
private final String registeredTableName;
|
||||
|
||||
public static DatatableData create(final String applicationTableName, final String registeredTableName) {
|
||||
return new DatatableData(applicationTableName, registeredTableName);
|
||||
@SuppressWarnings("unused")
|
||||
private final List<ResultsetColumnHeaderData> columnHeaderData;
|
||||
|
||||
public static DatatableData create(final String applicationTableName, final String registeredTableName,
|
||||
final List<ResultsetColumnHeaderData> columnHeaderData) {
|
||||
return new DatatableData(applicationTableName, registeredTableName, columnHeaderData);
|
||||
}
|
||||
|
||||
private DatatableData(final String applicationTableName, final String registeredTableName) {
|
||||
private DatatableData(final String applicationTableName, final String registeredTableName,
|
||||
final List<ResultsetColumnHeaderData> columnHeaderData) {
|
||||
this.applicationTableName = applicationTableName;
|
||||
this.registeredTableName = registeredTableName;
|
||||
this.columnHeaderData = columnHeaderData;
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -17,7 +17,6 @@ public final class ResultsetColumnHeaderData {
|
||||
|
||||
private final String columnName;
|
||||
private final String columnType;
|
||||
@SuppressWarnings("unused")
|
||||
private final Long columnLength;
|
||||
private final String columnDisplayType;
|
||||
private final boolean isColumnNullable;
|
||||
@@ -161,6 +160,10 @@ public final class ResultsetColumnHeaderData {
|
||||
public String getColumnType() {
|
||||
return this.columnType;
|
||||
}
|
||||
|
||||
public Long getColumnLength() {
|
||||
return this.columnLength;
|
||||
}
|
||||
|
||||
public String getColumnDisplayType() {
|
||||
return this.columnDisplayType;
|
||||
|
||||
+32
@@ -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.infrastructure.dataqueries.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class CreateDatatableCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final ReadWriteNonCoreDataService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.createDatatable(command);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.infrastructure.dataqueries.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
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.dataqueries.service.ReadWriteNonCoreDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class DeleteDatatableCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final ReadWriteNonCoreDataService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
|
||||
|
||||
this.writePlatformService.deleteDatatable(datatableName);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withCommandId(command.commandId())
|
||||
.withResourceIdAsString(datatableName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.infrastructure.dataqueries.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
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.dataqueries.service.ReadWriteNonCoreDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class UpdateDatatableCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final ReadWriteNonCoreDataService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
|
||||
|
||||
this.writePlatformService.updateDatatable(datatableName, command);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withCommandId(command.commandId())
|
||||
.withResourceIdAsString(datatableName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -29,7 +29,6 @@ import javax.ws.rs.core.StreamingOutput;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils;
|
||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
|
||||
import org.mifosplatform.infrastructure.dataqueries.data.ReportData;
|
||||
@@ -38,6 +37,7 @@ import org.mifosplatform.infrastructure.dataqueries.data.ReportParameterJoinData
|
||||
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
|
||||
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetRowData;
|
||||
import org.mifosplatform.infrastructure.dataqueries.exception.ReportNotFoundException;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
|
||||
@@ -243,7 +243,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
|
||||
if (noPentaho) { throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled",
|
||||
"Pentaho is not enabled"); }
|
||||
|
||||
final String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator + "pentahoReports" + File.separator + reportName + ".prpt";
|
||||
final String reportPath = FileSystemContentRepository.MIFOSX_BASE_DIR + File.separator + "pentahoReports" + File.separator + reportName + ".prpt";
|
||||
logger.info("Report path: " + reportPath);
|
||||
|
||||
// load report definition
|
||||
@@ -357,7 +357,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
|
||||
@Override
|
||||
public String retrieveReportPDF(final String reportName, final String type, final Map<String, String> queryParams) {
|
||||
|
||||
String fileLocation = FileUtils.MIFOSX_BASE_DIR + File.separator + "";
|
||||
String fileLocation = FileSystemContentRepository.MIFOSX_BASE_DIR + File.separator + "";
|
||||
if (!new File(fileLocation).isDirectory()) {
|
||||
new File(fileLocation).mkdirs();
|
||||
}
|
||||
|
||||
+9
-1
@@ -16,7 +16,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
public interface ReadWriteNonCoreDataService {
|
||||
|
||||
List<DatatableData> retrieveDatatableNames(String appTable);
|
||||
|
||||
|
||||
DatatableData retrieveDatatable(String datatable);
|
||||
|
||||
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
|
||||
void registerDatatable(String datatable, String appTable);
|
||||
|
||||
@@ -25,6 +27,12 @@ public interface ReadWriteNonCoreDataService {
|
||||
|
||||
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
|
||||
|
||||
CommandProcessingResult createDatatable(JsonCommand command);
|
||||
|
||||
void updateDatatable(String datatableName, JsonCommand command);
|
||||
|
||||
void deleteDatatable(String datatableName);
|
||||
|
||||
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
|
||||
|
||||
+486
-106
@@ -18,6 +18,7 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.hibernate.exception.SQLGrammarException;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
@@ -25,6 +26,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
|
||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||
@@ -47,12 +49,21 @@ import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Service
|
||||
public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataService {
|
||||
|
||||
private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
|
||||
private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {{
|
||||
put("String", "VARCHAR"); put("Number", "INT"); put("Decimal", "DECIMAL"); put("Date", "DATE");
|
||||
put("Text", "TEXT"); put ("Dropdown", "INT");
|
||||
}};
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DataSource dataSource;
|
||||
@@ -60,16 +71,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final JsonParserHelper helper;
|
||||
private final GenericDataService genericDataService;
|
||||
private final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
|
||||
@Autowired
|
||||
public ReadWriteNonCoreDataServiceImpl(final TenantAwareRoutingDataSource dataSource, final PlatformSecurityContext context,
|
||||
final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService) {
|
||||
final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService,
|
||||
final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
|
||||
this.dataSource = dataSource;
|
||||
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
|
||||
this.context = context;
|
||||
this.fromJsonHelper = fromJsonHelper;
|
||||
this.helper = new JsonParserHelper();
|
||||
this.genericDataService = genericDataService;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,13 +110,41 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
while (rs.next()) {
|
||||
final String appTableName = rs.getString("application_table_name");
|
||||
final String registeredDatatableName = rs.getString("registered_table_name");
|
||||
final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
|
||||
.fillResultsetColumnHeaders(registeredDatatableName);
|
||||
|
||||
datatables.add(DatatableData.create(appTableName, registeredDatatableName));
|
||||
datatables.add(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData));
|
||||
}
|
||||
|
||||
return datatables;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatatableData retrieveDatatable(final String datatable) {
|
||||
|
||||
// PERMITTED datatables
|
||||
final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists"
|
||||
+ " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
|
||||
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
|
||||
+ " where ur.appuser_id = " + context.authenticatedUser().getId() + " and registered_table_name='" + datatable + "'"
|
||||
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
|
||||
+ " order by application_table_name, registered_table_name";
|
||||
|
||||
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
|
||||
|
||||
DatatableData datatableData = null;
|
||||
while (rs.next()) {
|
||||
final String appTableName = rs.getString("application_table_name");
|
||||
final String registeredDatatableName = rs.getString("registered_table_name");
|
||||
final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
|
||||
.fillResultsetColumnHeaders(registeredDatatableName);
|
||||
|
||||
datatableData = DatatableData.create(appTableName, registeredDatatableName, columnHeaderData);
|
||||
}
|
||||
|
||||
return datatableData;
|
||||
}
|
||||
|
||||
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
|
||||
logger.error(dve.getMessage(), dve);
|
||||
}
|
||||
@@ -115,7 +157,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
|
||||
final String registerDatatableSql = "insert into x_registered_table (registered_table_name, application_table_name) values ('"
|
||||
+ dataTableName + "', '" + applicationTableName + "')";
|
||||
|
||||
|
||||
final String createPermission = "'CREATE_" + dataTableName + "'";
|
||||
final String createPermissionChecker = "'CREATE_" + dataTableName + "_CHECKER'";
|
||||
final String readPermission = "'READ_" + dataTableName + "'";
|
||||
@@ -160,15 +202,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
+ ", 'DELETE', '"
|
||||
+ dataTableName + "', false)";
|
||||
|
||||
|
||||
|
||||
try {
|
||||
String[] sqlArray = {registerDatatableSql, permissionsSql};
|
||||
String[] sqlArray = { registerDatatableSql, permissionsSql };
|
||||
this.jdbcTemplate.batchUpdate(sqlArray);
|
||||
|
||||
|
||||
} catch (DataIntegrityViolationException dve) {
|
||||
Throwable realCause = dve.getMostSpecificCause();
|
||||
//even if duplicate is only due to permission duplicate, okay to show duplicate datatable error msg
|
||||
// even if duplicate is only due to permission duplicate, okay to
|
||||
// show duplicate datatable error msg
|
||||
if (realCause.getMessage().contains("Duplicate entry")) { throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.registered", "Datatable `" + dataTableName
|
||||
+ "` is already registered against an application table.", "dataTableName", dataTableName); }
|
||||
@@ -191,9 +232,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
|
||||
final String deletePermissionsSql = "delete from m_permission where code in " + permissionList;
|
||||
|
||||
final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
|
||||
final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
|
||||
|
||||
String[] sqlArray = {deleteRolePermissionsSql, deletePermissionsSql, deleteRegisteredDatatableSql};
|
||||
String[] sqlArray = { deleteRolePermissionsSql, deletePermissionsSql, deleteRegisteredDatatableSql };
|
||||
this.jdbcTemplate.batchUpdate(sqlArray);
|
||||
}
|
||||
|
||||
@@ -213,9 +254,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
final String sql = getAddSql(columnHeaders, dataTableName, getFKField(appTable), appTableId, dataParams);
|
||||
|
||||
this.jdbcTemplate.update(sql);
|
||||
|
||||
|
||||
return commandProcessingResult; //
|
||||
|
||||
|
||||
} catch (ConstraintViolationException dve) {
|
||||
// NOTE: jdbctemplate throws a
|
||||
// org.hibernate.exception.ConstraintViolationException even though
|
||||
@@ -240,11 +281,356 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDatatableName(final String name) {
|
||||
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException("error.msg.datatables.datatable.null.name",
|
||||
"Data table name must not be blank.");
|
||||
} else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.datatables.datatable.invalid.name.regex",
|
||||
"Invalid data table name.", name);
|
||||
}
|
||||
}
|
||||
|
||||
private String datatableColumnNameToCodeValueName(final String columnName, final String code) {
|
||||
|
||||
return (code + "_cd_" + columnName);
|
||||
}
|
||||
|
||||
private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) {
|
||||
|
||||
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||
String type = (column.has("type")) ? column.get("type").getAsString() : null;
|
||||
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
|
||||
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||
String code = (column.has("code")) ? column.get("code").getAsString() : null;
|
||||
|
||||
if (StringUtils.isNotBlank(code)) {
|
||||
name = datatableColumnNameToCodeValueName(name, code);
|
||||
}
|
||||
|
||||
String mysqlType = apiTypeToMySQL.get(type);
|
||||
sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
if (type.equals("String")) {
|
||||
sqlBuilder = sqlBuilder.append("(" + length + ")");
|
||||
} else if (type.equals("Decimal")) {
|
||||
sqlBuilder = sqlBuilder.append("(19,6)");
|
||||
} else if (type.equals("Dropdown")) {
|
||||
sqlBuilder = sqlBuilder.append("(11)");
|
||||
}
|
||||
}
|
||||
if (mandatory != null) {
|
||||
if (mandatory) {
|
||||
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||
} else {
|
||||
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
sqlBuilder = sqlBuilder.append(", ");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createDatatable(final JsonCommand command) {
|
||||
|
||||
String datatableName = null;
|
||||
|
||||
try {
|
||||
context.authenticatedUser();
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
JsonElement element = this.fromJsonHelper.parse(command.json());
|
||||
JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element);
|
||||
datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element);
|
||||
String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
|
||||
Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element);
|
||||
|
||||
if (multiRow == null) {
|
||||
multiRow = false;
|
||||
}
|
||||
|
||||
validateDatatableName(datatableName);
|
||||
validateAppTable(apptableName);
|
||||
|
||||
String fkColumnName = apptableName.substring(2) + "_id";
|
||||
String fkName = datatableName.toLowerCase().replaceAll("\\s", "_");
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` (");
|
||||
|
||||
if (multiRow) {
|
||||
sqlBuilder = sqlBuilder
|
||||
.append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ")
|
||||
.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
|
||||
} else {
|
||||
sqlBuilder = sqlBuilder
|
||||
.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
|
||||
}
|
||||
|
||||
for (JsonElement column : columns) {
|
||||
parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder);
|
||||
}
|
||||
|
||||
// Remove trailing comma and space
|
||||
sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length());
|
||||
|
||||
if (multiRow) {
|
||||
sqlBuilder = sqlBuilder
|
||||
.append(", PRIMARY KEY (`id`)")
|
||||
.append(", KEY `fk_" + apptableName + "` (`" + fkColumnName + "`)")
|
||||
.append(", CONSTRAINT `fk_" + fkName + "` ")
|
||||
.append("FOREIGN KEY (`" + fkColumnName + "`) ")
|
||||
.append("REFERENCES `" + apptableName + "` (`id`)");
|
||||
} else {
|
||||
sqlBuilder = sqlBuilder
|
||||
.append(", PRIMARY KEY (`" + fkColumnName + "`)")
|
||||
.append(", CONSTRAINT `fk_" + fkName + "` ")
|
||||
.append("FOREIGN KEY (`" + fkColumnName + "`) ")
|
||||
.append("REFERENCES `" + apptableName + "` (`id`)");
|
||||
}
|
||||
|
||||
sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
|
||||
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||
|
||||
registerDatatable(datatableName, apptableName);
|
||||
} catch (SQLGrammarException e) {
|
||||
Throwable realCause = e.getCause();
|
||||
|
||||
if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.already.exists", realCause.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withCommandId(command.commandId())
|
||||
.withResourceIdAsString(datatableName)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void parseDatatableColumnForUpdate(final JsonObject column,
|
||||
final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition, StringBuilder sqlBuilder) {
|
||||
|
||||
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||
String newName = (column.has("newName")) ? column.get("newName").getAsString() : name;
|
||||
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||
String after = (column.has("after")) ? column.get("after").getAsString() : null;
|
||||
String code = (column.has("code")) ? column.get("code").getAsString() : null;
|
||||
String newCode = (column.has("newCode")) ? column.get("newCode").getAsString() : null;
|
||||
|
||||
if (StringUtils.isNotBlank(code)) {
|
||||
name = datatableColumnNameToCodeValueName(name, code);
|
||||
|
||||
if (StringUtils.isNotBlank(newCode)) {
|
||||
newName = datatableColumnNameToCodeValueName(newName, newCode);
|
||||
} else {
|
||||
newName = datatableColumnNameToCodeValueName(newName, code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapColumnNameDefinition.containsKey(name)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.datatable.column.missing.update.parse",
|
||||
"Column " + name + " does not exist.", name);
|
||||
}
|
||||
|
||||
String type = mapColumnNameDefinition.get(name).getColumnType();
|
||||
Long length = mapColumnNameDefinition.get(name).getColumnLength();
|
||||
|
||||
sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + type);
|
||||
if (length != null && length > 0) {
|
||||
if (type.equals("DECIMAL")) {
|
||||
sqlBuilder.append("(19,6)");
|
||||
} else if (type.equals("VARCHAR")) {
|
||||
sqlBuilder.append("(" + length + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (mandatory != null) {
|
||||
if (mandatory) {
|
||||
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||
} else {
|
||||
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||
}
|
||||
}
|
||||
if (after != null) {
|
||||
sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) {
|
||||
|
||||
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||
String type = (column.has("type")) ? column.get("type").getAsString() : null;
|
||||
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
|
||||
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||
String after = (column.has("after")) ? column.get("after").getAsString() : null;
|
||||
String code = (column.has("code")) ? column.get("code").getAsString() : null;
|
||||
|
||||
if (StringUtils.isNotBlank(code)) {
|
||||
name = datatableColumnNameToCodeValueName(name, code);
|
||||
}
|
||||
|
||||
String mysqlType = apiTypeToMySQL.get(type);
|
||||
sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType);
|
||||
|
||||
if (type != null) {
|
||||
if (type.equals("String") && length != null) {
|
||||
sqlBuilder = sqlBuilder.append("(" + length + ")");
|
||||
} else if (type.equals("Decimal")) {
|
||||
sqlBuilder = sqlBuilder.append("(19,6)");
|
||||
} else if (type.equals("Dropdown")) {
|
||||
sqlBuilder = sqlBuilder.append("(11)");
|
||||
}
|
||||
}
|
||||
if (mandatory != null) {
|
||||
if (mandatory) {
|
||||
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||
} else {
|
||||
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||
}
|
||||
}
|
||||
if (after != null) {
|
||||
sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) {
|
||||
|
||||
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||
|
||||
sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateDatatable(final String datatableName, final JsonCommand command) {
|
||||
|
||||
try {
|
||||
context.authenticatedUser();
|
||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
JsonElement element = this.fromJsonHelper.parse(command.json());
|
||||
JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element);
|
||||
JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element);
|
||||
JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element);
|
||||
String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
|
||||
|
||||
validateDatatableName(datatableName);
|
||||
|
||||
List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName);
|
||||
Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition = new HashMap<String, ResultsetColumnHeaderData>();
|
||||
for (ResultsetColumnHeaderData columnHeader : columnHeaderData) {
|
||||
mapColumnNameDefinition.put(columnHeader.getColumnName(), columnHeader);
|
||||
}
|
||||
|
||||
if (!StringUtils.isBlank(apptableName)) {
|
||||
validateAppTable(apptableName);
|
||||
|
||||
String oldApptableName = this.queryForApplicationTableName(datatableName);
|
||||
if (!StringUtils.equals(oldApptableName, apptableName)) {
|
||||
deregisterDatatable(datatableName);
|
||||
registerDatatable(datatableName, apptableName);
|
||||
}
|
||||
}
|
||||
|
||||
if (changeColumns == null && addColumns == null && dropColumns == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropColumns != null) {
|
||||
|
||||
StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
|
||||
|
||||
for (JsonElement column : dropColumns) {
|
||||
parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder);
|
||||
}
|
||||
|
||||
// Remove the first comma, right after ALTER TABLE `datatable`
|
||||
int indexOfFirstComma = sqlBuilder.indexOf(",");
|
||||
if (indexOfFirstComma != -1) {
|
||||
sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
|
||||
}
|
||||
|
||||
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||
}
|
||||
if (addColumns != null) {
|
||||
|
||||
StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
|
||||
|
||||
for (JsonElement column : addColumns) {
|
||||
parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder);
|
||||
}
|
||||
|
||||
// Remove the first comma, right after ALTER TABLE `datatable`
|
||||
int indexOfFirstComma = sqlBuilder.indexOf(",");
|
||||
if (indexOfFirstComma != -1) {
|
||||
sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
|
||||
}
|
||||
|
||||
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||
}
|
||||
if (changeColumns != null) {
|
||||
|
||||
StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
|
||||
|
||||
for (JsonElement column : changeColumns) {
|
||||
parseDatatableColumnForUpdate(column.getAsJsonObject(), mapColumnNameDefinition, sqlBuilder);
|
||||
}
|
||||
|
||||
// Remove the first comma, right after ALTER TABLE `datatable`
|
||||
int indexOfFirstComma = sqlBuilder.indexOf(",");
|
||||
if (indexOfFirstComma != -1) {
|
||||
sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma);
|
||||
}
|
||||
|
||||
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||
}
|
||||
} catch (SQLGrammarException e) {
|
||||
Throwable realCause = e.getCause();
|
||||
|
||||
if (realCause.getMessage().toLowerCase().contains("unknown column")) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.column.missing.update", realCause.getMessage());
|
||||
} else if (realCause.getMessage().toLowerCase().contains("can't drop")) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.column.missing.drop", realCause.getMessage());
|
||||
} else if (realCause.getMessage().toLowerCase().contains("duplicate column")) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.duplicate.column", realCause.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void deleteDatatable(final String datatableName) {
|
||||
|
||||
try {
|
||||
context.authenticatedUser();
|
||||
|
||||
validateDatatableName(datatableName);
|
||||
deregisterDatatable(datatableName);
|
||||
|
||||
String sql = "DROP TABLE `" + datatableName + "`";
|
||||
this.jdbcTemplate.execute(sql);
|
||||
} catch (SQLGrammarException e) {
|
||||
Throwable realCause = e.getCause();
|
||||
|
||||
if (realCause.getMessage().contains("Unknown table")) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.datatable.missing", realCause.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateDatatableEntryOneToOne(final String dataTableName, final Long appTableId, final JsonCommand command) {
|
||||
|
||||
return updateDatatableEntry(dataTableName, appTableId, null, command);
|
||||
return updateDatatableEntry(dataTableName, appTableId, null, command);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -252,34 +638,37 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
public CommandProcessingResult updateDatatableEntryOneToMany(final String dataTableName, final Long appTableId, final Long datatableId,
|
||||
final JsonCommand command) {
|
||||
|
||||
return updateDatatableEntry(dataTableName, appTableId, datatableId, command);
|
||||
return updateDatatableEntry(dataTableName, appTableId, datatableId, command);
|
||||
}
|
||||
|
||||
|
||||
private CommandProcessingResult updateDatatableEntry(final String dataTableName, final Long appTableId, final Long datatableId,
|
||||
final JsonCommand command) {
|
||||
|
||||
|
||||
final String appTable = queryForApplicationTableName(dataTableName);
|
||||
CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
|
||||
|
||||
|
||||
final GenericResultsetData grs = retrieveDataTableGenericResultSetForUpdate(appTable, dataTableName, appTableId, datatableId);
|
||||
|
||||
|
||||
if (grs.hasNoEntries()) { throw new DatatableNotFoundException(dataTableName, appTableId); }
|
||||
|
||||
|
||||
if (grs.hasMoreThanOneEntry()) { throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update",
|
||||
"Application table: " + dataTableName + " Foreign key id: " + appTableId); }
|
||||
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
|
||||
Map<String, String> dataParams = this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
|
||||
|
||||
String pkName = "id"; //1:M datatable
|
||||
if (datatableId == null) { pkName = getFKField(appTable);} //1:1 datatable
|
||||
|
||||
String pkName = "id"; // 1:M datatable
|
||||
if (datatableId == null) {
|
||||
pkName = getFKField(appTable);
|
||||
} // 1:1 datatable
|
||||
|
||||
final Map<String, Object> changes = getAffectedAndChangedColumns(grs, dataParams, pkName);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
Long pkValue = appTableId;
|
||||
if (datatableId != null) { pkValue = datatableId;}
|
||||
Long pkValue = appTableId;
|
||||
if (datatableId != null) {
|
||||
pkValue = datatableId;
|
||||
}
|
||||
final String sql = getUpdateSql(dataTableName, pkName, pkValue, changes);
|
||||
logger.info("Update sql: " + sql);
|
||||
if (StringUtils.isNotBlank(sql)) {
|
||||
@@ -287,20 +676,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
changes.put("locale", dataParams.get("locale"));
|
||||
changes.put("dateFormat", "yyyy-MM-dd");
|
||||
} else {
|
||||
logger.info("No Changes");
|
||||
}
|
||||
logger.info("No Changes");
|
||||
}
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(commandProcessingResult.getOfficeId()) //
|
||||
.withGroupId(commandProcessingResult.getGroupId()) //
|
||||
.withClientId(commandProcessingResult.getClientId()) //
|
||||
.withSavingsId(commandProcessingResult.getSavingsId()) //
|
||||
.withLoanId(commandProcessingResult.getLoanId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
.withOfficeId(commandProcessingResult.getOfficeId()) //
|
||||
.withGroupId(commandProcessingResult.getGroupId()) //
|
||||
.withClientId(commandProcessingResult.getClientId()) //
|
||||
.withSavingsId(commandProcessingResult.getSavingsId()) //
|
||||
.withLoanId(commandProcessingResult.getLoanId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -308,13 +696,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
|
||||
final String appTable = queryForApplicationTableName(dataTableName);
|
||||
CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(appTable, appTableId);
|
||||
|
||||
|
||||
final String deleteOneToOneEntrySql = getDeleteEntriesSql(dataTableName, getFKField(appTable), appTableId);
|
||||
|
||||
int rowsDeleted = this.jdbcTemplate.update(deleteOneToOneEntrySql);
|
||||
if (rowsDeleted < 1) { throw new DatatableNotFoundException(dataTableName, appTableId); }
|
||||
|
||||
|
||||
return commandProcessingResult;
|
||||
}
|
||||
|
||||
@@ -358,10 +745,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
|
||||
return new GenericResultsetData(columnHeaders, result);
|
||||
}
|
||||
|
||||
|
||||
private GenericResultsetData retrieveDataTableGenericResultSetForUpdate(final String appTable, String dataTableName, final Long appTableId,
|
||||
final Long id) {
|
||||
private GenericResultsetData retrieveDataTableGenericResultSetForUpdate(final String appTable, final String dataTableName,
|
||||
final Long appTableId, final Long id) {
|
||||
|
||||
final List<ResultsetColumnHeaderData> columnHeaders = this.genericDataService.fillResultsetColumnHeaders(dataTableName);
|
||||
|
||||
@@ -383,9 +769,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
private CommandProcessingResult checkMainResourceExistsWithinScope(final String appTable, final Long appTableId) {
|
||||
|
||||
final String sql = dataScopedSQL(appTable, appTableId);
|
||||
logger.info("data scoped sql: " + sql);
|
||||
logger.info("data scoped sql: " + sql);
|
||||
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
|
||||
|
||||
|
||||
if (!rs.next()) { throw new DatatableNotFoundException(appTable, appTableId); }
|
||||
|
||||
Long officeId = getLongSqlRowSet(rs, "officeId");
|
||||
@@ -393,90 +779,86 @@ logger.info("data scoped sql: " + sql);
|
||||
Long clientId = getLongSqlRowSet(rs, "clientId");
|
||||
Long savingsId = getLongSqlRowSet(rs, "savingsId");
|
||||
Long LoanId = getLongSqlRowSet(rs, "loanId");
|
||||
|
||||
|
||||
if (rs.next()) { throw new DatatableSystemErrorException("System Error: More than one row returned from data scoping query"); }
|
||||
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(officeId) //
|
||||
.withGroupId(groupId) //
|
||||
.withClientId(clientId) //
|
||||
.withSavingsId(savingsId) //
|
||||
.withLoanId(LoanId) //
|
||||
.build();
|
||||
.withOfficeId(officeId) //
|
||||
.withGroupId(groupId) //
|
||||
.withClientId(clientId) //
|
||||
.withSavingsId(savingsId) //
|
||||
.withLoanId(LoanId) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private Long getLongSqlRowSet(SqlRowSet rs, String column) {
|
||||
|
||||
private Long getLongSqlRowSet(final SqlRowSet rs, final String column) {
|
||||
Long val = rs.getLong(column);
|
||||
if (val == 0) val = null;
|
||||
return val;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
private String dataScopedSQL(final String appTable, final Long appTableId) {
|
||||
/*
|
||||
* unfortunately have to, one way or another, be able to restrict data
|
||||
* to the users office hierarchy. Here, a few key tables are done.
|
||||
* But if additional fields are needed on other tables the
|
||||
* same pattern applies
|
||||
* to the users office hierarchy. Here, a few key tables are done. But
|
||||
* if additional fields are needed on other tables the same pattern
|
||||
* applies
|
||||
*/
|
||||
|
||||
AppUser currentUser = context.authenticatedUser();
|
||||
String scopedSQL = null;
|
||||
/*
|
||||
* m_loan and m_savings_account are connected to an m_office thru either an m_client or an m_group
|
||||
* If both it means it relates to an m_client that is in a group (still an m_client account)
|
||||
* m_loan and m_savings_account are connected to an m_office thru either
|
||||
* an m_client or an m_group If both it means it relates to an m_client
|
||||
* that is in a group (still an m_client account)
|
||||
*/
|
||||
if (appTable.equalsIgnoreCase("m_loan")) {
|
||||
scopedSQL = "select distinctrow x.* from (" +
|
||||
" (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId from m_loan l " +
|
||||
" join m_client c on c.id = l.client_id " +
|
||||
" join m_office o on o.id = c.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where l.id = " + appTableId + ")" +
|
||||
" union all " +
|
||||
" (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId from m_loan l " +
|
||||
" join m_group g on g.id = l.group_id " +
|
||||
" join m_office o on o.id = g.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where l.id = " + appTableId + ")" +
|
||||
" ) x";
|
||||
scopedSQL = "select distinctrow x.* from ("
|
||||
+ " (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId from m_loan l "
|
||||
+ " join m_client c on c.id = l.client_id "
|
||||
+ " join m_office o on o.id = c.office_id and o.hierarchy like '"
|
||||
+ currentUser.getOffice().getHierarchy()
|
||||
+ "%'"
|
||||
+ " where l.id = "
|
||||
+ appTableId
|
||||
+ ")"
|
||||
+ " union all "
|
||||
+ " (select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId from m_loan l "
|
||||
+ " join m_group g on g.id = l.group_id " + " join m_office o on o.id = g.office_id and o.hierarchy like '"
|
||||
+ currentUser.getOffice().getHierarchy() + "%'" + " where l.id = " + appTableId + ")" + " ) x";
|
||||
}
|
||||
if (appTable.equalsIgnoreCase("m_savings_account")) {
|
||||
scopedSQL = "select distinctrow x.* from (" +
|
||||
" (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId from m_savings_account s " +
|
||||
" join m_client c on c.id = s.client_id " +
|
||||
" join m_office o on o.id = c.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where s.id = " + appTableId+ ")" +
|
||||
" union all " +
|
||||
" (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId from m_savings_account s " +
|
||||
" join m_group g on g.id = s.group_id " +
|
||||
" join m_office o on o.id = g.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where s.id = " + appTableId+ ")" +
|
||||
" ) x";
|
||||
scopedSQL = "select distinctrow x.* from ("
|
||||
+ " (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId from m_savings_account s "
|
||||
+ " join m_client c on c.id = s.client_id "
|
||||
+ " join m_office o on o.id = c.office_id and o.hierarchy like '"
|
||||
+ currentUser.getOffice().getHierarchy()
|
||||
+ "%'"
|
||||
+ " where s.id = "
|
||||
+ appTableId
|
||||
+ ")"
|
||||
+ " union all "
|
||||
+ " (select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId from m_savings_account s "
|
||||
+ " join m_group g on g.id = s.group_id " + " join m_office o on o.id = g.office_id and o.hierarchy like '"
|
||||
+ currentUser.getOffice().getHierarchy() + "%'" + " where s.id = " + appTableId + ")" + " ) x";
|
||||
}
|
||||
if (appTable.equalsIgnoreCase("m_client")) {
|
||||
scopedSQL = "select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId from m_client c " +
|
||||
" join m_office o on o.id = c.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where c.id = " + appTableId;
|
||||
scopedSQL = "select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId from m_client c "
|
||||
+ " join m_office o on o.id = c.office_id and o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'"
|
||||
+ " where c.id = " + appTableId;
|
||||
}
|
||||
if (appTable.equalsIgnoreCase("m_group")) {
|
||||
scopedSQL = "select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId from m_group g "
|
||||
+ " join m_office o on o.id = g.office_id and o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'"
|
||||
+ " where g.id = " + appTableId;
|
||||
}
|
||||
if (appTable.equalsIgnoreCase("m_group")) {
|
||||
scopedSQL = "select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId from m_group g " +
|
||||
" join m_office o on o.id = g.office_id and o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" where g.id = " + appTableId;
|
||||
}
|
||||
if (appTable.equalsIgnoreCase("m_office")) {
|
||||
scopedSQL = "select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId from m_office o " +
|
||||
" where o.hierarchy like '" +
|
||||
currentUser.getOffice().getHierarchy() + "%'" +
|
||||
" and o.id = " + appTableId;
|
||||
}
|
||||
scopedSQL = "select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId from m_office o "
|
||||
+ " where o.hierarchy like '" + currentUser.getOffice().getHierarchy() + "%'" + " and o.id = " + appTableId;
|
||||
}
|
||||
|
||||
if (scopedSQL == null) { throw new PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria",
|
||||
"Application Table: " + appTable + " not catered for in data Scoping"); }
|
||||
if (scopedSQL == null) { throw new PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria", "Application Table: "
|
||||
+ appTable + " not catered for in data Scoping"); }
|
||||
|
||||
return scopedSQL;
|
||||
|
||||
@@ -621,7 +1003,7 @@ logger.info("data scoped sql: " + sql);
|
||||
affectedAndChangedColumns.put(key, columnValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return affectedAndChangedColumns;
|
||||
}
|
||||
|
||||
@@ -635,9 +1017,7 @@ logger.info("data scoped sql: " + sql);
|
||||
if (key.equals(grs.getColumnHeaders().get(i).getColumnName())) {
|
||||
columnValue = columnValues.get(i);
|
||||
|
||||
if (notTheSame(columnValue, keyValue, colType)) {
|
||||
return true;
|
||||
}
|
||||
if (notTheSame(columnValue, keyValue, colType)) { return true; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-39
@@ -5,36 +5,16 @@
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.documentmanagement.api;
|
||||
|
||||
import java.io.File;
|
||||
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.ApiConstants;
|
||||
import com.sun.jersey.core.header.FormDataContentDisposition;
|
||||
import com.sun.jersey.multipart.FormDataBodyPart;
|
||||
import com.sun.jersey.multipart.FormDataParam;
|
||||
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.core.service.FileUtils;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.FileData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.service.DocumentReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.service.DocumentWritePlatformService;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
@@ -42,9 +22,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.sun.jersey.core.header.FormDataContentDisposition;
|
||||
import com.sun.jersey.multipart.FormDataBodyPart;
|
||||
import com.sun.jersey.multipart.FormDataParam;
|
||||
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;
|
||||
|
||||
@Path("{entityType}/{entityId}/documents")
|
||||
@Component
|
||||
@@ -95,8 +83,6 @@ public class DocumentManagementApiResource {
|
||||
@FormDataParam("file") final FormDataContentDisposition fileDetails, @FormDataParam("file") final FormDataBodyPart bodyPart,
|
||||
@FormDataParam("name") final String name, @FormDataParam("description") final String description) {
|
||||
|
||||
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
|
||||
|
||||
/**
|
||||
* TODO: also need to have a backup and stop reading from stream after
|
||||
* max size is reached to protect against malicious clients
|
||||
@@ -124,8 +110,6 @@ public class DocumentManagementApiResource {
|
||||
@FormDataParam("file") final FormDataBodyPart bodyPart, @FormDataParam("name") final String name,
|
||||
@FormDataParam("description") final String description) {
|
||||
|
||||
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
|
||||
|
||||
final Set<String> modifiedParams = new HashSet<String>();
|
||||
modifiedParams.add("name");
|
||||
modifiedParams.add("description");
|
||||
@@ -166,9 +150,6 @@ public class DocumentManagementApiResource {
|
||||
|
||||
final DocumentData documentData = this.documentReadPlatformService.retrieveDocument(entityType, entityId, documentId);
|
||||
|
||||
// we do not want to send document location as a part of the response
|
||||
documentData.setLocation(null);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, documentData, this.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
@@ -182,11 +163,10 @@ public class DocumentManagementApiResource {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.SystemEntityType);
|
||||
|
||||
final DocumentData documentData = this.documentReadPlatformService.retrieveDocument(entityType, entityId, documentId);
|
||||
final File file = new File(documentData.fileLocation());
|
||||
final ResponseBuilder response = Response.ok(file);
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + documentData.fileName() + "\"");
|
||||
response.header("Content-Type", documentData.contentType());
|
||||
final FileData fileData = this.documentReadPlatformService.retrieveFileData(entityType, entityId, documentId);
|
||||
final ResponseBuilder response = Response.ok(fileData.file());
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + fileData.name() + "\"");
|
||||
response.header("Content-Type", fileData.contentType());
|
||||
|
||||
return response.build();
|
||||
}
|
||||
|
||||
+37
-52
@@ -3,9 +3,8 @@
|
||||
* 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.api;
|
||||
package org.mifosplatform.infrastructure.documentmanagement.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
@@ -22,19 +21,16 @@ import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.infrastructure.core.api.ApiConstants;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_DATA_URI_SUFFIX;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_FILE_EXTENSION;
|
||||
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_MIME_TYPE;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils.IMAGE_FILE_EXTENSION;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.ImageData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.service.ImageReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.service.ImageWritePlatformService;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.portfolio.client.data.ClientData;
|
||||
import org.mifosplatform.portfolio.client.exception.ImageNotFoundException;
|
||||
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.mifosplatform.portfolio.client.service.ClientWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -44,22 +40,26 @@ import com.sun.jersey.core.header.FormDataContentDisposition;
|
||||
import com.sun.jersey.multipart.FormDataBodyPart;
|
||||
import com.sun.jersey.multipart.FormDataParam;
|
||||
|
||||
@Path("/clients/{clientId}/images")
|
||||
/**
|
||||
* TODO Vishwas need to change the url to entity/entityId/images to make the
|
||||
* image management API's generic for other entities like staff etc
|
||||
**/
|
||||
@Path("{clients}/{clientId}/images")
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
public class ClientImagesApiResource {
|
||||
public class ImagesApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
private final ClientWritePlatformService clientWritePlatformService;
|
||||
private final ImageReadPlatformService imageReadPlatformService;
|
||||
private final ImageWritePlatformService imageWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
|
||||
|
||||
@Autowired
|
||||
public ClientImagesApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService,
|
||||
final ClientWritePlatformService clientWritePlatformService, final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
|
||||
public ImagesApiResource(final PlatformSecurityContext context, final ImageReadPlatformService readPlatformService,
|
||||
final ImageWritePlatformService imageWritePlatformService, final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
|
||||
this.context = context;
|
||||
this.clientReadPlatformService = readPlatformService;
|
||||
this.clientWritePlatformService = clientWritePlatformService;
|
||||
this.imageReadPlatformService = readPlatformService;
|
||||
this.imageWritePlatformService = imageWritePlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
}
|
||||
|
||||
@@ -76,12 +76,11 @@ public class ClientImagesApiResource {
|
||||
// TODO: vishwas might need more advances validation (like reading magic
|
||||
// number) for handling malicious clients
|
||||
// and clients not setting mime type
|
||||
FileUtils.validateClientImageNotEmpty(fileDetails.getFileName());
|
||||
FileUtils.validateImageMimeType(bodyPart.getMediaType().toString());
|
||||
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, fileDetails.getFileName(), ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
|
||||
ContentRepositoryUtils.validateClientImageNotEmpty(fileDetails.getFileName());
|
||||
ContentRepositoryUtils.validateImageMimeType(bodyPart.getMediaType().toString());
|
||||
|
||||
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
|
||||
inputStream);
|
||||
final CommandProcessingResult result = this.imageWritePlatformService.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
|
||||
inputStream, fileSize);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
@@ -94,9 +93,9 @@ public class ClientImagesApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String addNewClientImage(@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
|
||||
|
||||
final Base64EncodedImage base64EncodedImage = FileUtils.extractImageFromDataURL(jsonRequestBody);
|
||||
final Base64EncodedImage base64EncodedImage = ContentRepositoryUtils.extractImageFromDataURL(jsonRequestBody);
|
||||
|
||||
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, base64EncodedImage);
|
||||
final CommandProcessingResult result = this.imageWritePlatformService.saveOrUpdateClientImage(clientId, base64EncodedImage);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
@@ -111,20 +110,17 @@ public class ClientImagesApiResource {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
|
||||
|
||||
final ClientData clientData = this.clientReadPlatformService.retrieveOne(clientId);
|
||||
|
||||
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
|
||||
final ImageData imageData = this.imageReadPlatformService.retrieveClientImage(clientId);
|
||||
|
||||
// TODO: Need a better way of determining image type
|
||||
String imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.JPEG.getValue();
|
||||
if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.PNG.getValue();
|
||||
String imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.JPEG.getValue();
|
||||
if (StringUtils.endsWith(imageData.location(), ContentRepositoryUtils.IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(imageData.location(), ContentRepositoryUtils.IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
imageDataURISuffix = ContentRepositoryUtils.IMAGE_DATA_URI_SUFFIX.PNG.getValue();
|
||||
}
|
||||
|
||||
String clientImageAsBase64Text = imageDataURISuffix + Base64.encodeFromFile(clientData.imageKey());
|
||||
|
||||
String clientImageAsBase64Text = imageDataURISuffix + Base64.encodeBytes(imageData.getContent());
|
||||
return clientImageAsBase64Text;
|
||||
}
|
||||
|
||||
@@ -134,26 +130,15 @@ public class ClientImagesApiResource {
|
||||
public Response downloadClientImage(@PathParam("clientId") final Long clientId) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
|
||||
final ClientData clientData = this.clientReadPlatformService.retrieveOne(clientId);
|
||||
final ImageData imageData = this.imageReadPlatformService.retrieveClientImage(clientId);
|
||||
|
||||
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
|
||||
|
||||
File image = new File(clientData.imageKey());
|
||||
String imageName = image.getName();
|
||||
ResponseBuilder response = Response.ok(image);
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + imageName + "\"");
|
||||
ResponseBuilder response = Response.ok(imageData.getContent());
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + imageData.getEntityDisplayName() + IMAGE_FILE_EXTENSION.JPEG
|
||||
+ "\"");
|
||||
|
||||
// TODO: Need a better way of determining image type
|
||||
|
||||
// determine image type
|
||||
String contentType = IMAGE_MIME_TYPE.JPEG.getValue();
|
||||
if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
contentType = IMAGE_MIME_TYPE.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
contentType = IMAGE_MIME_TYPE.PNG.getValue();
|
||||
}
|
||||
|
||||
response.header("Content-Type", contentType);
|
||||
response.header("Content-Type", imageData.contentType());
|
||||
return response.build();
|
||||
}
|
||||
|
||||
@@ -187,7 +172,7 @@ public class ClientImagesApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteClientImage(@PathParam("clientId") final Long clientId) {
|
||||
this.clientWritePlatformService.deleteClientImage(clientId);
|
||||
this.imageWritePlatformService.deleteClientImage(clientId);
|
||||
return this.toApiJsonSerializer.serialize(new CommandProcessingResult(clientId));
|
||||
}
|
||||
|
||||
+9
@@ -22,6 +22,7 @@ public class DocumentCommand {
|
||||
private Long size;
|
||||
private String type;
|
||||
private String location;
|
||||
private Integer storageType;
|
||||
|
||||
private final Set<String> modifiedParameters;
|
||||
|
||||
@@ -95,6 +96,14 @@ public class DocumentCommand {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Integer getStorageType() {
|
||||
return this.storageType;
|
||||
}
|
||||
|
||||
public void setStorageType(Integer storageType) {
|
||||
this.storageType = storageType;
|
||||
}
|
||||
|
||||
public boolean isNameChanged() {
|
||||
return this.modifiedParameters.contains("name");
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.mifosplatform.infrastructure.documentmanagement.contentrepository;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.FileData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.ImageData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
|
||||
public interface ContentRepository {
|
||||
|
||||
public StorageType type = null;
|
||||
|
||||
// TODO:Vishwas Need to move these settings to the Database
|
||||
public static final Integer MAX_FILE_UPLOAD_SIZE_IN_MB = 5;
|
||||
|
||||
// TODO:Vishwas Need to move these settings to the Database
|
||||
public static final Integer MAX_IMAGE_UPLOAD_SIZE_IN_MB = 1;
|
||||
|
||||
public abstract String saveFile(InputStream uploadedInputStream, DocumentCommand documentCommand);
|
||||
|
||||
public abstract void deleteFile(String fileName, String documentPath);
|
||||
|
||||
public abstract FileData fetchFile(DocumentData documentData);
|
||||
|
||||
public abstract String saveImage(InputStream uploadedInputStream, Long resourceId, String imageName, Long fileSize);
|
||||
|
||||
public abstract String saveImage(Base64EncodedImage base64EncodedImage, Long resourceId, String imageName);
|
||||
|
||||
public abstract void deleteImage(final Long resourceId, final String location);
|
||||
|
||||
public abstract ImageData fetchImage(ImageData imageData);
|
||||
|
||||
public abstract StorageType getStorageType();
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.mifosplatform.infrastructure.documentmanagement.contentrepository;
|
||||
|
||||
import org.mifosplatform.infrastructure.configuration.data.S3CredentialsData;
|
||||
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.mifosplatform.infrastructure.configuration.service.ExternalServicesReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ContentRepositoryFactory {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private ExternalServicesReadPlatformService externalServicesReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public ContentRepositoryFactory(final ApplicationContext applicationContext,
|
||||
final ExternalServicesReadPlatformService externalServicesReadPlatformService) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.externalServicesReadPlatformService = externalServicesReadPlatformService;
|
||||
}
|
||||
|
||||
public ContentRepository getRepository() {
|
||||
ConfigurationDomainService configurationDomainServiceJpa = applicationContext.getBean("configurationDomainServiceJpa",
|
||||
ConfigurationDomainService.class);
|
||||
if (configurationDomainServiceJpa.isAmazonS3Enabled()) { return createS3DocumentStore(); }
|
||||
return new FileSystemContentRepository();
|
||||
}
|
||||
|
||||
public ContentRepository getRepository(StorageType documentStoreType) {
|
||||
if (documentStoreType == StorageType.FILE_SYSTEM) { return new FileSystemContentRepository(); }
|
||||
return createS3DocumentStore();
|
||||
}
|
||||
|
||||
private ContentRepository createS3DocumentStore() {
|
||||
S3CredentialsData s3CredentialsData = externalServicesReadPlatformService.getS3Credentials();
|
||||
return new S3ContentRepository(s3CredentialsData.getBucketName(), s3CredentialsData.getSecretKey(),
|
||||
s3CredentialsData.getAccessKey());
|
||||
}
|
||||
}
|
||||
+39
-129
@@ -1,15 +1,5 @@
|
||||
/**
|
||||
* 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.infrastructure.core.service;
|
||||
package org.mifosplatform.infrastructure.documentmanagement.contentrepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
@@ -20,17 +10,11 @@ import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.core.exception.ImageDataURLNotValidException;
|
||||
import org.mifosplatform.infrastructure.core.exception.ImageUploadException;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentManagementException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.exception.ContentManagementException;
|
||||
|
||||
import com.lowagie.text.pdf.codec.Base64;
|
||||
public class ContentRepositoryUtils {
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(FileUtils.class);
|
||||
|
||||
public static final String MIFOSX_BASE_DIR = System.getProperty("user.home") + File.separator + ".mifosx";
|
||||
public static Random random = new Random();
|
||||
|
||||
public static enum IMAGE_MIME_TYPE {
|
||||
GIF("image/gif"), JPEG("image/jpeg"), PNG("image/png");
|
||||
@@ -44,7 +28,7 @@ public class FileUtils {
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static enum IMAGE_FILE_EXTENSION {
|
||||
GIF(".gif"), JPEG(".jpeg"), JPG(".jpg"), PNG(".png");
|
||||
@@ -58,7 +42,7 @@ public class FileUtils {
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static enum IMAGE_DATA_URI_SUFFIX {
|
||||
GIF("data:" + IMAGE_MIME_TYPE.GIF.getValue() + ";base64,"), JPEG("data:" + IMAGE_MIME_TYPE.JPEG.getValue() + ";base64,"), PNG(
|
||||
@@ -73,107 +57,6 @@ public class FileUtils {
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
|
||||
public static Random random = new Random();
|
||||
|
||||
/**
|
||||
* Generate a random String
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String generateRandomString() {
|
||||
String characters = "abcdefghijklmnopqrstuvwxyz123456789";
|
||||
int length = generateRandomNumber();
|
||||
char[] text = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
text[i] = characters.charAt(random.nextInt(characters.length()));
|
||||
}
|
||||
return new String(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random number between 5 to 16
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static int generateRandomNumber() {
|
||||
Random randomGenerator = new Random();
|
||||
return randomGenerator.nextInt(11) + 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the directory path for storing the new document
|
||||
*
|
||||
* @param entityType
|
||||
* @param entityId
|
||||
* @return
|
||||
*/
|
||||
public static String generateFileParentDirectory(String entityType, Long entityId) {
|
||||
return FileUtils.MIFOSX_BASE_DIR + File.separator + ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim()
|
||||
+ File.separator + "documents" + File.separator + entityType + File.separator + entityId + File.separator
|
||||
+ FileUtils.generateRandomString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate directory path for storing new Image
|
||||
*/
|
||||
public static String generateClientImageParentDirectory(final Long resourceId) {
|
||||
return FileUtils.MIFOSX_BASE_DIR + File.separator + ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim()
|
||||
+ File.separator + "images" + File.separator + "clients" + File.separator + resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uploadedInputStream
|
||||
* @param uploadedFileLocation
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String saveToFileSystem(InputStream uploadedInputStream, String uploadedFileLocation, String fileName) throws IOException {
|
||||
String fileLocation = uploadedFileLocation + File.separator + fileName;
|
||||
OutputStream out = new FileOutputStream(new File(fileLocation));
|
||||
int read = 0;
|
||||
byte[] bytes = new byte[1024];
|
||||
|
||||
while ((read = uploadedInputStream.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param base64EncodedImage
|
||||
* @param uploadedFileLocation
|
||||
* @param fileName
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String saveToFileSystem(Base64EncodedImage base64EncodedImage, String uploadedFileLocation, String fileName)
|
||||
throws IOException {
|
||||
String fileLocation = uploadedFileLocation + File.separator + fileName + base64EncodedImage.getFileExtension();
|
||||
OutputStream out = new FileOutputStream(new File(fileLocation));
|
||||
byte[] imgBytes = Base64.decode(base64EncodedImage.getBase64EncodedString());
|
||||
out.write(imgBytes);
|
||||
out.flush();
|
||||
out.close();
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileSize
|
||||
* @param name
|
||||
*/
|
||||
public static void validateFileSizeWithinPermissibleRange(Long fileSize, String name, int maxFileSize) {
|
||||
/**
|
||||
* Using Content-Length gives me size of the entire request, which is
|
||||
* good enough for now for a fast fail as the length of the rest of the
|
||||
* content i.e name and description while compared to the uploaded file
|
||||
* size is negligible
|
||||
**/
|
||||
if (fileSize != null && ((fileSize / (1024 * 1024)) > maxFileSize)) { throw new DocumentManagementException(name, fileSize,
|
||||
maxFileSize); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +93,17 @@ public class FileUtils {
|
||||
return new Base64EncodedImage(base64EncodedString, fileExtension);
|
||||
}
|
||||
|
||||
public static void validateFileSizeWithinPermissibleRange(Long fileSize, String name) {
|
||||
/**
|
||||
* Using Content-Length gives me size of the entire request, which is
|
||||
* good enough for now for a fast fail as the length of the rest of the
|
||||
* content i.e name and description while compared to the uploaded file
|
||||
* size is negligible
|
||||
**/
|
||||
if (fileSize != null && ((fileSize / (1024 * 1024)) > ContentRepository.MAX_FILE_UPLOAD_SIZE_IN_MB)) { throw new ContentManagementException(
|
||||
name, fileSize, ContentRepository.MAX_FILE_UPLOAD_SIZE_IN_MB); }
|
||||
}
|
||||
|
||||
public static void validateClientImageNotEmpty(String imageFileName) {
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
if (imageFileName == null) {
|
||||
@@ -223,12 +117,28 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteClientImage(final Long resourceId, final String location) {
|
||||
File fileToBeDeleted = new File(location);
|
||||
boolean fileDeleted = fileToBeDeleted.delete();
|
||||
if (!fileDeleted) {
|
||||
// no need to throw an Error, simply log a warning
|
||||
logger.warn("Unable to delete image associated with clients with Id " + resourceId);
|
||||
/**
|
||||
* Generate a random String
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String generateRandomString() {
|
||||
String characters = "abcdefghijklmnopqrstuvwxyz123456789";
|
||||
int length = generateRandomNumber();
|
||||
char[] text = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
text[i] = characters.charAt(random.nextInt(characters.length()));
|
||||
}
|
||||
return new String(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random number between 5 to 16
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static int generateRandomNumber() {
|
||||
Random randomGenerator = new Random();
|
||||
return randomGenerator.nextInt(11) + 5;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.infrastructure.documentmanagement.contentrepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.FileData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.ImageData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.exception.ContentManagementException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.lowagie.text.pdf.codec.Base64;
|
||||
|
||||
public class FileSystemContentRepository implements ContentRepository {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(FileSystemContentRepository.class);
|
||||
|
||||
public static final String MIFOSX_BASE_DIR = System.getProperty("user.home") + File.separator + ".mifosx";
|
||||
|
||||
@Override
|
||||
public String saveFile(InputStream uploadedInputStream, DocumentCommand documentCommand) {
|
||||
String fileName = documentCommand.getFileName();
|
||||
String uploadDocumentLocation = generateFileParentDirectory(documentCommand.getParentEntityType(),
|
||||
documentCommand.getParentEntityId());
|
||||
|
||||
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(documentCommand.getSize(), fileName);
|
||||
makeDirectories(uploadDocumentLocation);
|
||||
|
||||
String fileLocation = uploadDocumentLocation + File.separator + fileName;
|
||||
|
||||
writeFileToFileSystem(fileName, uploadedInputStream, fileLocation);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveImage(InputStream uploadedInputStream, Long resourceId, String imageName, Long fileSize) {
|
||||
String uploadImageLocation = generateClientImageParentDirectory(resourceId);
|
||||
|
||||
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
|
||||
makeDirectories(uploadImageLocation);
|
||||
|
||||
String fileLocation = uploadImageLocation + File.separator + imageName;
|
||||
|
||||
writeFileToFileSystem(imageName, uploadedInputStream, fileLocation);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveImage(Base64EncodedImage base64EncodedImage, Long resourceId, String imageName) {
|
||||
String uploadImageLocation = generateClientImageParentDirectory(resourceId);
|
||||
|
||||
makeDirectories(uploadImageLocation);
|
||||
|
||||
String fileLocation = uploadImageLocation + File.separator + imageName + base64EncodedImage.getFileExtension();
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(new File(fileLocation));
|
||||
byte[] imgBytes = Base64.decode(base64EncodedImage.getBase64EncodedString());
|
||||
out.write(imgBytes);
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new ContentManagementException(imageName, ioe.getMessage());
|
||||
}
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteImage(final Long resourceId, final String location) {
|
||||
final boolean fileDeleted = deleteFile(location);
|
||||
if (!fileDeleted) {
|
||||
// no need to throw an Error, simply log a warning
|
||||
logger.warn("Unable to delete image associated with clients with Id " + resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String fileName, String documentPath) {
|
||||
final boolean fileDeleted = deleteFile(documentPath);
|
||||
if (!fileDeleted) { throw new ContentManagementException(fileName, null); }
|
||||
}
|
||||
|
||||
private boolean deleteFile(String documentPath) {
|
||||
final File fileToBeDeleted = new File(documentPath);
|
||||
return fileToBeDeleted.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.FILE_SYSTEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileData fetchFile(DocumentData documentData) {
|
||||
File file = new File(documentData.fileLocation());
|
||||
return new FileData(file, documentData.fileName(), documentData.contentType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageData fetchImage(ImageData imageData) {
|
||||
File file = new File(imageData.location());
|
||||
imageData.updateContent(file);
|
||||
return imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the directory path for storing the new document
|
||||
*
|
||||
* @param entityType
|
||||
* @param entityId
|
||||
* @return
|
||||
*/
|
||||
private String generateFileParentDirectory(String entityType, Long entityId) {
|
||||
return FileSystemContentRepository.MIFOSX_BASE_DIR + File.separator
|
||||
+ ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "documents" + File.separator
|
||||
+ entityType + File.separator + entityId + File.separator + ContentRepositoryUtils.generateRandomString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate directory path for storing new Image
|
||||
*/
|
||||
private String generateClientImageParentDirectory(final Long resourceId) {
|
||||
return FileSystemContentRepository.MIFOSX_BASE_DIR + File.separator
|
||||
+ ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "images" + File.separator
|
||||
+ "clients" + File.separator + resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create the directory if it does not exist *
|
||||
*/
|
||||
private void makeDirectories(String uploadDocumentLocation) {
|
||||
if (!new File(uploadDocumentLocation).isDirectory()) {
|
||||
new File(uploadDocumentLocation).mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFileToFileSystem(String fileName, InputStream uploadedInputStream, String fileLocation) {
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(new File(fileLocation));
|
||||
int read = 0;
|
||||
byte[] bytes = new byte[1024];
|
||||
|
||||
while ((read = uploadedInputStream.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException ioException) {
|
||||
throw new ContentManagementException(fileName, ioException.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package org.mifosplatform.infrastructure.documentmanagement.contentrepository;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.FileData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.data.ImageData;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.exception.ContentManagementException;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentNotFoundException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.amazonaws.AmazonClientException;
|
||||
import com.amazonaws.AmazonServiceException;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.DeleteObjectRequest;
|
||||
import com.amazonaws.services.s3.model.GetObjectRequest;
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata;
|
||||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||
import com.amazonaws.services.s3.model.S3Object;
|
||||
import com.lowagie.text.pdf.codec.Base64;
|
||||
|
||||
public class S3ContentRepository implements ContentRepository {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(S3ContentRepository.class);
|
||||
|
||||
private final String s3BucketName;
|
||||
private AmazonS3 s3Client;
|
||||
|
||||
public S3ContentRepository(String bucketName, String secretKey, String accessKey) {
|
||||
this.s3BucketName = bucketName;
|
||||
this.s3Client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveFile(InputStream toUpload, DocumentCommand documentCommand) {
|
||||
String fileName = documentCommand.getFileName();
|
||||
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(documentCommand.getSize(), fileName);
|
||||
|
||||
String uploadDocFolder = generateFileParentDirectory(documentCommand.getParentEntityType(), documentCommand.getParentEntityId());
|
||||
String uploadDocFullPath = uploadDocFolder + File.separator + fileName;
|
||||
|
||||
uploadDocument(fileName, toUpload, uploadDocFullPath);
|
||||
return uploadDocFullPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String documentName, String documentPath) {
|
||||
try {
|
||||
deleteObjectFromS3(documentPath);
|
||||
} catch (AmazonClientException ace) {
|
||||
throw new ContentManagementException(documentName, ace.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveImage(InputStream toUploadInputStream, Long resourceId, String imageName, Long fileSize) {
|
||||
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
|
||||
String uploadImageLocation = generateClientImageParentDirectory(resourceId);
|
||||
String fileLocation = uploadImageLocation + File.separator + imageName;
|
||||
|
||||
uploadDocument(imageName, toUploadInputStream, fileLocation);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveImage(Base64EncodedImage base64EncodedImage, Long resourceId, String imageName) {
|
||||
String uploadImageLocation = generateClientImageParentDirectory(resourceId);
|
||||
String fileLocation = uploadImageLocation + File.separator + imageName + base64EncodedImage.getFileExtension();
|
||||
InputStream toUploadInputStream = new ByteArrayInputStream(Base64.decode(base64EncodedImage.getBase64EncodedString()));
|
||||
|
||||
uploadDocument(imageName, toUploadInputStream, fileLocation);
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteImage(Long resourceId, String location) {
|
||||
try {
|
||||
deleteObjectFromS3(location);
|
||||
} catch (AmazonServiceException ase) {
|
||||
deleteObjectAmazonServiceExceptionMessage(ase);
|
||||
logger.warn("Unable to delete image associated with clients with Id " + resourceId);
|
||||
} catch (AmazonClientException ace) {
|
||||
deleteObjectAmazonClientExceptionMessage(ace);
|
||||
logger.warn("Unable to delete image associated with clients with Id " + resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.S3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileData fetchFile(DocumentData documentData) throws DocumentNotFoundException {
|
||||
FileData fileData = null;
|
||||
String fileName = documentData.fileName();
|
||||
try {
|
||||
logger.info("Downloading an object");
|
||||
S3Object s3object = s3Client.getObject(new GetObjectRequest(s3BucketName, documentData.fileLocation()));
|
||||
fileData = new FileData(s3object.getObjectContent(), fileName, documentData.contentType());
|
||||
} catch (AmazonClientException ace) {
|
||||
logger.error(ace.getMessage());
|
||||
throw new DocumentNotFoundException(documentData.getParentEntityType(), documentData.getParentEntityId(), documentData.getId());
|
||||
}
|
||||
return fileData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageData fetchImage(ImageData imageData) {
|
||||
S3Object s3object = s3Client.getObject(new GetObjectRequest(s3BucketName, imageData.location()));
|
||||
imageData.updateContent(s3object.getObjectContent());
|
||||
return imageData;
|
||||
}
|
||||
|
||||
private void deleteObjectAmazonClientExceptionMessage(AmazonClientException ace) {
|
||||
String message = "Caught an AmazonClientException." + "Error Message: " + ace.getMessage();
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
private void deleteObjectAmazonServiceExceptionMessage(AmazonServiceException ase) {
|
||||
String message = "Caught an AmazonServiceException." + "Error Message: " + ase.getMessage() + "HTTP Status Code: "
|
||||
+ ase.getStatusCode() + "AWS Error Code: " + ase.getErrorCode() + "Error Type: " + ase.getErrorType()
|
||||
+ "Request ID: " + ase.getRequestId();
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
private String generateFileParentDirectory(String entityType, Long entityId) {
|
||||
return "documents" + File.separator + entityType + File.separator + entityId + File.separator
|
||||
+ ContentRepositoryUtils.generateRandomString();
|
||||
}
|
||||
|
||||
private String generateClientImageParentDirectory(Long resourceId) {
|
||||
return "images" + File.separator + "clients" + File.separator + resourceId;
|
||||
}
|
||||
|
||||
private void deleteObjectFromS3(String location) {
|
||||
s3Client.deleteObject(new DeleteObjectRequest(s3BucketName, location));
|
||||
}
|
||||
|
||||
private void uploadDocument(String filename, InputStream inputStream, String s3UploadLocation) throws ContentManagementException {
|
||||
try {
|
||||
logger.info("Uploading a new object to S3 from a file to " + s3UploadLocation);
|
||||
s3Client.putObject(new PutObjectRequest(this.s3BucketName, s3UploadLocation, inputStream, new ObjectMetadata()));
|
||||
} catch (AmazonClientException ace) {
|
||||
String message = ace.getMessage();
|
||||
throw new ContentManagementException(filename, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-7
@@ -5,16 +5,15 @@
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.documentmanagement.data;
|
||||
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
|
||||
/**
|
||||
* Immutable data object represent document being managed on platform.
|
||||
*/
|
||||
public class DocumentData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
@SuppressWarnings("unused")
|
||||
private final String parentEntityType;
|
||||
@SuppressWarnings("unused")
|
||||
private final Long parentEntityId;
|
||||
@SuppressWarnings("unused")
|
||||
private final String name;
|
||||
@@ -24,10 +23,11 @@ public class DocumentData {
|
||||
private final String type;
|
||||
@SuppressWarnings("unused")
|
||||
private final String description;
|
||||
private String location;
|
||||
private final String location;
|
||||
private final Integer storageType;
|
||||
|
||||
public DocumentData(final Long id, final String parentEntityType, final Long parentEntityId, final String name, final String fileName,
|
||||
final Long size, final String type, final String description, final String location) {
|
||||
final Long size, final String type, final String description, final String location, Integer storageType) {
|
||||
this.id = id;
|
||||
this.parentEntityType = parentEntityType;
|
||||
this.parentEntityId = parentEntityId;
|
||||
@@ -37,6 +37,7 @@ public class DocumentData {
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.location = location;
|
||||
this.storageType = storageType;
|
||||
}
|
||||
|
||||
public String contentType() {
|
||||
@@ -51,8 +52,20 @@ public class DocumentData {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public void setLocation(final String location) {
|
||||
this.location = location;
|
||||
public StorageType storageType() {
|
||||
return StorageType.fromInt(storageType);
|
||||
}
|
||||
|
||||
public String getParentEntityType() {
|
||||
return this.parentEntityType;
|
||||
}
|
||||
|
||||
public Long getParentEntityId() {
|
||||
return this.parentEntityId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
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;
|
||||
|
||||
public class FileData {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileData.class);
|
||||
|
||||
private final File file;
|
||||
private final String fileName;
|
||||
private final String contentType;
|
||||
private final InputStream inputStream;
|
||||
|
||||
public FileData(File file, String fileName, String contentType) {
|
||||
this.file = file;
|
||||
this.fileName = fileName;
|
||||
this.contentType = contentType;
|
||||
this.inputStream = null;
|
||||
}
|
||||
|
||||
public FileData(InputStream inputStream, String fileName, String contentType) {
|
||||
this.file = null;
|
||||
this.inputStream = inputStream;
|
||||
this.fileName = fileName;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public String contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public InputStream file() {
|
||||
try {
|
||||
if (inputStream == null) { return new FileInputStream(file); }
|
||||
return inputStream;
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.error(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package org.mifosplatform.infrastructure.documentmanagement.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.contentrepository.ContentRepositoryUtils;
|
||||
import org.mifosplatform.infrastructure.documentmanagement.domain.StorageType;
|
||||
|
||||
public class ImageData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long imageId;
|
||||
private final String location;
|
||||
private final Integer storageType;
|
||||
private final String entityDisplayName;
|
||||
|
||||
private File file;
|
||||
private String contentType;
|
||||
private InputStream inputStream;
|
||||
|
||||
public ImageData(Long imageId, String location, Integer storageType, String entityDisplayName) {
|
||||
this.imageId = imageId;
|
||||
this.location = location;
|
||||
this.storageType = storageType;
|
||||
this.entityDisplayName = entityDisplayName;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
// TODO Vishwas Fix error handling
|
||||
try {
|
||||
if (inputStream == null) {
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
return IOUtils.toByteArray(fileInputStream);
|
||||
}
|
||||
|
||||
return IOUtils.toByteArray(inputStream);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String setImageContentType() {
|
||||
String contentType = ContentRepositoryUtils.IMAGE_MIME_TYPE.JPEG.getValue();
|
||||
|
||||
if (file != null) {
|
||||
String fileName = file.getName();
|
||||
|
||||
if (StringUtils.endsWith(fileName, ContentRepositoryUtils.IMAGE_FILE_EXTENSION.GIF.getValue())) {
|
||||
contentType = ContentRepositoryUtils.IMAGE_MIME_TYPE.GIF.getValue();
|
||||
} else if (StringUtils.endsWith(fileName, ContentRepositoryUtils.IMAGE_FILE_EXTENSION.PNG.getValue())) {
|
||||
contentType = ContentRepositoryUtils.IMAGE_MIME_TYPE.PNG.getValue();
|
||||
}
|
||||
}
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String contentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
public StorageType storageType() {
|
||||
return StorageType.fromInt(storageType);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return this.file.getName();
|
||||
}
|
||||
|
||||
public String location() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public void updateContent(InputStream objectContent) {
|
||||
this.inputStream = objectContent;
|
||||
}
|
||||
|
||||
public void updateContent(File file) {
|
||||
this.file = file;
|
||||
setImageContentType();
|
||||
}
|
||||
|
||||
public String getEntityDisplayName() {
|
||||
return this.entityDisplayName;
|
||||
}
|
||||
|
||||
}
|
||||
+10
-4
@@ -41,15 +41,18 @@ public class Document extends AbstractPersistable<Long> {
|
||||
@Column(name = "location", length = 500)
|
||||
private String location;
|
||||
|
||||
@Column(name = "storage_type_enum")
|
||||
private Integer storageType;
|
||||
|
||||
public Document() {}
|
||||
|
||||
public static Document createNew(final String parentEntityType, final Long parentEntityId, final String name, final String fileName,
|
||||
final Long size, final String type, final String description, final String location) {
|
||||
return new Document(parentEntityType, parentEntityId, name, fileName, size, type, description, location);
|
||||
final Long size, final String type, final String description, final String location, final StorageType storageType) {
|
||||
return new Document(parentEntityType, parentEntityId, name, fileName, size, type, description, location, storageType);
|
||||
}
|
||||
|
||||
private Document(final String parentEntityType, final Long parentEntityId, final String name, final String fileName, final Long size,
|
||||
final String type, final String description, final String location) {
|
||||
final String type, final String description, final String location, final StorageType storageType) {
|
||||
this.parentEntityType = StringUtils.defaultIfEmpty(parentEntityType, null);
|
||||
this.parentEntityId = parentEntityId;
|
||||
this.name = StringUtils.defaultIfEmpty(name, null);
|
||||
@@ -58,6 +61,7 @@ public class Document extends AbstractPersistable<Long> {
|
||||
this.type = StringUtils.defaultIfEmpty(type, null);
|
||||
this.description = StringUtils.defaultIfEmpty(description, null);
|
||||
this.location = StringUtils.defaultIfEmpty(location, null);
|
||||
this.storageType = storageType.getValue();
|
||||
}
|
||||
|
||||
public void update(final DocumentCommand command) {
|
||||
@@ -79,7 +83,6 @@ public class Document extends AbstractPersistable<Long> {
|
||||
if (command.isSizeChanged()) {
|
||||
this.size = command.getSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getParentEntityType() {
|
||||
@@ -146,4 +149,7 @@ public class Document extends AbstractPersistable<Long> {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public StorageType storageType() {
|
||||
return StorageType.fromInt(this.storageType);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.infrastructure.documentmanagement.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_image")
|
||||
public final class Image extends AbstractPersistable<Long> {
|
||||
|
||||
@Column(name = "location", length = 500)
|
||||
private String location;
|
||||
|
||||
@Column(name = "storage_type_enum")
|
||||
private Integer storageType;
|
||||
|
||||
public Image(String location, StorageType storageType) {
|
||||
this.location = location;
|
||||
this.storageType = storageType.getValue();
|
||||
}
|
||||
|
||||
protected Image() {
|
||||
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public Integer getStorageType() {
|
||||
return this.storageType;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public void setStorageType(Integer storageType) {
|
||||
this.storageType = storageType;
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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.infrastructure.documentmanagement.domain;
|
||||
|
||||
import org.mifosplatform.portfolio.client.domain.Client;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface ImageRepository extends JpaRepository<Image, Long>, JpaSpecificationExecutor<Client> {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user