diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index ec56abcf64..6a89ccce5b 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -63,9 +63,6 @@ def apply_workflow(doc, action): transitions = get_transitions(doc, workflow) user = frappe.session.user - if not has_approval_access(user, doc): - frappe.throw(_("Self approval is not allowed")) - # find the transition transition = None for t in transitions: @@ -75,6 +72,9 @@ def apply_workflow(doc, action): if not transition: frappe.throw(_("Not a valid Workflow Action"), WorkflowTransitionError) + if not has_approval_access(user, doc, transition): + frappe.throw(_("Self approval is not allowed")) + # update workflow state field doc.set(workflow.workflow_state_field, transition.next_state) @@ -136,18 +136,14 @@ def validate_workflow(doc): def get_workflow(doctype): return frappe.get_doc('Workflow', get_workflow_name(doctype)) -def has_approval_access(user, doc): - workflow_name = get_workflow_name(doc.get('doctype')) +def has_approval_access(user, doc, transition): return (user == 'Administrator' - or is_self_approval_enabled(workflow_name) + or transition.get('allow_self_approval') or user != doc.owner) def get_workflow_state_field(workflow_name): return get_workflow_field_value(workflow_name, 'workflow_state_field') -def is_self_approval_enabled(workflow_name): - return get_workflow_field_value(workflow_name, 'allow_self_approval') - def send_email_alert(workflow_name): return get_workflow_field_value(workflow_name, 'send_email_alert') diff --git a/frappe/public/js/frappe/form/workflow.js b/frappe/public/js/frappe/form/workflow.js index 9655050f11..044d5b53ac 100644 --- a/frappe/public/js/frappe/form/workflow.js +++ b/frappe/public/js/frappe/form/workflow.js @@ -61,18 +61,7 @@ frappe.ui.form.States = Class.extend({ let doctype = this.frm.doctype; - function has_approval_access() { - let approval_access = false; - const user = frappe.session.user; - if (user === 'Administrator' - || frappe.workflow.is_self_approval_enabled(doctype) - || user !== me.frm.doc.owner) { - approval_access = true; - } - return approval_access; - } - - if(state && has_approval_access()) { + if(state) { // show actions from that state this.show_actions(state); } @@ -89,9 +78,20 @@ frappe.ui.form.States = Class.extend({ return; } + function has_approval_access(transition) { + let approval_access = false; + const user = frappe.session.user; + if (user === 'Administrator' + || transition.allow_self_approval + || user !== me.frm.doc.owner) { + approval_access = true; + } + return approval_access; + } + frappe.workflow.get_transitions(this.frm.doc).then(transitions => { $.each(transitions, function(i, d) { - if(frappe.user_roles.includes(d.allowed)) { + if(frappe.user_roles.includes(d.allowed) && has_approval_access(d)) { added = true; me.frm.page.add_action_item(__(d.action), function() { frappe.xcall('frappe.model.workflow.apply_workflow', diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py index 7df67ac477..cc8f40879c 100644 --- a/frappe/workflow/doctype/workflow/test_workflow.py +++ b/frappe/workflow/doctype/workflow/test_workflow.py @@ -22,7 +22,7 @@ class TestWorkflow(unittest.TestCase): self.workflow.document_type = 'ToDo' self.workflow.workflow_state_field = 'workflow_state' self.workflow.is_active = 1 - self.workflow.allow_self_approval = 1 + self.workflow.send_email_alert = 0 self.workflow.append('states', dict( state = 'Pending', allow_edit = 'All' )) @@ -34,13 +34,13 @@ class TestWorkflow(unittest.TestCase): state = 'Rejected', allow_edit = 'Test Approver' )) self.workflow.append('transitions', dict( - state = 'Pending', action='Approve', next_state = 'Approved', allowed='Test Approver' + state = 'Pending', action='Approve', next_state = 'Approved', allowed='Test Approver', allow_self_approval= 1 )) self.workflow.append('transitions', dict( - state = 'Pending', action='Reject', next_state = 'Rejected', allowed='Test Approver' + state = 'Pending', action='Reject', next_state = 'Rejected', allowed='Test Approver', allow_self_approval= 1 )) self.workflow.append('transitions', dict( - state = 'Rejected', action='Review', next_state = 'Pending', allowed='All' + state = 'Rejected', action='Review', next_state = 'Pending', allowed='All', allow_self_approval= 1 )) self.workflow.insert() frappe.set_user('Administrator') diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json index 11968cae16..28bc186bc8 100644 --- a/frappe/workflow/doctype/workflow/workflow.json +++ b/frappe/workflow/doctype/workflow/workflow.json @@ -110,39 +110,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Allow approval for creator of the document", - "fieldname": "allow_self_approval", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Self Approval", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 8553e79944..0e2c203ff8 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -101,7 +101,7 @@ def update_completed_workflow_actions(doc, user=None): def get_next_possible_transitions(workflow_name, state): return frappe.get_all('Workflow Transition', - fields=['allowed', 'action', 'state'], + fields=['allowed', 'action', 'state', 'allow_self_approval'], filters=[['parent', '=', workflow_name], ['state', '=', state]]) @@ -109,7 +109,7 @@ def get_users_next_action_data(transitions, doc): user_data_map = {} for transition in transitions: users = get_users_with_role(transition.allowed) - filtered_users = filter_allowed_users(users, doc) + filtered_users = filter_allowed_users(users, doc, transition) for user in filtered_users: if not user_data_map.get(user): user_data_map[user] = { @@ -188,14 +188,14 @@ def get_doc_workflow_state(doc): workflow_state_field = get_workflow_state_field(workflow_name) return doc.get(workflow_state_field) -def filter_allowed_users(users, doc): +def filter_allowed_users(users, doc, transition): """Filters list of users by checking if user has access to doc and - if the user satisfies 'workflow self approval' condition + if the user satisfies 'workflow transision self approval' condition """ from frappe.permissions import has_permission filtered_users = [] for user in users: - if (has_approval_access(user, doc) + if (has_approval_access(user, doc, transition) and has_permission(doctype=doc, user=user)): filtered_users.append(user) return filtered_users diff --git a/frappe/workflow/doctype/workflow_transition/workflow_transition.json b/frappe/workflow/doctype/workflow_transition/workflow_transition.json index 8797d2ab9f..25e28ea3d7 100644 --- a/frappe/workflow/doctype/workflow_transition/workflow_transition.json +++ b/frappe/workflow/doctype/workflow_transition/workflow_transition.json @@ -1,293 +1,335 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:36", - "custom": 0, - "description": "Defines actions on states and the next step and allowed roles.", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2013-02-22 01:27:36", + "custom": 0, + "description": "Defines actions on states and the next step and allowed roles.", + "docstatus": 0, + "doctype": "DocType", + "editable_grid": 1, "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "state", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "State", - "length": 0, - "no_copy": 0, - "options": "Workflow State", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "state", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "State", + "length": 0, + "no_copy": 0, + "options": "Workflow State", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "200px", + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "action", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Action", - "length": 0, - "no_copy": 0, - "options": "Workflow Action Master", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "action", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Action", + "length": 0, + "no_copy": 0, + "options": "Workflow Action Master", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "200px", + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "next_state", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Next State", - "length": 0, - "no_copy": 0, - "options": "Workflow State", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "next_state", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Next State", + "length": 0, + "no_copy": 0, + "options": "Workflow State", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "200px", + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allowed", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Allowed", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allowed", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Allowed", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "200px", + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "conditions", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Conditions", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "Allow approval for creator of the document", + "fieldname": "allow_self_approval", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Self Approval", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "condition", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Condition", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "conditions", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Conditions", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Condition", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "example", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Example", - "length": 0, - "no_copy": 0, - "options": "
doc.grand_total > 0\n\nConditions should be written in simple Python. Please use properties available in the form only.
", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "example", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Example", + "length": 0, + "no_copy": 0, + "options": "doc.grand_total > 0\n\nConditions should be written in simple Python. Please use properties available in the form only.
", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-18 10:30:54.706642", - "modified_by": "Administrator", - "module": "Workflow", - "name": "Workflow Transition", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-06-19 10:28:53.294908", + "modified_by": "Administrator", + "module": "Workflow", + "name": "Workflow Transition", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "track_changes": 0, "track_seen": 0 } \ No newline at end of file