Browse Source

Merge branch 'develop' into camera_upload

version-14
Himanshu 3 years ago
committed by GitHub
parent
commit
adb22a9beb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 297 additions and 72 deletions
  1. +9
    -7
      frappe/commands/site.py
  2. +1
    -1
      frappe/desk/query_report.py
  3. +3
    -3
      frappe/hooks.py
  4. +0
    -2
      frappe/model/document.py
  5. +1
    -0
      frappe/patches.txt
  6. +5
    -0
      frappe/patches/v14_0/update_auto_account_deletion_duration.py
  7. +1
    -1
      frappe/public/js/frappe/views/reports/query_report.js
  8. +3
    -3
      frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py
  9. +14
    -2
      frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py
  10. +4
    -4
      frappe/website/doctype/website_settings/website_settings.json
  11. +1
    -1
      frappe/website/web_form/request_to_delete_data/request_to_delete_data.js
  12. +50
    -1
      frappe/workflow/doctype/workflow/test_workflow.py
  13. +33
    -11
      frappe/workflow/doctype/workflow_action/workflow_action.json
  14. +131
    -36
      frappe/workflow/doctype/workflow_action/workflow_action.py
  15. +0
    -0
      frappe/workflow/doctype/workflow_action_permitted_role/__init__.py
  16. +33
    -0
      frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.json
  17. +8
    -0
      frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.py

+ 9
- 7
frappe/commands/site.py View File

@@ -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')



+ 1
- 1
frappe/desk/query_report.py View File

@@ -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


+ 3
- 3
frappe/hooks.py View File

@@ -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",


+ 0
- 2
frappe/model/document.py View File

@@ -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":


+ 1
- 0
frappe/patches.txt View File

@@ -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

+ 5
- 0
frappe/patches/v14_0/update_auto_account_deletion_duration.py View File

@@ -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)

+ 1
- 1
frappe/public/js/frappe/views/reports/query_report.js View File

@@ -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;
}, []);


+ 3
- 3
frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py View File

@@ -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():


+ 14
- 2
frappe/website/doctype/personal_data_deletion_request/test_personal_data_deletion_request.py View File

@@ -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")

+ 4
- 4
frappe/website/doctype/website_settings/website_settings.json View File

@@ -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
}
}

+ 1
- 1
frappe/website/web_form/request_to_delete_data/request_to_delete_data.js View File

@@ -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);
}


+ 50
- 1
frappe/workflow/doctype/workflow/test_workflow.py View File

@@ -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')


+ 33
- 11
frappe/workflow/doctype/workflow_action/workflow_action.json View File

@@ -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
}

+ 131
- 36
frappe/workflow/doctype/workflow_action/workflow_action.py View File

@@ -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
frappe/workflow/doctype/workflow_action_permitted_role/__init__.py View File


+ 33
- 0
frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.json View File

@@ -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": []
}

+ 8
- 0
frappe/workflow/doctype/workflow_action_permitted_role/workflow_action_permitted_role.py View File

@@ -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

Loading…
Cancel
Save