@@ -1,7 +1,7 @@ | |||
# imports - standard imports | |||
import os | |||
import sys | |||
import shutil | |||
import sys | |||
# imports - third party imports | |||
import click | |||
@@ -65,11 +65,11 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, | |||
"Restore site database from an sql file" | |||
from frappe.installer import ( | |||
_new_site, | |||
extract_sql_from_archive, | |||
extract_files, | |||
extract_sql_from_archive, | |||
is_downgrade, | |||
is_partial, | |||
validate_database_sql | |||
validate_database_sql, | |||
) | |||
from frappe.utils.backups import Backup | |||
if not os.path.exists(sql_file_path): | |||
@@ -207,7 +207,7 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, | |||
@click.option('--encryption-key', help='Backup encryption key') | |||
@pass_context | |||
def partial_restore(context, sql_file_path, verbose, encryption_key=None): | |||
from frappe.installer import partial_restore, extract_sql_from_archive | |||
from frappe.installer import extract_sql_from_archive, partial_restore | |||
from frappe.utils.backups import Backup | |||
if not os.path.exists(sql_file_path): | |||
@@ -545,7 +545,7 @@ def _use(site, sites_path='.'): | |||
def use(site, sites_path='.'): | |||
if os.path.exists(os.path.join(sites_path, site)): | |||
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: | |||
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: | |||
sitefile.write(site) | |||
print("Current Site set to {}".format(site)) | |||
else: | |||
@@ -751,6 +751,7 @@ def set_admin_password(context, admin_password=None, logout_all_sessions=False): | |||
def set_user_password(site, user, password, logout_all_sessions=False): | |||
import getpass | |||
from frappe.utils.password import update_password | |||
try: | |||
@@ -881,15 +882,16 @@ def stop_recording(context): | |||
raise SiteNotSpecifiedError | |||
@click.command('ngrok') | |||
@click.option('--bind-tls', is_flag=True, default=False, help='Returns a reference to the https tunnel.') | |||
@pass_context | |||
def start_ngrok(context): | |||
def start_ngrok(context, bind_tls): | |||
from pyngrok import ngrok | |||
site = get_site(context) | |||
frappe.init(site=site) | |||
port = frappe.conf.http_port or frappe.conf.webserver_port | |||
tunnel = ngrok.connect(addr=str(port), host_header=site) | |||
tunnel = ngrok.connect(addr=str(port), host_header=site, bind_tls=bind_tls) | |||
print(f'Public URL: {tunnel.public_url}') | |||
print('Inspect logs at http://localhost:4040') | |||
@@ -406,7 +406,7 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visi | |||
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 | |||
@@ -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", | |||
@@ -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": | |||
@@ -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 |
@@ -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) |
@@ -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; | |||
}, []); | |||
@@ -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(): | |||
@@ -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") |
@@ -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 | |||
} | |||
} |
@@ -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 = `<br><b>${sla_description}</b>`; | |||
intro_wrapper.html(intro_wrapper.html() + sla_description_wrapper); | |||
} | |||
@@ -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') | |||
@@ -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 | |||
} |
@@ -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) | |||
@@ -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": [] | |||
} |
@@ -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 |