diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index d9bf4f70fa..ba26f8affa 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -399,7 +399,7 @@ def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=F
for column in data.columns:
if column.get("hidden"):
continue
- result[0].append(column.get("label"))
+ result[0].append(_(column.get("label")))
column_width = cint(column.get('width', 0))
# to convert into scale accepted by openpyxl
column_width /= 10
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 4895c97200..be1b0134c1 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -221,7 +221,8 @@ scheduler_events = {
"frappe.deferred_insert.save_to_db",
"frappe.desk.form.document_follow.send_hourly_updates",
"frappe.integrations.doctype.google_calendar.google_calendar.sync",
- "frappe.email.doctype.newsletter.newsletter.send_scheduled_email"
+ "frappe.email.doctype.newsletter.newsletter.send_scheduled_email",
+ "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.process_data_deletion_request"
],
"daily": [
"frappe.email.queue.set_expiry_for_email_queue",
@@ -240,8 +241,7 @@ scheduler_events = {
"frappe.automation.doctype.auto_repeat.auto_repeat.set_auto_repeat_as_completed",
"frappe.email.doctype.unhandled_email.unhandled_email.remove_old_unhandled_emails",
"frappe.core.doctype.prepared_report.prepared_report.delete_expired_prepared_reports",
- "frappe.core.doctype.log_settings.log_settings.run_log_clean_up",
- "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.process_data_deletion_request"
+ "frappe.core.doctype.log_settings.log_settings.run_log_clean_up"
],
"daily_long": [
"frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily",
diff --git a/frappe/model/document.py b/frappe/model/document.py
index dc0fd2caf0..3c38ff3442 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -1003,8 +1003,6 @@ class Document(BaseDocument):
- `on_cancel` for **Cancel**
- `update_after_submit` for **Update after Submit**"""
- doc_before_save = self.get_doc_before_save()
-
if self._action=="save":
self.run_method("on_update")
elif self._action=="submit":
diff --git a/frappe/patches.txt b/frappe/patches.txt
index c889d9a4da..a666480c90 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -197,3 +197,4 @@ frappe.patches.v14_0.copy_mail_data #08.03.21
frappe.patches.v14_0.update_github_endpoints #08-11-2021
frappe.patches.v14_0.remove_db_aggregation
frappe.patches.v14_0.update_color_names_in_kanban_board_column
+frappe.patches.v14_0.update_auto_account_deletion_duration
diff --git a/frappe/patches/v14_0/update_auto_account_deletion_duration.py b/frappe/patches/v14_0/update_auto_account_deletion_duration.py
new file mode 100644
index 0000000000..74957066e6
--- /dev/null
+++ b/frappe/patches/v14_0/update_auto_account_deletion_duration.py
@@ -0,0 +1,5 @@
+import frappe
+
+def execute():
+ days = frappe.db.get_single_value("Website Settings", "auto_account_deletion")
+ frappe.db.set_value("Website Settings", None, "auto_account_deletion", days * 24)
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index 7c12809fcd..4c4e02bf41 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -1343,7 +1343,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
if (file_format === 'CSV') {
const column_row = this.columns.reduce((acc, col) => {
if (!col.hidden) {
- acc.push(col.label);
+ acc.push(__(col.label));
}
return acc;
}, []);
diff --git a/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py b/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py
index 3699cdfbbd..e2f583fd48 100644
--- a/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py
+++ b/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py
@@ -7,7 +7,7 @@ import re
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import get_fullname, date_diff, get_datetime
+from frappe.utils import get_fullname, time_diff_in_hours, get_datetime
from frappe.utils.user import get_system_managers
from frappe.utils.verified_command import get_signed_params, verify_request
import json
@@ -353,8 +353,8 @@ def process_data_deletion_request():
for request in requests:
doc = frappe.get_doc("Personal Data Deletion Request", request)
- if date_diff(get_datetime(), doc.creation) >= auto_account_deletion:
- doc.add_comment("Comment", _("The User record for this request has been auto-deleted due to inactivity."))
+ if time_diff_in_hours(get_datetime(), doc.creation) >= auto_account_deletion:
+ doc.add_comment("Comment", _("The User record for this request has been auto-deleted due to inactivity by system admins."))
doc.trigger_data_deletion()
def remove_unverified_record():
diff --git a/frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py b/frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py
index 27dcfe5858..675a891130 100644
--- a/frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py
+++ b/frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py
@@ -4,10 +4,10 @@
import frappe
import unittest
from frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request import (
- remove_unverified_record,
+ remove_unverified_record, process_data_deletion_request
)
from frappe.website.doctype.personal_data_download_request.test_personal_data_download_request import (
- create_user_if_not_exists,
+ create_user_if_not_exists
)
from datetime import datetime, timedelta
@@ -58,3 +58,15 @@ class TestPersonalDataDeletionRequest(unittest.TestCase):
self.assertFalse(
frappe.db.exists("Personal Data Deletion Request", self.delete_request.name)
)
+
+ def test_process_auto_request(self):
+ frappe.db.set_value("Website Settings", None, "auto_account_deletion", "1")
+ date_time_obj = datetime.strptime(
+ self.delete_request.creation, "%Y-%m-%d %H:%M:%S.%f"
+ ) + timedelta(hours=-2)
+ self.delete_request.db_set("creation", date_time_obj)
+ self.delete_request.db_set("status", "Pending Approval")
+
+ process_data_deletion_request()
+ self.delete_request.reload()
+ self.assertEqual(self.delete_request.status, "Deleted")
diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json
index 2a6b3dc1fb..3b199a4b58 100644
--- a/frappe/website/doctype/website_settings/website_settings.json
+++ b/frappe/website/doctype/website_settings/website_settings.json
@@ -404,10 +404,10 @@
"label": "Show Account Deletion Link in My Account Page"
},
{
- "default": "3",
+ "default": "72",
"fieldname": "auto_account_deletion",
"fieldtype": "Int",
- "label": "Auto Account Deletion within (Days)"
+ "label": "Auto Account Deletion within (Hours)"
},
{
"fieldname": "footer_powered",
@@ -421,7 +421,7 @@
"issingle": 1,
"links": [],
"max_attachments": 10,
- "modified": "2022-02-28 23:05:42.493192",
+ "modified": "2022-02-24 15:37:22.360138",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Settings",
@@ -446,4 +446,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.js b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.js
index 1b9e9ad79b..731fe29cef 100644
--- a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.js
+++ b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.js
@@ -5,7 +5,7 @@ frappe.ready(function() {
callback: (data) => {
if (data.message) {
const intro_wrapper = $('#introduction .ql-editor.read-mode');
- const sla_description = __("Note: Your request for account deletion will be fulfilled within {0} days.", [data.message]);
+ const sla_description = __("Note: Your request for account deletion will be fulfilled within {0} hours.", [data.message]);
const sla_description_wrapper = `
${sla_description}`;
intro_wrapper.html(intro_wrapper.html() + sla_description_wrapper);
}
diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py
index d2d85e696b..14ecdfb5a1 100644
--- a/frappe/workflow/doctype/workflow/test_workflow.py
+++ b/frappe/workflow/doctype/workflow/test_workflow.py
@@ -5,6 +5,7 @@ import unittest
from frappe.utils import random_string
from frappe.model.workflow import apply_workflow, WorkflowTransitionError, WorkflowPermissionError, get_common_transition_actions
from frappe.test_runner import make_test_records
+from frappe.query_builder import DocType
class TestWorkflow(unittest.TestCase):
@@ -15,9 +16,31 @@ class TestWorkflow(unittest.TestCase):
def setUp(self):
self.workflow = create_todo_workflow()
frappe.set_user('Administrator')
+ if self._testMethodName == "test_if_workflow_actions_were_processed_using_user":
+ if not frappe.db.has_column('Workflow Action', 'user'):
+ # mariadb would raise this statement would create an implicit commit
+ # if we do not commit before alter statement
+ # nosemgrep
+ frappe.db.commit()
+ frappe.db.multisql({
+ 'mariadb': 'ALTER TABLE `tabWorkflow Action` ADD COLUMN user varchar(140)',
+ 'postgres': 'ALTER TABLE "tabWorkflow Action" ADD COLUMN "user" varchar(140)'
+ })
+ frappe.cache().delete_value('table_columns')
def tearDown(self):
frappe.delete_doc('Workflow', 'Test ToDo')
+ if self._testMethodName == "test_if_workflow_actions_were_processed_using_user":
+ if frappe.db.has_column('Workflow Action', 'user'):
+ # mariadb would raise this statement would create an implicit commit
+ # if we do not commit before alter statement
+ # nosemgrep
+ frappe.db.commit()
+ frappe.db.multisql({
+ 'mariadb': 'ALTER TABLE `tabWorkflow Action` DROP COLUMN user',
+ 'postgres': 'ALTER TABLE "tabWorkflow Action" DROP COLUMN "user"'
+ })
+ frappe.cache().delete_value('table_columns')
def test_default_condition(self):
'''test default condition is set'''
@@ -75,7 +98,7 @@ class TestWorkflow(unittest.TestCase):
actions = get_common_transition_actions([todo1, todo2], 'ToDo')
self.assertListEqual(actions, ['Review'])
- def test_if_workflow_actions_were_processed(self):
+ def test_if_workflow_actions_were_processed_using_role(self):
frappe.db.delete("Workflow Action")
user = frappe.get_doc('User', 'test2@example.com')
user.add_roles('Test Approver', 'System Manager')
@@ -93,6 +116,32 @@ class TestWorkflow(unittest.TestCase):
self.assertEqual(workflow_actions[0].status, 'Completed')
frappe.set_user('Administrator')
+ def test_if_workflow_actions_were_processed_using_user(self):
+ frappe.db.delete("Workflow Action")
+
+ user = frappe.get_doc('User', 'test2@example.com')
+ user.add_roles('Test Approver', 'System Manager')
+ frappe.set_user('test2@example.com')
+
+ doc = self.test_default_condition()
+ workflow_actions = frappe.get_all('Workflow Action', fields=['*'])
+ self.assertEqual(len(workflow_actions), 1)
+
+ # test if status of workflow actions are updated on approval
+ WorkflowAction = DocType("Workflow Action")
+ WorkflowActionPermittedRole = DocType("Workflow Action Permitted Role")
+ frappe.qb.update(WorkflowAction).set(WorkflowAction.user, 'test2@example.com').run()
+ frappe.qb.update(WorkflowActionPermittedRole).set(WorkflowActionPermittedRole.role, '').run()
+
+ self.test_approve(doc)
+
+ user.remove_roles('Test Approver', 'System Manager')
+ workflow_actions = frappe.get_all('Workflow Action', fields=['status'])
+ self.assertEqual(len(workflow_actions), 1)
+ self.assertEqual(workflow_actions[0].status, 'Completed')
+ frappe.set_user('Administrator')
+
+
def test_update_docstatus(self):
todo = create_new_todo()
apply_workflow(todo, 'Approve')
diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.json b/frappe/workflow/doctype/workflow_action/workflow_action.json
index f1290d001f..aeb60feceb 100644
--- a/frappe/workflow/doctype/workflow_action/workflow_action.json
+++ b/frappe/workflow/doctype/workflow_action/workflow_action.json
@@ -8,9 +8,12 @@
"status",
"reference_name",
"reference_doctype",
- "user",
"workflow_state",
- "completed_by"
+ "column_break_5",
+ "completed_by_role",
+ "completed_by",
+ "permitted_roles",
+ "user"
],
"fields": [
{
@@ -24,16 +27,14 @@
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Name",
- "options": "reference_doctype",
- "search_index": 1
+ "options": "reference_doctype"
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Reference Document Type",
- "options": "DocType",
- "search_index": 1
+ "options": "DocType"
},
{
"fieldname": "user",
@@ -47,18 +48,38 @@
"fieldname": "workflow_state",
"fieldtype": "Data",
"hidden": 1,
- "label": "Workflow State",
- "search_index": 1
+ "label": "Workflow State"
},
{
+ "depends_on": "eval: doc.completed_by",
"fieldname": "completed_by",
"fieldtype": "Link",
- "label": "Completed By",
- "options": "User"
+ "label": "Completed By User",
+ "options": "User",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval: doc.completed_by_role",
+ "fieldname": "completed_by_role",
+ "fieldtype": "Link",
+ "label": "Completed By Role",
+ "options": "Role",
+ "read_only": 1
+ },
+ {
+ "fieldname": "permitted_roles",
+ "fieldtype": "Table MultiSelect",
+ "label": "Permitted Roles",
+ "options": "Workflow Action Permitted Role",
+ "read_only": 1
}
],
"links": [],
- "modified": "2021-07-01 09:07:52.848618",
+ "modified": "2022-02-23 21:06:45.122258",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow Action",
@@ -72,6 +93,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "reference_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py
index c8561fe922..0ab8924a6b 100644
--- a/frappe/workflow/doctype/workflow_action/workflow_action.py
+++ b/frappe/workflow/doctype/workflow_action/workflow_action.py
@@ -13,24 +13,46 @@ from frappe.model.workflow import apply_workflow, get_workflow_name, has_approva
from frappe.desk.notifications import clear_doctype_notifications
from frappe.utils.user import get_users_with_role
from frappe.utils.data import get_link_to_form
+from frappe.query_builder import DocType
class WorkflowAction(Document):
pass
def on_doctype_update():
- frappe.db.add_index("Workflow Action", ["status", "user"])
+ # The search order in any use case is no ["reference_name", "reference_doctype", "status"]
+ # The index scan would happen from left to right
+ # so even if status is not in the where clause the index will be used
+ frappe.db.add_index("Workflow Action", ["reference_name", "reference_doctype", "status"])
def get_permission_query_conditions(user):
if not user: user = frappe.session.user
if user == "Administrator": return ""
- return "(`tabWorkflow Action`.`user`='{user}')".format(user=user)
+ roles = frappe.get_roles(user)
+
+ WorkflowAction = DocType("Workflow Action")
+ WorkflowActionPermittedRole = DocType("Workflow Action Permitted Role")
+
+ permitted_workflow_actions = (frappe.qb.from_(WorkflowAction)
+ .join(WorkflowActionPermittedRole)
+ .on(WorkflowAction.name == WorkflowActionPermittedRole.parent)
+ .select(WorkflowAction.name)
+ .where(WorkflowActionPermittedRole.role.isin(roles))
+ ).get_sql()
+
+ return f"""(`tabWorkflow Action`.`name` in ({permitted_workflow_actions})
+ or `tabWorkflow Action`.`user`='{user}')
+ and `tabWorkflow Action`.`status`='Open'"""
def has_permission(doc, user):
- if user not in ['Administrator', doc.user]:
- return False
+
+ user_roles = set(frappe.get_roles(user))
+
+ permitted_roles = {permitted_role.role for permitted_role in doc.permitted_roles}
+
+ return user == "Administrator" or user_roles.intersection(permitted_roles)
def process_workflow_actions(doc, state):
workflow = get_workflow_name(doc.get('doctype'))
@@ -42,19 +64,18 @@ def process_workflow_actions(doc, state):
if is_workflow_action_already_created(doc): return
- clear_old_workflow_actions(doc)
- update_completed_workflow_actions(doc)
+ update_completed_workflow_actions(doc, workflow=workflow, workflow_state=get_doc_workflow_state(doc))
clear_doctype_notifications('Workflow Action')
next_possible_transitions = get_next_possible_transitions(workflow, get_doc_workflow_state(doc), doc)
if not next_possible_transitions: return
- user_data_map = get_users_next_action_data(next_possible_transitions, doc)
+ user_data_map, roles = get_users_next_action_data(next_possible_transitions, doc)
if not user_data_map: return
- create_workflow_actions_for_users(user_data_map.keys(), doc)
+ create_workflow_actions_for_roles(roles, doc)
if send_email_alert(workflow):
enqueue(send_workflow_action_email, queue='short', users_data=list(user_data_map.values()), doc=doc)
@@ -132,20 +153,85 @@ def return_link_expired_page(doc, doc_workflow_state):
frappe.bold(frappe.get_value('User', doc.get("modified_by"), 'full_name'))
), indicator_color='blue')
-def clear_old_workflow_actions(doc, user=None):
+
+def update_completed_workflow_actions(doc, user=None, workflow=None, workflow_state=None):
+ allowed_roles = get_allowed_roles(user, workflow, workflow_state)
+ # There is no transaction leading upto this state
+ # so no older actions to complete
+ if not allowed_roles:
+ return
+ if workflow_action := get_workflow_action_by_role(doc, allowed_roles):
+ update_completed_workflow_actions_using_role(doc, user, allowed_roles, workflow_action)
+ else:
+ # backwards compatibility
+ # for workflow actions saved using user
+ clear_old_workflow_actions_using_user(doc, user)
+ update_completed_workflow_actions_using_user(doc, user)
+
+def get_allowed_roles(user, workflow, workflow_state):
user = user if user else frappe.session.user
- frappe.db.delete("Workflow Action", {
- "reference_doctype": doc.get("doctype"),
- "reference_name": doc.get("name"),
- "user": ("!=", user),
- "status": "Open"
- })
-def update_completed_workflow_actions(doc, user=None):
+ allowed_roles = frappe.get_all('Workflow Transition',
+ fields='allowed',
+ filters=[
+ ['parent', '=', workflow],
+ ['next_state', '=', workflow_state]
+ ],
+ pluck = 'allowed')
+
+ user_roles = set(frappe.get_roles(user))
+ return set(allowed_roles).intersection(user_roles)
+
+def get_workflow_action_by_role(doc, allowed_roles):
+ WorkflowAction = DocType("Workflow Action")
+ WorkflowActionPermittedRole = DocType("Workflow Action Permitted Role")
+ return (frappe.qb.from_(WorkflowAction).join(WorkflowActionPermittedRole)
+ .on(WorkflowAction.name == WorkflowActionPermittedRole.parent)
+ .select(WorkflowAction.name, WorkflowActionPermittedRole.role)
+ .where((WorkflowAction.reference_name == doc.get('name'))
+ & (WorkflowAction.reference_doctype == doc.get('doctype'))
+ & (WorkflowAction.status == 'Open')
+ & (WorkflowActionPermittedRole.role.isin(list(allowed_roles))))
+ .orderby(WorkflowActionPermittedRole.role).limit(1)).run(as_dict=True)
+
+def update_completed_workflow_actions_using_role(doc, user=None, allowed_roles = set(), workflow_action=None):
user = user if user else frappe.session.user
- frappe.db.sql("""UPDATE `tabWorkflow Action` SET `status`='Completed', `completed_by`=%s
- WHERE `reference_doctype`=%s AND `reference_name`=%s AND `user`=%s AND `status`='Open'""",
- (user, doc.get('doctype'), doc.get('name'), user))
+ WorkflowAction = DocType("Workflow Action")
+
+ if not workflow_action:
+ return
+
+ (frappe.qb.update(WorkflowAction)
+ .set(WorkflowAction.status, 'Completed')
+ .set(WorkflowAction.completed_by, user)
+ .set(WorkflowAction.completed_by_role, workflow_action[0].role)
+ .where(WorkflowAction.name == workflow_action[0].name)
+ ).run()
+
+def clear_old_workflow_actions_using_user(doc, user=None):
+ user = user if user else frappe.session.user
+
+ if frappe.db.has_column('Workflow Action', 'user'):
+ frappe.db.delete("Workflow Action", {
+ "reference_name": doc.get("name"),
+ "reference_doctype": doc.get("doctype"),
+ "status": "Open",
+ "user": ("!=", user)
+ })
+
+def update_completed_workflow_actions_using_user(doc, user=None):
+ user = user or frappe.session.user
+
+ if frappe.db.has_column('Workflow Action', 'user'):
+ WorkflowAction = DocType("Workflow Action")
+ (frappe.qb.update(WorkflowAction)
+ .set(WorkflowAction.status, 'Completed')
+ .set(WorkflowAction.completed_by, user)
+ .where((WorkflowAction.reference_name == doc.get('name'))
+ & (WorkflowAction.reference_doctype == doc.get('doctype'))
+ & (WorkflowAction.status == 'Open')
+ & (WorkflowAction.user == user))
+ ).run()
def get_next_possible_transitions(workflow_name, state, doc=None):
transitions = frappe.get_all('Workflow Transition',
@@ -167,8 +253,10 @@ def get_next_possible_transitions(workflow_name, state, doc=None):
return transitions_to_return
def get_users_next_action_data(transitions, doc):
+ roles = set()
user_data_map = {}
for transition in transitions:
+ roles.add(transition.allowed)
users = get_users_with_role(transition.allowed)
filtered_users = filter_allowed_users(users, doc, transition)
for user in filtered_users:
@@ -182,19 +270,24 @@ def get_users_next_action_data(transitions, doc):
'action_name': transition.action,
'action_link': get_workflow_action_url(transition.action, doc, user)
}))
- return user_data_map
+ return user_data_map, roles
-def create_workflow_actions_for_users(users, doc):
- for user in users:
- frappe.get_doc({
- 'doctype': 'Workflow Action',
- 'reference_doctype': doc.get('doctype'),
- 'reference_name': doc.get('name'),
- 'workflow_state': get_doc_workflow_state(doc),
- 'status': 'Open',
- 'user': user
- }).insert(ignore_permissions=True)
+def create_workflow_actions_for_roles(roles, doc):
+ workflow_action = frappe.get_doc({
+ 'doctype': 'Workflow Action',
+ 'reference_doctype': doc.get('doctype'),
+ 'reference_name': doc.get('name'),
+ 'workflow_state': get_doc_workflow_state(doc),
+ 'status': 'Open',
+ })
+
+ for role in roles:
+ workflow_action.append('permitted_roles', {
+ 'role': role
+ })
+
+ workflow_action.insert(ignore_permissions=True)
def send_workflow_action_email(users_data, doc):
common_args = get_common_email_args(doc)
@@ -249,18 +342,20 @@ def get_confirm_workflow_action_url(doc, action, user):
def is_workflow_action_already_created(doc):
return frappe.db.exists({
'doctype': 'Workflow Action',
- 'reference_doctype': doc.get('doctype'),
'reference_name': doc.get('name'),
- 'workflow_state': get_doc_workflow_state(doc)
+ 'reference_doctype': doc.get('doctype'),
+ 'workflow_state': get_doc_workflow_state(doc),
})
def clear_workflow_actions(doctype, name):
if not (doctype and name):
return
- frappe.db.delete("Workflow Action", {
- "reference_doctype": doctype,
- "reference_name": name
- })
+ frappe.db.delete("Workflow Action", filters = {
+ "reference_name": name,
+ "reference_doctype": doctype,
+ }
+ )
+
def get_doc_workflow_state(doc):
workflow_name = get_workflow_name(doc.get('doctype'))
workflow_state_field = get_workflow_state_field(workflow_name)
diff --git a/frappe/workflow/doctype/workflow_action_permitted_role/__init__.py b/frappe/workflow/doctype/workflow_action_permitted_role/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.json b/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.json
new file mode 100644
index 0000000000..19b2dcba19
--- /dev/null
+++ b/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "hash",
+ "creation": "2022-02-21 20:28:05.662187",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "role"
+ ],
+ "fields": [
+ {
+ "fieldname": "role",
+ "fieldtype": "Link",
+ "label": "Role",
+ "options": "Role"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-02-21 20:28:05.662187",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow Action Permitted Role",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.py b/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.py
new file mode 100644
index 0000000000..0370f6a4c8
--- /dev/null
+++ b/frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2022, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class WorkflowActionPermittedRole(Document):
+ pass