mifosx-270 api docs audit/makerchecker/permissions + wip mifosx-97 make functionality match api docs

This commit is contained in:
John Woodlock
2013-04-05 02:09:04 +01:00
parent 0ade90b70c
commit d98360dd39
8 changed files with 725 additions and 264 deletions
+439 -53
View File
@@ -937,9 +937,8 @@
<td><a href="#permissions">Permission</a></td> <td><a href="#permissions">Permission</a></td>
<td>permissions</td> <td>permissions</td>
<td></td> <td></td>
<td><a href="#permissions_list">List Application <td><a href="#permissions_list">List Application Permissions</a></td>
Permissions</a></td> <td><a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a></td>
<td></td>
<td></td> <td></td>
</tr> </tr>
</table> </table>
@@ -7055,8 +7054,17 @@ Content-Type: application/json
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h3>Audits</h3> <h3>Audits</h3>
<p> <p>Every non-read Mifos API request is audited. A fully processed request can
<p /> not be changed or deleted. See maker checker api for situations where an audit is not
fully processed.<br><br>
Permissions: To search and look at audit entries a user needs to be attached to a role
that has one of the ALL_FUNCTIONS, ALL_FUNCTIONS_READ or READ_AUDIT permissions.<br><br>
Data Scope: A user can only see audits that are within their data scope. However,
'head office' users can see all audits including those that aren't office/branch related
e.g. Loan Product changes.
<p/>
</div> </div>
</div> </div>
@@ -7065,9 +7073,106 @@ Content-Type: application/json
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h4>List Audits</h4> <h4>List Audits</h4>
<p></p> <p>Get a list of audits that match the criteria supplied,
and are within the requestors' data scope.</p>
<h5>Arguments</h5>
<dl class="argument-list">
<dt>actionName</dt>
<dd>optional</dd>
<dd>Examples: CREATE, UPDATE, DISBURSE</dd>
<dt>entityName</dt>
<dd>optional</dd>
<dd>Examples: CLIENT, LOAN, FUND</dd>
<dt>resourceId</dt>
<dd>optional, <span>
The id value of the entityName</span>
</dd>
<dt>makerId</dt>
<dd>optional, <span>
The id value of the application user creating the entry</span>
</dd>
<dt>makerDateTimeFrom</dt>
<dd>optional, <span>
Get entries created on or after this</span>
</dd>
<dd>Example: 2013-04-10 08:00:00</dd>
<dt>makerDateTimeTo</dt>
<dd>optional, <span>
Get entries created on or before this</span>
</dd>
<dd>Example: 2013-05-10 08:00:00</dd>
<dt>checkerId</dt>
<dd>optional, <span>
The id value of the application user that checked (approved) the entry</span>
</dd>
<dt>checkerDateTimeFrom</dt>
<dd>optional, <span>
Get entries checked on or after this</span>
</dd>
<dd>Example: 2013-04-10 08:00:00</dd>
<dt>checkerDateTimeTo</dt>
<dd>optional, <span>
Get entries checked on or before this</span>
</dd>
<dd>Example: 2013-05-10 18:00:00</dd>
<dt>processingResult</dt>
<dd>optional, <span>
The enum value of the processing status</span>
</dd>
<dd>values:<br>
0: Invalid<br>
1: processed<br>
2: awaiting.approval<br>
3: rejected
</dd>
<dt>officeId</dt>
<dd>optional, <span>
The id value of the office/branch associated with the entry (if there is one)</span>
</dd>
<dt>groupId</dt>
<dd>optional, <span>
The id value of the group associated with the entry (if there is one).
A group is a general idea and may be a Center, a Group, a Communal Bank or other.</span>
</dd>
<dt>clientId</dt>
<dd>optional, <span>
The id value of the client associated with the entry (if there is one)</span>
</dd>
<dt>loanId</dt>
<dd>optional, <span>
The id value of the loan associated with the entry (if there is one)</span>
</dd>
<dt>savingsAccountId</dt>
<dd>optional, <span>
The id value of the savings account associated with the entry (if there is one)</span>
</dd>
<dt>includeJson</dt>
<dd>optional, <span>
Values are true, false. Default is false.</span>
</dd>
</dl>
<p>Example Requests:</p> <p>Example Requests:</p>
<div class=apiClick>audits</div> <div class=apiClick>audits</div><br>
<div class=apiClick>audits?fields=madeOnDate,maker,processingResult</div><br>
<div class=apiClick>audits?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00</div><br>
<div class=apiClick>audits?officeId=1</div><br>
<div class=apiClick>audits?officeId=1&includeJson=true</div>
</div> </div>
<div class="method-example"> <div class="method-example">
<code class="method-declaration"> <code class="method-declaration">
@@ -7156,7 +7261,6 @@ GET https://DomainName/api/v1/audits
</div> </div>
</div> </div>
<a id="audits_searchtemplate" name="audits_searchtemplate" <a id="audits_searchtemplate" name="audits_searchtemplate"
class="old-syle-anchor">&nbsp;</a> class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
@@ -7262,8 +7366,212 @@ GET https://DomainName/api/v1/audits/searchtemplate
</div> </div>
<a id="audits_retrieve" name="audits_retrieve"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve an Audit Entry</h4>
<p></p>
<p>Example Requests:</p>
<div class=apiClick>audits/20</div>
<div class=apiClick>audits/20?fields=madeOnDate,maker,processingResult</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/audits
</code>
<code class="method-response">
{
"id": 20,
"actionName": "REPAYMENT",
"entityName": "LOAN",
"resourceId": 868,
"maker": "dataentry1",
"madeOnDate": 1358449025000,
"processingResult": "processed",
"commandAsJson": "{\"transactionDate\":\"28 September 2012\",\"transactionAmount\":\"1,967.00\",\"locale\":\"en\",\"dateFormat\":\"dd MMMM yyyy\"}",
"officeName": "another office",
"clientName": "another client",
"loanAccountNo": "23"
}
</code>
</div>
</div>
<!-- Maker Checker starts here -->
<a id="makercheckers" name="makercheckers" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Maker Checker (or 4-eye) functionality</h3>
<p>Mifos X Maker Checker functionality allows an MFI to define transactions
as having a maker and a checker phase. One user enters, deletes or changes data.
Then, another user that has "Checker" rights for that transaction, can inspect
and approve the data.
</p>
<p>By default, Maker Checker functionality is disabled.
See <a href="#configs_globalconfig_update">Update Global Configuration</a> to enable/disable
Maker Checker functionality at a global level.
</p>
<p>
Additionally, Maker Checker functionality for each transaction (permission) is disabled by default.
see <a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a> to enable/disable
Maker Checker functionality at a transaction (permission) level.
</p>
<p>
Finally, to give checking rights to a user (via a role associated with the user) see
<a href="#rolespermissions_update">Update a Role's Permissions</a>
</p>
<p>For example, <br>
<code>
{
"permissions":{
"CREATE_GUARANTOR_CHECKER":true,
"CREATE_CLIENT_CHECKER":true
}
}
</code>
<br>
will give checking rights for CREATE_GUARANTOR and CREATE_CLIENT</p>
<p>Alternatively, the special permissions ALL_FUNCTIONS or CHECKER_SUPER_USER will give blanket
checking rights.
</p>
<p>
When a user "makes" an entry that is enabled for Maker Checker, it is audited and the audit status
is set to a value of 2 (awaiting.approval).
</p>
Checkers can only Check and approve entries that they have been permitted to check.<br><br>
Checkers can only Check and approve entries that are within their data scope. However,
'head office' Checkers can Check and approve all entries
including those that aren't office/branch related (as long as they have the Checker permissions)
e.g. Loan Product changes.
<p/>
</div>
</div>
<a id="makercheckers_list" name="makercheckers_list"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>List Maker Checker Entries</h4>
<p>Get a list of entries that can be checked by the requestor that match the criteria supplied.</p>
<h5>Arguments</h5>
<dl class="argument-list">
<dt>actionName</dt>
<dd>optional</dd>
<dd>Examples: CREATE, UPDATE, DISBURSE</dd>
<dt>entityName</dt>
<dd>optional</dd>
<dd>Examples: CLIENT, LOAN, FUND</dd>
<dt>resourceId</dt>
<dd>optional, <span>
The id value of the entityName</span>
</dd>
<dt>makerId</dt>
<dd>optional, <span>
The id value of the application user creating the entry</span>
</dd>
<dt>makerDateTimeFrom</dt>
<dd>optional, <span>
Get entries created on or after this</span>
</dd>
<dd>Example: 2013-04-10 08:00:00</dd>
<dt>makerDateTimeTo</dt>
<dd>optional, <span>
Get entries created on or before this</span>
</dd>
<dd>Example: 2013-05-10 08:00:00</dd>
<dt>officeId</dt>
<dd>optional, <span>
The id value of the office/branch associated with the entry (if there is one)</span>
</dd>
<dt>groupId</dt>
<dd>optional, <span>
The id value of the group associated with the entry (if there is one).
A group is a general idea and may be a Center, a Group, a Communal Bank or other.</span>
</dd>
<dt>clientId</dt>
<dd>optional, <span>
The id value of the client associated with the entry (if there is one)</span>
</dd>
<dt>loanId</dt>
<dd>optional, <span>
The id value of the loan associated with the entry (if there is one)</span>
</dd>
<dt>savingsAccountId</dt>
<dd>optional, <span>
The id value of the savings account associated with the entry (if there is one)</span>
</dd>
<dt>includeJson</dt>
<dd>optional, <span>
Values are true, false. Default is false.</span>
</dd>
</dl>
<p>Example Requests:</p>
<div class=apiClick>makercheckers</div><br>
<div class=apiClick>makercheckers?fields=madeOnDate,maker,processingResult</div><br>
<div class=apiClick>makercheckers?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00</div><br>
<div class=apiClick>makercheckers?officeId=1</div><br>
<div class=apiClick>makercheckers?officeId=1&includeJson=true</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/makercheckers
</code>
<code class="method-response">
[
{
"id": 654,
"actionName": "CREATE",
"entityName": "LOANPRODUCT",
"resourceId": 15,
"maker": "keithwoodlock",
"madeOnDate": 1364924512000,
"processingResult": "awaiting.approval"
},
{
"id": 666,
"actionName": "CREATE",
"entityName": "CLIENT",
"resourceId": 363,
"maker": "keithwoodlock",
"madeOnDate": 1365012843000,
"processingResult": "awaiting.approval",
"officeName": "my office name"
},
{
"id": 670,
"actionName": "CREATE",
"entityName": "CLIENTNOTE",
"resourceId": 287,
"maker": "keithwoodlock",
"madeOnDate": 1365014204000,
"processingResult": "awaiting.approval",
"officeName": "my office name",
"clientName": "gg ggg"
}
]
</code>
</div>
</div>
<a id="makercheckers_searchtemplate" name="makercheckers_searchtemplate" <a id="makercheckers_searchtemplate" name="makercheckers_searchtemplate"
class="old-syle-anchor">&nbsp;</a> class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
@@ -7320,6 +7628,42 @@ GET https://DomainName/api/v1/audits/searchtemplate
</div> </div>
</div> </div>
<a id="makercheckers_approve" name="makercheckers_approve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Approve Maker Checker Entry</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/makercheckers/{auditId}?command=approve
</code>
<code class="method-request">
POST makercheckers/1?command=approve
Content-Type: application/json
</code>
<code class="method-response">{ "auditId": 1 } </code>
</div>
</div>
<a id="makercheckers_delete" name="makercheckers_delete" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Delete Maker Checker Entry</h4>
</div>
<div class="method-example">
<code class="method-declaration">
DELETE https://DomainName/api/v1/makercheckers/{auditId}
</code>
<code class="method-request">
DELETE makercheckers/1
Content-Type: application/json
</code>
<code class="method-response">{ "auditId": 1 } </code>
</div>
</div>
<!-- Fund starts here --> <!-- Fund starts here -->
<a id="funds_create" name="funds_create" class="old-syle-anchor">&nbsp;</a> <a id="funds_create" name="funds_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
@@ -11324,6 +11668,9 @@ Request body:
<div class="method-description"> <div class="method-description">
<h3>Permissions</h3> <h3>Permissions</h3>
<p>An API capability to support management of application permissions for user administration.</p> <p>An API capability to support management of application permissions for user administration.</p>
<p>There is no Mifos X functionality for creating or deleting permissions. Permissions come pre-installed.</p>
<p>Permissions are not updated, except in the case of enabling or disabling non-read transactions for
Maker Checker functionality</p>
</div> </div>
</div> </div>
@@ -11332,10 +11679,25 @@ Request body:
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h4>List Application Permissions</h4> <h4>List Application Permissions</h4>
<h5>Arguments</h5>
<dl class="argument-list">
<dt>makerCheckerable</dt>
<dd>optional, <span>
Values are true, false. Default is false.</span>
<p>If makerCheckerable=false or not supplied then a list of application permissions is returned. The "selected"
attribute is always true in this case.
</p>
<p>If makerCheckerable=true then the "selected" attribute shows whether the permission is
enabled for Maker Check functionality.
</p>
<p>
Note: Each Mifos X transaction is associated with a permission.
</p>
</dd>
</dl>
<p>Example Requests:</p> <p>Example Requests:</p>
<div class=apiClick>permissions</div> <div class=apiClick>permissions</div><br><br>
<br> <div class=apiClick>permissions?makerCheckerable=true</div><br><br>
<br>
<div class=apiClick>permissions?fields=grouping,code</div> <div class=apiClick>permissions?fields=grouping,code</div>
</div> </div>
<div class="method-example"> <div class="method-example">
@@ -11364,6 +11726,30 @@ GET https://DomainName/api/v1/permissions
</div> </div>
</div> </div>
<a id="permissions_update" name="permissions_update" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Enable/Disable Permissions for Maker Checker</h4>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/permissions
</code>
<code class="method-request">
PUT permissions
Content-Type: application/json
Request Body:
{
"permissions":{
"CREATE_GUARANTOR":true,
"CREATE_CLIENT":true
}
}
</code>
</div>
</div>
<!-- start of accounting api docs --> <!-- start of accounting api docs -->
<a id="glaccounts" name="glaccounts" class="old-syle-anchor">&nbsp;</a> <a id="glaccounts" name="glaccounts" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
@@ -12985,46 +13371,46 @@ GET https://DomainName/api/v1/search?query=000000111
<td><a href="#configs_codes_codevalues_update">Update a Code Value</a></td> <td><a href="#configs_codes_codevalues_update">Update a Code Value</a></td>
<td><a href="#configs_codes_codevalues_delete">Delete a Code Value</a></td> <td><a href="#configs_codes_codevalues_delete">Delete a Code Value</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="#audits">Audits</a></td> <td><a href="#audits">Audits</a></td>
<td>audits</td> <td>audits</td>
<td></td> <td></td>
<td><a href="#audits_list">List Audits</a></td> <td><a href="#audits_list">List Audits</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>audits/{auditId}</td> <td>audits/{auditId}</td>
<td></td> <td></td>
<td><a href="#audits_retrieve">Retrieve an Audit Entry</a></td> <td><a href="#audits_retrieve">Retrieve an Audit Entry</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td><a href="#makercheckers">Makercheckers</a></td> <td><a href="#makercheckers">Makercheckers</a></td>
<td>makercheckers</td> <td>makercheckers</td>
<td></td> <td></td>
<td><a href="#makercheckers_list">List Maker Checker Entries</a></td> <td><a href="#makercheckers_list">List Maker Checker Entries</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>makercheckers/{auditId}</td> <td>makercheckers/{auditId}</td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td><a href="#makercheckers_delete">Delete Maker Checker Entry</a></td> <td><a href="#makercheckers_delete">Delete Maker Checker Entry</a></td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td>makercheckers/{auditId}?command=approve</td> <td>makercheckers/{auditId}?command=approve</td>
<td><a href="#makercheckers_approve">Approve Maker Checker Entry</a></td> <td><a href="#makercheckers_approve">Approve Maker Checker Entry</a></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr class="alt"> <tr class="alt">
<td><a href="#funds">Funds</a></td> <td><a href="#funds">Funds</a></td>
<td>funds</td> <td>funds</td>
@@ -13297,8 +13683,8 @@ GET https://DomainName/api/v1/search?query=000000111
<td><a href="#permissions">Permission</a></td> <td><a href="#permissions">Permission</a></td>
<td>permissions</td> <td>permissions</td>
<td></td> <td></td>
<td><a href="#permissions_list">List Permissions</a></td> <td><a href="#permissions_list">List Application Permissions</a></td>
<td></td> <td><a href="#permissions_update">Enable/Disable Permissions for Maker Checker</a></td>
<td></td> <td></td>
</tr> </tr>
</table> </table>
@@ -0,0 +1,15 @@
/*
Remove READ_MAKERCHECKER permission as no longer used.
*/
delete from m_role_permission
where exists
(select 'f' from m_permission p
where p.id = m_role_permission.permission_id and p.code = 'READ_MAKERCHECKER');
delete from m_permission
where code = 'READ_MAKERCHECKER';
@@ -34,7 +34,6 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -50,9 +49,10 @@ public class MakercheckersApiResource {
"checkedOnDate", "processingResult", "commandAsJson", "checkedOnDate", "processingResult", "commandAsJson",
"officeName", "groupLevelName", "groupName", "clientName", "officeName", "groupLevelName", "groupName", "clientName",
"loanAccountNo", "savingsAccountNo")); "loanAccountNo", "savingsAccountNo"));
private final String resourceNameForPermissions = "MAKERCHECKER";
private final PlatformSecurityContext context; //Next line removed because read permissions checks part of read services
//private final String resourceNameForPermissions = "MAKERCHECKER";
private final AuditReadPlatformService readPlatformService; private final AuditReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer<CommandSourceData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<CommandSourceData> toApiJsonSerializer;
private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit; private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit;
@@ -62,14 +62,12 @@ public class MakercheckersApiResource {
@Autowired @Autowired
public MakercheckersApiResource( public MakercheckersApiResource(
final PlatformSecurityContext context,
final AuditReadPlatformService readPlatformService, final AuditReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer<CommandSourceData> toApiJsonSerializer, final DefaultToApiJsonSerializer<CommandSourceData> toApiJsonSerializer,
final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit, final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializerAudit,
final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate, final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate,
final ApiRequestParameterHelper apiRequestParameterHelper, final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService writePlatformService) { final PortfolioCommandSourceWritePlatformService writePlatformService) {
this.context = context;
this.readPlatformService = readPlatformService; this.readPlatformService = readPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer; this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper; this.apiRequestParameterHelper = apiRequestParameterHelper;
@@ -94,9 +92,6 @@ public class MakercheckersApiResource {
@QueryParam("loanid") final Integer loanId, @QueryParam("loanid") final Integer loanId,
@QueryParam("savingsAccountId") final Integer savingsAccountId) { @QueryParam("savingsAccountId") final Integer savingsAccountId) {
context.authenticatedUser().validateHasReadPermission(
resourceNameForPermissions);
final String extraCriteria = getExtraCriteria(actionName, entityName, final String extraCriteria = getExtraCriteria(actionName, entityName,
resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
officeId, groupId, clientId, loanId, savingsAccountId); officeId, groupId, clientId, loanId, savingsAccountId);
@@ -118,9 +113,6 @@ public class MakercheckersApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) { public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(
resourceNameForPermissions);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper
.process(uriInfo.getQueryParameters()); .process(uriInfo.getQueryParameters());
@@ -25,159 +25,175 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_command_source") @Table(name = "m_portfolio_command_source")
public class CommandSource extends AbstractPersistable<Long> { public class CommandSource extends AbstractPersistable<Long> {
@Column(name = "action_name", nullable = true, length = 100) @Column(name = "action_name", nullable = true, length = 100)
private String actionName; private String actionName;
@Column(name = "entity_name", nullable = true, length = 100) @Column(name = "entity_name", nullable = true, length = 100)
private String entityName; private String entityName;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "office_id") @Column(name = "office_id")
private Long officeId; private Long officeId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "group_id") @Column(name = "group_id")
private Long groupId; private Long groupId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "client_id") @Column(name = "client_id")
private Long clientId; private Long clientId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "loan_id") @Column(name = "loan_id")
private Long loanId; private Long loanId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "savings_account_id") @Column(name = "savings_account_id")
private Long savingsId; private Long savingsId;
@Column(name = "api_get_url", length = 100) @Column(name = "api_get_url", length = 100)
private String resourceGetUrl; private String resourceGetUrl;
@Column(name = "resource_id") @Column(name = "resource_id")
private Long resourceId; private Long resourceId;
@Column(name = "subresource_id") @Column(name = "subresource_id")
private Long subresourceId; private Long subresourceId;
@Column(name = "command_as_json", length = 1000) @Column(name = "command_as_json", length = 1000)
private String commandAsJson; private String commandAsJson;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "maker_id", nullable = false) @JoinColumn(name = "maker_id", nullable = false)
private AppUser maker; private AppUser maker;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "made_on_date", nullable = false) @Column(name = "made_on_date", nullable = false)
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date madeOnDate; private Date madeOnDate;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "checker_id", nullable = true) @JoinColumn(name = "checker_id", nullable = true)
private AppUser checker; private AppUser checker;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "checked_on_date", nullable = true) @Column(name = "checked_on_date", nullable = true)
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date checkedOnDate; private Date checkedOnDate;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "processing_result_enum", nullable = false) @Column(name = "processing_result_enum", nullable = false)
private Integer processingResult; private Integer processingResult;
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) { public static CommandSource fullEntryFrom(final CommandWrapper wrapper,
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), command.json(), maker, DateTime.now()); final JsonCommand command, final AppUser maker) {
} return new CommandSource(wrapper.actionName(), wrapper.entityName(),
wrapper.getHref(), command.entityId(), command.subentityId(),
command.json(), maker, DateTime.now());
}
protected CommandSource() { protected CommandSource() {
// //
} }
private CommandSource( private CommandSource(final String actionName, final String entityName,
final String actionName, final String entityName, final String href, final String href, final Long resourceId, final Long subresourceId,
final Long resourceId, final Long subresourceId, final String commandSerializedAsJson, final AppUser maker,
final String commandSerializedAsJson, final AppUser maker, final DateTime madeOnDateTime) {
final DateTime madeOnDateTime) { this.actionName = actionName;
this.actionName = actionName; this.entityName = entityName;
this.entityName = entityName; this.resourceGetUrl = href;
this.resourceGetUrl = href; this.resourceId = resourceId;
this.resourceId = resourceId; this.subresourceId = subresourceId;
this.subresourceId = subresourceId; this.commandAsJson = commandSerializedAsJson;
this.commandAsJson = commandSerializedAsJson; this.maker = maker;
this.maker = maker; this.madeOnDate = madeOnDateTime.toDate();
this.madeOnDate = madeOnDateTime.toDate(); this.processingResult = CommandProcessingResultType.PROCESSED
this.processingResult = CommandProcessingResultType.PROCESSED.getValue(); .getValue();
} }
public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) { public void markAsChecked(final AppUser checker,
this.checker = checker; final DateTime checkedOnDate) {
this.checkedOnDate = checkedOnDate.toDate(); this.checker = checker;
this.processingResult = CommandProcessingResultType.PROCESSED.getValue(); this.checkedOnDate = checkedOnDate.toDate();
} this.processingResult = CommandProcessingResultType.PROCESSED
.getValue();
}
public void updateResourceId(final Long resourceId) { public void updateResourceId(final Long resourceId) {
this.resourceId = resourceId; this.resourceId = resourceId;
} }
public void updateSubresourceId(final Long subresourceId) { public void updateSubresourceId(final Long subresourceId) {
this.subresourceId = subresourceId; this.subresourceId = subresourceId;
} }
public void updateJsonTo(final String json) { public void updateJsonTo(final String json) {
this.commandAsJson = json; this.commandAsJson = json;
} }
public Long resourceId() { public Long resourceId() {
return this.resourceId; return this.resourceId;
} }
public Long subresourceId() { public Long subresourceId() {
return this.subresourceId; return this.subresourceId;
} }
public boolean hasJson() { public boolean hasJson() {
return StringUtils.isNotBlank(this.commandAsJson); return StringUtils.isNotBlank(this.commandAsJson);
} }
public String json() { public String json() {
return this.commandAsJson; return this.commandAsJson;
} }
public String getActionName() { public String getActionName() {
return this.actionName; return this.actionName;
} }
public String getEntityName() { public String getEntityName() {
return this.entityName; return this.entityName;
} }
public String getPermissionCode() { public String getPermissionCode() {
return this.actionName + "_" + this.entityName; return this.actionName + "_" + this.entityName;
} }
public Long getResourceId() { public Long getResourceId() {
return this.resourceId; return this.resourceId;
} }
public Long getSubresourceId() { public Long getSubresourceId() {
return this.subresourceId; return this.subresourceId;
} }
public void markAsAwaitingApproval() { public void markAsAwaitingApproval() {
this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL
} .getValue();
}
public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId) { public boolean isMarkedAsAwaitingApproval() {
this.officeId = officeId; if (this.processingResult
this.groupId = groupId; .equals(CommandProcessingResultType.AWAITING_APPROVAL
this.clientId = clientId; .getValue()))
this.loanId = loanId; return true;
this.savingsId = savingsId;
}
public String getResourceGetUrl() { return false;
return this.resourceGetUrl; }
}
public void updateForAudit(final Long officeId, final Long groupId,
final Long clientId, final Long loanId, final Long savingsId) {
this.officeId = officeId;
this.groupId = groupId;
this.clientId = clientId;
this.loanId = loanId;
this.savingsId = savingsId;
}
public String getResourceGetUrl() {
return this.resourceGetUrl;
}
} }
@@ -0,0 +1,18 @@
/**
* 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.commands.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when client resources are not found.
*/
public class CommandNotAwaitingApprovalException extends AbstractPlatformResourceNotFoundException {
public CommandNotAwaitingApprovalException(Long id) {
super("error.msg.command.id.not.awaiting.approval", "Audit with identifier " + id + " is Not Awaiting Approval", id);
}
}
@@ -13,6 +13,6 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN
public class CommandNotFoundException extends AbstractPlatformResourceNotFoundException { public class CommandNotFoundException extends AbstractPlatformResourceNotFoundException {
public CommandNotFoundException(Long id) { public CommandNotFoundException(Long id) {
super("error.msg.command.id.invalid", "Command with identifier " + id + " does not exist", id); super("error.msg.command.id.invalid", "Audit with identifier " + id + " does not exist", id);
} }
} }
@@ -8,12 +8,13 @@ package org.mifosplatform.commands.service;
import org.mifosplatform.commands.domain.CommandSource; import org.mifosplatform.commands.domain.CommandSource;
import org.mifosplatform.commands.domain.CommandSourceRepository; import org.mifosplatform.commands.domain.CommandSourceRepository;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.exception.CommandNotAwaitingApprovalException;
import org.mifosplatform.commands.exception.CommandNotFoundException;
import org.mifosplatform.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException; import org.mifosplatform.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -21,87 +22,120 @@ import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@Service @Service
public class PortfolioCommandSourceWritePlatformServiceImpl implements PortfolioCommandSourceWritePlatformService { public class PortfolioCommandSourceWritePlatformServiceImpl implements
PortfolioCommandSourceWritePlatformService {
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final CommandSourceRepository commandSourceRepository; private final CommandSourceRepository commandSourceRepository;
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final CommandProcessingService processAndLogCommandService; private final CommandProcessingService processAndLogCommandService;
@Autowired @Autowired
public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityContext context, public PortfolioCommandSourceWritePlatformServiceImpl(
final CommandSourceRepository commandSourceRepository, final FromJsonHelper fromApiJsonHelper, final PlatformSecurityContext context,
final CommandProcessingService processAndLogCommandService) { final CommandSourceRepository commandSourceRepository,
this.context = context; final FromJsonHelper fromApiJsonHelper,
this.commandSourceRepository = commandSourceRepository; final CommandProcessingService processAndLogCommandService) {
this.fromApiJsonHelper = fromApiJsonHelper; this.context = context;
this.processAndLogCommandService = processAndLogCommandService; this.commandSourceRepository = commandSourceRepository;
} this.fromApiJsonHelper = fromApiJsonHelper;
this.processAndLogCommandService = processAndLogCommandService;
}
@Override @Override
public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
boolean isApprovedByChecker = false; boolean isApprovedByChecker = false;
// check if is update of own account details // check if is update of own account details
if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser().getId())) { if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser()
// then allow this operation to proceed. .getId())) {
// maker checker doesnt mean anything here. // then allow this operation to proceed.
isApprovedByChecker = true; // set to true in case permissions have // maker checker doesnt mean anything here.
// been maker-checker enabled by isApprovedByChecker = true; // set to true in case permissions have
// accident. // been maker-checker enabled by
} else { // accident.
// if not user changing their own details - check user has } else {
// permission to perform specific task. // if not user changing their own details - check user has
context.authenticatedUser().validateHasPermissionTo(wrapper.getTaskPermissionName()); // permission to perform specific task.
} context.authenticatedUser().validateHasPermissionTo(
wrapper.getTaskPermissionName());
}
final String json = wrapper.getJson(); final String json = wrapper.getJson();
CommandProcessingResult result = null; CommandProcessingResult result = null;
try { try {
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json); final JsonElement parsedCommand = this.fromApiJsonHelper
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), .parse(json);
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), final JsonCommand command = JsonCommand.from(json, parsedCommand,
wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(), this.fromApiJsonHelper, wrapper.getEntityName(),
wrapper.getTransactionId()); wrapper.getEntityId(), wrapper.getSubentityId(),
wrapper.getGroupId(), wrapper.getClientId(),
wrapper.getLoanId(), wrapper.getSavingsId(),
wrapper.getCodeId(), wrapper.getSupportedEntityType(),
wrapper.getSupportedEntityId(), wrapper.getTransactionId());
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker); result = this.processAndLogCommandService.processAndLogCommand(
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { wrapper, command, isApprovedByChecker);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult()); result = this.processAndLogCommandService.logCommand(e
} .getCommandSourceResult());
}
return result; return result;
} }
@Override @Override
public CommandProcessingResult approveEntry(final Long commandId) { public CommandProcessingResult approveEntry(final Long makerCheckerId) {
final CommandSource commandSourceInput = this.commandSourceRepository.findOne(commandId); CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
if (commandSourceInput == null) {throw new ClientNotFoundException(commandId);}
context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode()); final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(
makerCheckerId, commandSourceInput.getActionName(),
commandSourceInput.getEntityName(),
commandSourceInput.resourceId(),
commandSourceInput.subresourceId(),
commandSourceInput.getResourceGetUrl());
final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(commandId, commandSourceInput.getActionName(), final JsonElement parsedCommand = this.fromApiJsonHelper
commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(), .parse(commandSourceInput.json());
commandSourceInput.getResourceGetUrl()); final JsonCommand command = JsonCommand.fromExistingCommand(
makerCheckerId, commandSourceInput.json(), parsedCommand,
this.fromApiJsonHelper, commandSourceInput.getEntityName(),
commandSourceInput.resourceId(),
commandSourceInput.subresourceId());
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json()); final boolean makerCheckerApproval = true;
final JsonCommand command = JsonCommand.fromExistingCommand(commandId, commandSourceInput.json(), parsedCommand, return this.processAndLogCommandService.processAndLogCommand(wrapper,
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(), command, makerCheckerApproval);
commandSourceInput.subresourceId()); }
final boolean makerCheckerApproval = true; @Transactional
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval); @Override
} public Long deleteEntry(final Long makerCheckerId) {
@Transactional validateMakerCheckerTransaction(makerCheckerId);
@Override
public Long deleteEntry(final Long makerCheckerId) {
context.authenticatedUser(); this.commandSourceRepository.delete(makerCheckerId);
this.commandSourceRepository.delete(makerCheckerId); return makerCheckerId;
}
return makerCheckerId; private CommandSource validateMakerCheckerTransaction(
} final Long makerCheckerId) {
final CommandSource commandSourceInput = this.commandSourceRepository
.findOne(makerCheckerId);
if (commandSourceInput == null) {
throw new CommandNotFoundException(makerCheckerId);
}
if (!(commandSourceInput.isMarkedAsAwaitingApproval())) {
throw new CommandNotAwaitingApprovalException(makerCheckerId);
}
context.authenticatedUser().validateHasCheckerPermissionTo(
commandSourceInput.getPermissionCode());
return commandSourceInput;
}
} }
@@ -441,7 +441,7 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
public void validateHasCheckerPermissionTo(final String function) { public void validateHasCheckerPermissionTo(final String function) {
final String checkerPermissionName = function.toUpperCase() + "_CHECKER"; final String checkerPermissionName = function.toUpperCase() + "_CHECKER";
if (hasNotPermissionTo(checkerPermissionName)) { if (hasNotPermissionTo("CHECKER_SUPER_USER") && hasNotPermissionTo(checkerPermissionName)) {
final String authorizationMessage = "User has no authority to be a checker for: " + function; final String authorizationMessage = "User has no authority to be a checker for: " + function;
throw new NoAuthorizationException(authorizationMessage); throw new NoAuthorizationException(authorizationMessage);
} }