diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index 833aa2445..03deae638 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -937,9 +937,8 @@
Permission |
permissions |
|
- List Application
- Permissions |
- |
+ List Application Permissions |
+ Enable/Disable Permissions for Maker Checker |
|
@@ -7055,8 +7054,17 @@ Content-Type: application/json
Audits
-
-
+
Every non-read Mifos API request is audited. A fully processed request can
+ not be changed or deleted. See maker checker api for situations where an audit is not
+ fully processed.
+
+ 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.
+
+ 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.
+
@@ -7065,9 +7073,106 @@ Content-Type: application/json
List Audits
-
+
Get a list of audits that match the criteria supplied,
+ and are within the requestors' data scope.
+
Arguments
+
+ - actionName
+ - optional
+ - Examples: CREATE, UPDATE, DISBURSE
+
+ - entityName
+ - optional
+ - Examples: CLIENT, LOAN, FUND
+
+ - resourceId
+ - optional,
+ The id value of the entityName
+
+
+ - makerId
+ - optional,
+ The id value of the application user creating the entry
+
+
+ - makerDateTimeFrom
+ - optional,
+ Get entries created on or after this
+
+ - Example: 2013-04-10 08:00:00
+
+ - makerDateTimeTo
+ - optional,
+ Get entries created on or before this
+
+ - Example: 2013-05-10 08:00:00
+
+ - checkerId
+ - optional,
+ The id value of the application user that checked (approved) the entry
+
+
+ - checkerDateTimeFrom
+ - optional,
+ Get entries checked on or after this
+
+ - Example: 2013-04-10 08:00:00
+
+ - checkerDateTimeTo
+ - optional,
+ Get entries checked on or before this
+
+ - Example: 2013-05-10 18:00:00
+
+ - processingResult
+ - optional,
+ The enum value of the processing status
+
+ - values:
+ 0: Invalid
+ 1: processed
+ 2: awaiting.approval
+ 3: rejected
+
+
+ - officeId
+ - optional,
+ The id value of the office/branch associated with the entry (if there is one)
+
+
+ - groupId
+ - optional,
+ 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.
+
+
+ - clientId
+ - optional,
+ The id value of the client associated with the entry (if there is one)
+
+
+ - loanId
+ - optional,
+ The id value of the loan associated with the entry (if there is one)
+
+
+ - savingsAccountId
+ - optional,
+ The id value of the savings account associated with the entry (if there is one)
+
+
+ - includeJson
+ - optional,
+ Values are true, false. Default is false.
+
+
+
Example Requests:
-
audits
+
audits
+
audits?fields=madeOnDate,maker,processingResult
+
audits?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00
+
audits?officeId=1
+
audits?officeId=1&includeJson=true
@@ -7156,7 +7261,6 @@ GET https://DomainName/api/v1/audits
-
@@ -7262,8 +7366,212 @@ GET https://DomainName/api/v1/audits/searchtemplate
+
+
+
+
Retrieve an Audit Entry
+
+
Example Requests:
+
audits/20
+
audits/20?fields=madeOnDate,maker,processingResult
+
+
+
+GET https://DomainName/api/v1/audits
+
+
+{
+ "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"
+}
+
+
+
+
+
+
+
+
Maker Checker (or 4-eye) functionality
+
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.
+
+
+
By default, Maker Checker functionality is disabled.
+ See Update Global Configuration to enable/disable
+ Maker Checker functionality at a global level.
+
+
+
+ Additionally, Maker Checker functionality for each transaction (permission) is disabled by default.
+ see Enable/Disable Permissions for Maker Checker to enable/disable
+ Maker Checker functionality at a transaction (permission) level.
+
+
+
+ Finally, to give checking rights to a user (via a role associated with the user) see
+ Update a Role's Permissions
+
+
For example,
+
+{
+ "permissions":{
+ "CREATE_GUARANTOR_CHECKER":true,
+ "CREATE_CLIENT_CHECKER":true
+ }
+}
+
+
+ will give checking rights for CREATE_GUARANTOR and CREATE_CLIENT
+
Alternatively, the special permissions ALL_FUNCTIONS or CHECKER_SUPER_USER will give blanket
+ checking rights.
+
+
+ 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).
+
+
+ Checkers can only Check and approve entries that they have been permitted to check.
+
+ 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.
+
+
+
+
+
+
+
+
List Maker Checker Entries
+
Get a list of entries that can be checked by the requestor that match the criteria supplied.
+
Arguments
+
+ - actionName
+ - optional
+ - Examples: CREATE, UPDATE, DISBURSE
+
+ - entityName
+ - optional
+ - Examples: CLIENT, LOAN, FUND
+
+ - resourceId
+ - optional,
+ The id value of the entityName
+
+
+ - makerId
+ - optional,
+ The id value of the application user creating the entry
+
+
+ - makerDateTimeFrom
+ - optional,
+ Get entries created on or after this
+
+ - Example: 2013-04-10 08:00:00
+
+ - makerDateTimeTo
+ - optional,
+ Get entries created on or before this
+
+ - Example: 2013-05-10 08:00:00
+
+ - officeId
+ - optional,
+ The id value of the office/branch associated with the entry (if there is one)
+
+
+ - groupId
+ - optional,
+ 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.
+
+
+ - clientId
+ - optional,
+ The id value of the client associated with the entry (if there is one)
+
+
+ - loanId
+ - optional,
+ The id value of the loan associated with the entry (if there is one)
+
+
+ - savingsAccountId
+ - optional,
+ The id value of the savings account associated with the entry (if there is one)
+
+
+ - includeJson
+ - optional,
+ Values are true, false. Default is false.
+
+
+
+
Example Requests:
+
makercheckers
+
makercheckers?fields=madeOnDate,maker,processingResult
+
makercheckers?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00
+
makercheckers?officeId=1
+
makercheckers?officeId=1&includeJson=true
+
+
+
+GET https://DomainName/api/v1/makercheckers
+
+
+[
+ {
+ "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"
+ }
+]
+
+
+
+
@@ -7320,6 +7628,42 @@ GET https://DomainName/api/v1/audits/searchtemplate
+
+
+
+
Approve Maker Checker Entry
+
+
+
+POST https://DomainName/api/v1/makercheckers/{auditId}?command=approve
+
+
+POST makercheckers/1?command=approve
+Content-Type: application/json
+
+ { "auditId": 1 }
+
+
+
+
+
+
+
Delete Maker Checker Entry
+
+
+
+DELETE https://DomainName/api/v1/makercheckers/{auditId}
+
+
+DELETE makercheckers/1
+Content-Type: application/json
+
+ { "auditId": 1 }
+
+
+
+
+
@@ -11324,6 +11668,9 @@ Request body:
Permissions
An API capability to support management of application permissions for user administration.
+
There is no Mifos X functionality for creating or deleting permissions. Permissions come pre-installed.
+
Permissions are not updated, except in the case of enabling or disabling non-read transactions for
+ Maker Checker functionality
@@ -11332,10 +11679,25 @@ Request body:
List Application Permissions
+
Arguments
+
+ - makerCheckerable
+ - optional,
+ Values are true, false. Default is false.
+
If makerCheckerable=false or not supplied then a list of application permissions is returned. The "selected"
+ attribute is always true in this case.
+
+ If makerCheckerable=true then the "selected" attribute shows whether the permission is
+ enabled for Maker Check functionality.
+
+
+ Note: Each Mifos X transaction is associated with a permission.
+
+
+
Example Requests:
-
permissions
-
-
+
permissions
+
permissions?makerCheckerable=true
permissions?fields=grouping,code
@@ -11364,6 +11726,30 @@ GET https://DomainName/api/v1/permissions
+
+
+
+
Enable/Disable Permissions for Maker Checker
+
+
+
+PUT https://DomainName/api/v1/permissions
+
+
+PUT permissions
+Content-Type: application/json
+Request Body:
+{
+ "permissions":{
+ "CREATE_GUARANTOR":true,
+ "CREATE_CLIENT":true
+ }
+}
+
+
+
+
+
@@ -12985,46 +13371,46 @@ GET https://DomainName/api/v1/search?query=000000111
Update a Code Value |
Delete a Code Value |
-
- | Audits |
- audits |
- |
- List Audits |
- |
- |
-
-
- |
- audits/{auditId} |
- |
- Retrieve an Audit Entry |
- |
- |
-
-
- | Makercheckers |
- makercheckers |
- |
- List Maker Checker Entries |
- |
- |
-
-
- |
- makercheckers/{auditId} |
- |
- |
- |
- Delete Maker Checker Entry |
-
-
- |
- makercheckers/{auditId}?command=approve |
- Approve Maker Checker Entry |
- |
- |
- |
-
+
+ | Audits |
+ audits |
+ |
+ List Audits |
+ |
+ |
+
+
+ |
+ audits/{auditId} |
+ |
+ Retrieve an Audit Entry |
+ |
+ |
+
+
+ | Makercheckers |
+ makercheckers |
+ |
+ List Maker Checker Entries |
+ |
+ |
+
+
+ |
+ makercheckers/{auditId} |
+ |
+ |
+ |
+ Delete Maker Checker Entry |
+
+
+ |
+ makercheckers/{auditId}?command=approve |
+ Approve Maker Checker Entry |
+ |
+ |
+ |
+
| Funds |
funds |
@@ -13297,8 +13683,8 @@ GET https://DomainName/api/v1/search?query=000000111
Permission |
permissions |
|
- List Permissions |
- |
+ List Application Permissions |
+ Enable/Disable Permissions for Maker Checker |
|
diff --git a/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql b/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql
new file mode 100644
index 000000000..62f6e4b97
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql
@@ -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';
+
+
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java
index ea56aa620..8c47d705c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java
@@ -34,7 +34,6 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
-import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -50,9 +49,10 @@ public class MakercheckersApiResource {
"checkedOnDate", "processingResult", "commandAsJson",
"officeName", "groupLevelName", "groupName", "clientName",
"loanAccountNo", "savingsAccountNo"));
- private final String resourceNameForPermissions = "MAKERCHECKER";
+
+ //Next line removed because read permissions checks part of read services
+ //private final String resourceNameForPermissions = "MAKERCHECKER";
- private final PlatformSecurityContext context;
private final AuditReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer
toApiJsonSerializer;
private final DefaultToApiJsonSerializer toApiJsonSerializerAudit;
@@ -62,14 +62,12 @@ public class MakercheckersApiResource {
@Autowired
public MakercheckersApiResource(
- final PlatformSecurityContext context,
final AuditReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer toApiJsonSerializer,
final DefaultToApiJsonSerializer toApiJsonSerializerAudit,
final DefaultToApiJsonSerializer toApiJsonSerializerSearchTemplate,
final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService writePlatformService) {
- this.context = context;
this.readPlatformService = readPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
@@ -94,9 +92,6 @@ public class MakercheckersApiResource {
@QueryParam("loanid") final Integer loanId,
@QueryParam("savingsAccountId") final Integer savingsAccountId) {
- context.authenticatedUser().validateHasReadPermission(
- resourceNameForPermissions);
-
final String extraCriteria = getExtraCriteria(actionName, entityName,
resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
officeId, groupId, clientId, loanId, savingsAccountId);
@@ -118,9 +113,6 @@ public class MakercheckersApiResource {
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
- context.authenticatedUser().validateHasReadPermission(
- resourceNameForPermissions);
-
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper
.process(uriInfo.getQueryParameters());
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
index 78e5c12fd..093db0a7a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java
@@ -25,159 +25,175 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_command_source")
public class CommandSource extends AbstractPersistable {
- @Column(name = "action_name", nullable = true, length = 100)
- private String actionName;
-
- @Column(name = "entity_name", nullable = true, length = 100)
- private String entityName;
-
- @SuppressWarnings("unused")
- @Column(name = "office_id")
- private Long officeId;
+ @Column(name = "action_name", nullable = true, length = 100)
+ private String actionName;
- @SuppressWarnings("unused")
- @Column(name = "group_id")
- private Long groupId;
-
- @SuppressWarnings("unused")
- @Column(name = "client_id")
- private Long clientId;
+ @Column(name = "entity_name", nullable = true, length = 100)
+ private String entityName;
- @SuppressWarnings("unused")
- @Column(name = "loan_id")
- private Long loanId;
-
- @SuppressWarnings("unused")
- @Column(name = "savings_account_id")
- private Long savingsId;
-
- @Column(name = "api_get_url", length = 100)
- private String resourceGetUrl;
+ @SuppressWarnings("unused")
+ @Column(name = "office_id")
+ private Long officeId;
- @Column(name = "resource_id")
- private Long resourceId;
-
- @Column(name = "subresource_id")
- private Long subresourceId;
+ @SuppressWarnings("unused")
+ @Column(name = "group_id")
+ private Long groupId;
- @Column(name = "command_as_json", length = 1000)
- private String commandAsJson;
+ @SuppressWarnings("unused")
+ @Column(name = "client_id")
+ private Long clientId;
- @SuppressWarnings("unused")
- @ManyToOne
- @JoinColumn(name = "maker_id", nullable = false)
- private AppUser maker;
+ @SuppressWarnings("unused")
+ @Column(name = "loan_id")
+ private Long loanId;
- @SuppressWarnings("unused")
- @Column(name = "made_on_date", nullable = false)
- @Temporal(TemporalType.TIMESTAMP)
- private Date madeOnDate;
+ @SuppressWarnings("unused")
+ @Column(name = "savings_account_id")
+ private Long savingsId;
- @SuppressWarnings("unused")
- @ManyToOne
- @JoinColumn(name = "checker_id", nullable = true)
- private AppUser checker;
+ @Column(name = "api_get_url", length = 100)
+ private String resourceGetUrl;
- @SuppressWarnings("unused")
- @Column(name = "checked_on_date", nullable = true)
- @Temporal(TemporalType.TIMESTAMP)
- private Date checkedOnDate;
+ @Column(name = "resource_id")
+ private Long resourceId;
- @SuppressWarnings("unused")
- @Column(name = "processing_result_enum", nullable = false)
- private Integer processingResult;
+ @Column(name = "subresource_id")
+ private Long subresourceId;
- public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) {
- return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), command.json(), maker, DateTime.now());
- }
+ @Column(name = "command_as_json", length = 1000)
+ private String commandAsJson;
- protected CommandSource() {
- //
- }
+ @SuppressWarnings("unused")
+ @ManyToOne
+ @JoinColumn(name = "maker_id", nullable = false)
+ private AppUser maker;
- private CommandSource(
- final String actionName, final String entityName, final String href,
- final Long resourceId, final Long subresourceId,
- final String commandSerializedAsJson, final AppUser maker,
- final DateTime madeOnDateTime) {
- this.actionName = actionName;
- this.entityName = entityName;
- this.resourceGetUrl = href;
- this.resourceId = resourceId;
- this.subresourceId = subresourceId;
- this.commandAsJson = commandSerializedAsJson;
- this.maker = maker;
- this.madeOnDate = madeOnDateTime.toDate();
- this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
- }
+ @SuppressWarnings("unused")
+ @Column(name = "made_on_date", nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date madeOnDate;
- public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) {
- this.checker = checker;
- this.checkedOnDate = checkedOnDate.toDate();
- this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
- }
+ @SuppressWarnings("unused")
+ @ManyToOne
+ @JoinColumn(name = "checker_id", nullable = true)
+ private AppUser checker;
- public void updateResourceId(final Long resourceId) {
- this.resourceId = resourceId;
- }
-
- public void updateSubresourceId(final Long subresourceId) {
- this.subresourceId = subresourceId;
- }
+ @SuppressWarnings("unused")
+ @Column(name = "checked_on_date", nullable = true)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date checkedOnDate;
- public void updateJsonTo(final String json) {
- this.commandAsJson = json;
- }
+ @SuppressWarnings("unused")
+ @Column(name = "processing_result_enum", nullable = false)
+ private Integer processingResult;
- public Long resourceId() {
- return this.resourceId;
- }
-
- public Long subresourceId() {
- return this.subresourceId;
- }
+ public static CommandSource fullEntryFrom(final CommandWrapper wrapper,
+ final JsonCommand command, final AppUser maker) {
+ return new CommandSource(wrapper.actionName(), wrapper.entityName(),
+ wrapper.getHref(), command.entityId(), command.subentityId(),
+ command.json(), maker, DateTime.now());
+ }
- public boolean hasJson() {
- return StringUtils.isNotBlank(this.commandAsJson);
- }
+ protected CommandSource() {
+ //
+ }
- public String json() {
- return this.commandAsJson;
- }
+ private CommandSource(final String actionName, final String entityName,
+ final String href, final Long resourceId, final Long subresourceId,
+ final String commandSerializedAsJson, final AppUser maker,
+ final DateTime madeOnDateTime) {
+ this.actionName = actionName;
+ this.entityName = entityName;
+ this.resourceGetUrl = href;
+ this.resourceId = resourceId;
+ this.subresourceId = subresourceId;
+ this.commandAsJson = commandSerializedAsJson;
+ this.maker = maker;
+ this.madeOnDate = madeOnDateTime.toDate();
+ this.processingResult = CommandProcessingResultType.PROCESSED
+ .getValue();
+ }
- public String getActionName() {
- return this.actionName;
- }
-
- public String getEntityName() {
- return this.entityName;
- }
+ public void markAsChecked(final AppUser checker,
+ final DateTime checkedOnDate) {
+ this.checker = checker;
+ this.checkedOnDate = checkedOnDate.toDate();
+ this.processingResult = CommandProcessingResultType.PROCESSED
+ .getValue();
+ }
- public String getPermissionCode() {
- return this.actionName + "_" + this.entityName;
- }
+ public void updateResourceId(final Long resourceId) {
+ this.resourceId = resourceId;
+ }
- public Long getResourceId() {
- return this.resourceId;
- }
-
- public Long getSubresourceId() {
- return this.subresourceId;
- }
+ public void updateSubresourceId(final Long subresourceId) {
+ this.subresourceId = subresourceId;
+ }
- public void markAsAwaitingApproval() {
- this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
- }
+ public void updateJsonTo(final String json) {
+ this.commandAsJson = json;
+ }
- 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 Long resourceId() {
+ return this.resourceId;
+ }
- public String getResourceGetUrl() {
- return this.resourceGetUrl;
- }
+ public Long subresourceId() {
+ return this.subresourceId;
+ }
+
+ public boolean hasJson() {
+ return StringUtils.isNotBlank(this.commandAsJson);
+ }
+
+ public String json() {
+ return this.commandAsJson;
+ }
+
+ public String getActionName() {
+ return this.actionName;
+ }
+
+ public String getEntityName() {
+ return this.entityName;
+ }
+
+ public String getPermissionCode() {
+ return this.actionName + "_" + this.entityName;
+ }
+
+ public Long getResourceId() {
+ return this.resourceId;
+ }
+
+ public Long getSubresourceId() {
+ return this.subresourceId;
+ }
+
+ public void markAsAwaitingApproval() {
+ this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL
+ .getValue();
+ }
+
+ public boolean isMarkedAsAwaitingApproval() {
+ if (this.processingResult
+ .equals(CommandProcessingResultType.AWAITING_APPROVAL
+ .getValue()))
+ return true;
+
+ return false;
+ }
+
+ 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;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java
new file mode 100644
index 000000000..966156f55
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java
index 358fb0e72..014f0340b 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java
@@ -13,6 +13,6 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN
public class CommandNotFoundException extends AbstractPlatformResourceNotFoundException {
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);
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
index da9532b9b..83e723d4a 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
@@ -8,12 +8,13 @@ package org.mifosplatform.commands.service;
import org.mifosplatform.commands.domain.CommandSource;
import org.mifosplatform.commands.domain.CommandSourceRepository;
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.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
-import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -21,87 +22,120 @@ import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonElement;
@Service
-public class PortfolioCommandSourceWritePlatformServiceImpl implements PortfolioCommandSourceWritePlatformService {
+public class PortfolioCommandSourceWritePlatformServiceImpl implements
+ PortfolioCommandSourceWritePlatformService {
- private final PlatformSecurityContext context;
- private final CommandSourceRepository commandSourceRepository;
- private final FromJsonHelper fromApiJsonHelper;
- private final CommandProcessingService processAndLogCommandService;
+ private final PlatformSecurityContext context;
+ private final CommandSourceRepository commandSourceRepository;
+ private final FromJsonHelper fromApiJsonHelper;
+ private final CommandProcessingService processAndLogCommandService;
- @Autowired
- public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityContext context,
- final CommandSourceRepository commandSourceRepository, final FromJsonHelper fromApiJsonHelper,
- final CommandProcessingService processAndLogCommandService) {
- this.context = context;
- this.commandSourceRepository = commandSourceRepository;
- this.fromApiJsonHelper = fromApiJsonHelper;
- this.processAndLogCommandService = processAndLogCommandService;
- }
+ @Autowired
+ public PortfolioCommandSourceWritePlatformServiceImpl(
+ final PlatformSecurityContext context,
+ final CommandSourceRepository commandSourceRepository,
+ final FromJsonHelper fromApiJsonHelper,
+ final CommandProcessingService processAndLogCommandService) {
+ this.context = context;
+ this.commandSourceRepository = commandSourceRepository;
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ this.processAndLogCommandService = processAndLogCommandService;
+ }
- @Override
- public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
+ @Override
+ public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
- boolean isApprovedByChecker = false;
- // check if is update of own account details
- if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser().getId())) {
- // then allow this operation to proceed.
- // maker checker doesnt mean anything here.
- isApprovedByChecker = true; // set to true in case permissions have
- // been maker-checker enabled by
- // accident.
- } else {
- // if not user changing their own details - check user has
- // permission to perform specific task.
- context.authenticatedUser().validateHasPermissionTo(wrapper.getTaskPermissionName());
- }
+ boolean isApprovedByChecker = false;
+ // check if is update of own account details
+ if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser()
+ .getId())) {
+ // then allow this operation to proceed.
+ // maker checker doesnt mean anything here.
+ isApprovedByChecker = true; // set to true in case permissions have
+ // been maker-checker enabled by
+ // accident.
+ } else {
+ // if not user changing their own details - check user has
+ // permission to perform specific task.
+ context.authenticatedUser().validateHasPermissionTo(
+ wrapper.getTaskPermissionName());
+ }
- final String json = wrapper.getJson();
- CommandProcessingResult result = null;
- try {
- final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
- 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());
+ final String json = wrapper.getJson();
+ CommandProcessingResult result = null;
+ try {
+ final JsonElement parsedCommand = this.fromApiJsonHelper
+ .parse(json);
+ 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());
- result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
- } catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
+ result = this.processAndLogCommandService.processAndLogCommand(
+ wrapper, command, isApprovedByChecker);
+ } catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
- result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
- }
+ result = this.processAndLogCommandService.logCommand(e
+ .getCommandSourceResult());
+ }
- return result;
- }
+ return result;
+ }
- @Override
- public CommandProcessingResult approveEntry(final Long commandId) {
+ @Override
+ public CommandProcessingResult approveEntry(final Long makerCheckerId) {
- final CommandSource commandSourceInput = this.commandSourceRepository.findOne(commandId);
- if (commandSourceInput == null) {throw new ClientNotFoundException(commandId);}
-
- context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode());
+ CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
- final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(commandId, commandSourceInput.getActionName(),
- commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(),
- commandSourceInput.getResourceGetUrl());
+ final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(
+ makerCheckerId, commandSourceInput.getActionName(),
+ commandSourceInput.getEntityName(),
+ commandSourceInput.resourceId(),
+ commandSourceInput.subresourceId(),
+ commandSourceInput.getResourceGetUrl());
- final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
- final JsonCommand command = JsonCommand.fromExistingCommand(commandId, commandSourceInput.json(), parsedCommand,
- this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
- commandSourceInput.subresourceId());
+ 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());
- final boolean makerCheckerApproval = true;
- return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
- }
+ final boolean makerCheckerApproval = true;
+ return this.processAndLogCommandService.processAndLogCommand(wrapper,
+ command, makerCheckerApproval);
+ }
- @Transactional
- @Override
- public Long deleteEntry(final Long makerCheckerId) {
+ @Transactional
+ @Override
+ public Long deleteEntry(final Long makerCheckerId) {
- context.authenticatedUser();
+ validateMakerCheckerTransaction(makerCheckerId);
- 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;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java
index d45c16743..5644803b5 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java
@@ -441,7 +441,7 @@ public class AppUser extends AbstractPersistable implements PlatformUser {
public void validateHasCheckerPermissionTo(final String function) {
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;
throw new NoAuthorizationException(authorizationMessage);
}