From a250e99377dffb0b102238c205e2a8e5dfcb37ea Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 13 Sep 2019 17:13:05 +0530 Subject: [PATCH] refactor: assignment rule and todo (#8420) * refactor: assignment rule and todo * fix: updated query and enabled background jobs * refactor: use ORM instead of SQL * style: remove print statement * refactor: minor change in tests * style: updated query for getting todo --- cypress/integration/list_view.js | 4 +- .../assignment_rule/assignment_rule.json | 442 ++------------ .../assignment_rule/assignment_rule.py | 72 ++- .../assignment_rule/test_assignment_rule.py | 35 +- frappe/desk/doctype/todo/todo.json | 576 ++---------------- frappe/desk/form/assign_to.py | 32 +- frappe/desk/form/load.py | 14 +- .../public/js/frappe/list/bulk_operations.js | 9 + frappe/public/js/frappe/list/list_view.js | 10 + 9 files changed, 227 insertions(+), 967 deletions(-) diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 52c6483ab4..1cf7f4e6d8 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -8,11 +8,11 @@ context('List View', () => { cy.clear_cache(); }); it('enables "Actions" button', () => { - const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Print','Delete']; + const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Print', 'Delete']; cy.go_to_list('ToDo'); cy.get('.level-item.list-row-checkbox.hidden-xs').click({ multiple: true, force: true }); cy.get('.btn.btn-primary.btn-sm.dropdown-toggle').contains('Actions').should('be.visible').click(); - cy.get('.dropdown-menu li:visible').should('have.length', 6).each((el, index) => { + cy.get('.dropdown-menu li:visible').should('have.length', 7).each((el, index) => { cy.wrap(el).contains(actions[index]); }).then((elements) => { cy.server(); diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.json b/frappe/automation/doctype/assignment_rule/assignment_rule.json index 984ca9928a..799efefd04 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.json +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.json @@ -1,498 +1,142 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, "allow_rename": 1, "autoname": "Prompt", - "beta": 0, "creation": "2019-02-28 17:12:18.815830", - "custom": 0, "description": "Automatically Assign Documents to Users", - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "document_type", + "priority", + "disabled", + "column_break_4", + "description", + "assignment_rules_section", + "assign_condition", + "column_break_6", + "unassign_condition", + "section_break_10", + "close_condition", + "assign_to_users_section", + "rule", + "users", + "last_user" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "document_type", "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": "Document Type", - "length": 0, - "no_copy": 0, "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Higher priority rule will be applied first", - "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Int", - "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": "Priority", - "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 + "label": "Priority" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "disabled", "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": "Disabled", - "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 + "label": "Disabled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_4", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Automatic Assignment", "description": "Example: {{ subject }}", - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", - "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": "Description", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assignment_rules_section", "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": "Assignment Rules", - "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 + "label": "Assignment Rules" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Simple Python Expression, Example: status == 'Open' and type == 'Bug'", - "fetch_if_empty": 0, "fieldname": "assign_condition", "fieldtype": "Code", - "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": "Assign 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_6", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", - "fetch_if_empty": 0, "fieldname": "unassign_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": "Unassign 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 + "label": "Unassign Condition" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assign_to_users_section", "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": "Assign To Users", - "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 + "label": "Assign To Users" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "rule", "fieldtype": "Select", - "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": "Rule", - "length": 0, - "no_copy": 0, "options": "Round Robin\nLoad Balancing", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "users", "fieldtype": "Table MultiSelect", - "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": "Users", - "length": 0, - "no_copy": 0, "options": "Assignment Rule User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "last_user", "fieldtype": "Link", - "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": "Last User", - "length": 0, - "no_copy": 0, "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "description": "Simple Python Expression, Example: Status in (\"Invalid\")", + "fieldname": "close_condition", + "fieldtype": "Code", + "label": "Close Condition" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-16 17:46:04.890120", + "modified": "2019-09-10 14:45:53.657667", "modified_by": "Administrator", "module": "Automation", "name": "Assignment Rule", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index a6b56b6466..f6f789a65d 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -23,12 +23,18 @@ class AssignmentRule(Document): return False + def apply_close(self, doc, assignments): + if (self.close_assignments and + self.name in [d.assignment_rule for d in assignments]): + return self.close_assignments(doc) + + return False + def apply_assign(self, doc): if self.safe_eval('assign_condition', doc): self.do_assignment(doc) return True - def do_assignment(self, doc): # clear existing assignment, to reassign assign_to.clear(doc.get('doctype'), doc.get('name')) @@ -52,6 +58,11 @@ class AssignmentRule(Document): if self.safe_eval('unassign_condition', doc): return assign_to.clear(doc.get('doctype'), doc.get('name')) + def close_assignments(self, doc): + '''Close assignments''' + if self.safe_eval('close_condition', doc): + return assign_to.close_all_assignments(doc.get('doctype'), doc.get('name')) + def get_user(self): ''' Get the next user for assignment @@ -108,13 +119,41 @@ def get_assignments(doc): return frappe.get_all('ToDo', fields = ['name', 'assignment_rule'], filters = dict( reference_type = doc.get('doctype'), reference_name = doc.get('name'), - status = 'Open' + status = ('!=', 'Cancelled') ), limit = 5) -def apply(doc, method): +@frappe.whitelist() +def bulk_apply(doctype, docnames): + import json + docnames = json.loads(docnames) + + background = len(docnames) > 5 + for name in docnames: + if background: + frappe.enqueue('frappe.automation.doctype.assignment_rule.assignment_rule.apply', doc=None, doctype=doctype, name=name) + else: + apply(None, doctype=doctype, name=name) + +def reopen_closed_assignment(doc): + todo = frappe.db.exists('ToDo', dict( + reference_type = doc.doctype, + reference_name = doc.name, + status = 'Closed' + )) + if not todo: + return False + todo = frappe.get_doc("ToDo", todo) + todo.status = 'Open' + todo.save() + return True + +def apply(doc, method=None, doctype=None, name=None): if frappe.flags.in_patch or frappe.flags.in_install: return + if not doc and doctype and name: + doc = frappe.get_doc(doctype, name) + assignment_rules = frappe.cache_manager.get_doctype_map('Assignment Rule', doc.doctype, dict( document_type = doc.doctype, disabled = 0), order_by = 'priority desc') @@ -130,19 +169,38 @@ def apply(doc, method): doc = doc.as_dict() assignments = get_assignments(doc) - clear = True + clear = True # are all assignments cleared + new_apply = False # are new assignments applied + if assignments: # first unassign + # use case, there are separate groups to be assigned for say L1 and L2, + # so when the value switches from L1 to L2, L1 team must be unassigned, then L2 can be assigned. clear = False for assignment_rule in assignment_rule_docs: clear = assignment_rule.apply_unassign(doc, assignments) - if clear: break + if clear: + break # apply rule only if there are no existing assignments if clear: for assignment_rule in assignment_rule_docs: - if assignment_rule.apply_assign(doc): break + new_apply = assignment_rule.apply_assign(doc) + if new_apply: + break + + # apply close rule only if assignments exists + assignments = get_assignments(doc) + if assignments: + for assignment_rule in assignment_rule_docs: + if not new_apply: + reopen = reopen_closed_assignment(doc) + if reopen: + break + close = assignment_rule.apply_close(doc, assignments) + if close: + break def get_assignment_rules(): - return [d.document_type for d in frappe.db.get_all('Assignment Rule', fields=['document_type'], filters=dict(disabled = 0))] \ No newline at end of file + return [d.document_type for d in frappe.db.get_all('Assignment Rule', fields=['document_type'], filters=dict(disabled = 0))] diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index 098b30cbae..3b1e1a76fa 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -91,22 +91,46 @@ class TestAutoAssign(unittest.TestCase): note = make_note(dict(public=1)) # check if auto assigned to first user - self.assertEqual(frappe.db.get_value('ToDo', dict( + todo = frappe.get_list('ToDo', dict( reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test@example.com') + ))[0] + + todo = frappe.get_doc('ToDo', todo['name']) + self.assertEqual(todo.owner, 'test@example.com') # test auto unassign note.public = 0 note.save() - # check if cleared - self.assertEqual(frappe.db.get_value('ToDo', dict( + todo.load_from_db() + + # check if todo is cancelled + self.assertEqual(todo.status, 'Cancelled') + + def test_close_assignment(self): + note = make_note(dict(public=1, content="valid")) + + # check if auto assigned + todo = frappe.get_list('ToDo', dict( reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), None) + ))[0] + + todo = frappe.get_doc('ToDo', todo['name']) + self.assertEqual(todo.owner, 'test@example.com') + + note.content="Closed" + note.save() + + todo.load_from_db() + + # check if todo is closed + self.assertEqual(todo.status, 'Closed') + # check if closed todo retained assignment + self.assertEqual(todo.owner, 'test@example.com') def check_multiple_rules(self): note = make_note(dict(public=1, notify_on_login=1)) @@ -131,6 +155,7 @@ def get_assignment_rule(): document_type = 'Note', assign_condition = 'public == 1', unassign_condition = 'public == 0 or notify_on_login == 1', + close_condition = '"Closed" in content', rule = 'Round Robin', users = [ dict(user = 'test@example.com'), diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index a0343e9f69..9e0598b128 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -1,687 +1,193 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "hash", - "beta": 0, "creation": "2012-07-03 13:30:35", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "description_and_status", + "status", + "priority", + "column_break_2", + "color", + "date", + "owner", + "description_section", + "description", + "section_break_6", + "reference_type", + "reference_name", + "column_break_10", + "role", + "assigned_by", + "assigned_by_full_name", + "sender", + "assignment_rule" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description_and_status", - "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": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Open", - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nClosed", - "permlevel": 0, - "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 + "options": "Open\nClosed\nCancelled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Medium", - "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Priority", - "length": 0, - "no_copy": 0, "oldfieldname": "priority", "oldfieldtype": "Data", - "options": "High\nMedium\nLow", - "permlevel": 0, - "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 + "options": "High\nMedium\nLow" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_2", - "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, - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Color", - "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": "Color", - "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 + "label": "Color" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Due Date", - "length": 0, - "no_copy": 0, "oldfieldname": "date", - "oldfieldtype": "Date", - "permlevel": 0, - "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 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "owner", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Allocated To", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "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 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description_section", - "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": "", - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "300px", - "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": "300px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_6", "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": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "label": "Reference" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "reference_type", "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": "Reference Type", - "length": 0, - "no_copy": 0, "oldfieldname": "reference_type", "oldfieldtype": "Data", - "options": "DocType", - "permlevel": 0, - "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 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "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": "Reference Name", - "length": 0, - "no_copy": 0, "oldfieldname": "reference_name", "oldfieldtype": "Data", - "options": "reference_type", - "permlevel": 0, - "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 + "options": "reference_type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_10", - "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, - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "role", "fieldtype": "Link", - "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": "Role", - "length": 0, - "no_copy": 0, "oldfieldname": "role", "oldfieldtype": "Link", - "options": "Role", - "permlevel": 0, - "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 + "options": "Role" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assigned_by", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Assigned By", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "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 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assigned_by.full_name", - "fetch_if_empty": 0, "fieldname": "assigned_by_full_name", "fieldtype": "Read Only", - "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": "Assigned By Full Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "label": "Assigned By Full Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sender", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sender", - "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 + "label": "Sender" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assignment_rule", "fieldtype": "Link", - "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": "Assignment Rule", - "length": 0, - "no_copy": 0, "options": "Assignment Rule", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 } ], - "has_web_view": 0, - "hide_toolbar": 0, "icon": "fa fa-check", "idx": 2, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-24 15:45:23.290491", + "modified": "2019-09-10 14:34:59.161750", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "All", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, "search_fields": "description, reference_type, reference_name", - "show_name_in_global_search": 0, + "sort_field": "modified", "sort_order": "DESC", "title_field": "description", "track_changes": 1, - "track_seen": 1, - "track_views": 0 -} + "track_seen": 1 +} \ No newline at end of file diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 38856d5068..27f7aa4984 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -20,7 +20,7 @@ def get(args=None): return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict( reference_type = args.get('doctype'), reference_name = args.get('name'), - status = 'Open' + status = ('!=', 'Cancelled') ), limit = 5) @frappe.whitelist() @@ -48,9 +48,6 @@ def add(args=None): else: from frappe.utils import nowdate - # if args.get("re_assign"): - # remove_from_todo_if_already_assigned(args['doctype'], args['name']) - if not args.get('description'): args['description'] = _('Assignment for {0} {1}'.format(args['doctype'], args['name'])) @@ -100,20 +97,29 @@ def add_multiple(args=None): args.update({"name": docname}) add(args) -def remove_from_todo_if_already_assigned(doctype, docname): - owner = frappe.db.get_value("ToDo", {"reference_type": doctype, "reference_name": docname, - "status":"Open"}, "owner") - if owner: - remove(doctype, docname, owner) +def close_all_assignments(doctype, name): + assignments = frappe.db.get_all('ToDo', fields=['owner'], filters = + dict(reference_type = doctype, reference_name = name, status=('!=', 'Cancelled'))) + if not assignments: + return False + + for assign_to in assignments: + set_status(doctype, name, assign_to.owner, status="Closed") + + return True @frappe.whitelist() def remove(doctype, name, assign_to): + set_status(doctype, name, assign_to, status="Cancelled") + +def set_status(doctype, name, assign_to, status="Cancelled"): """remove from todo""" try: - todo = frappe.db.get_value("ToDo", {"reference_type":doctype, "reference_name":name, "owner":assign_to, "status":"Open"}) + todo = frappe.db.get_value("ToDo", {"reference_type":doctype, + "reference_name":name, "owner":assign_to, "status": ('!=', status)}) if todo: todo = frappe.get_doc("ToDo", todo) - todo.status = "Closed" + todo.status = status todo.save(ignore_permissions=True) notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name) @@ -121,7 +127,7 @@ def remove(doctype, name, assign_to): pass # clear assigned_to if field exists - if frappe.get_meta(doctype).get_field("assigned_to"): + if frappe.get_meta(doctype).get_field("assigned_to") and status=="Cancelled": frappe.db.set_value(doctype, name, "assigned_to", None) return get({"doctype": doctype, "name": name}) @@ -136,7 +142,7 @@ def clear(doctype, name): return False for assign_to in assignments: - remove(doctype, name, assign_to.owner) + set_status(doctype, name, assign_to.owner, "Cancelled") return True diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index e790bf6d06..8c7082401a 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -217,12 +217,14 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields= return communications def get_assignments(dt, dn): - cl = frappe.db.sql("""select `name`, owner, description from `tabToDo` - where reference_type=%(doctype)s and reference_name=%(name)s and status='Open' - order by modified desc limit 5""", { - "doctype": dt, - "name": dn - }, as_dict=True) + cl = frappe.get_all("ToDo", + fields=['name', 'owner', 'description', 'status'], + limit= 5, + filters={ + 'reference_type': dt, + 'reference_name': dn, + 'status': ('!=', 'Cancelled'), + }) return cl diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 27db4d2200..53130afeb3 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -106,6 +106,15 @@ export default class BulkOperations { } } + apply_assignment_rule(docnames, done) { + if (docnames.length > 0) { + frappe.call('frappe.automation.doctype.assignment_rule.assignment_rule.bulk_apply', { + doctype: this.doctype, + docnames: docnames + }).then(() => done()); + } + } + submit_or_cancel(docnames, action='submit', done=null) { action = action.toLowerCase(); frappe diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 74b503e36b..5f9aac0afb 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1300,6 +1300,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }; }; + const bulk_assignment_rule = () => { + return { + label: __('Apply Assignment Rule'), + action: () => bulk_operations.apply_assignment_rule(this.get_checked_items(true), this.refresh), + standard: true + }; + }; + const bulk_printing = () => { return { label: __('Print'), @@ -1376,6 +1384,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { // bulk assignment actions_menu_items.push(bulk_assignment()); + actions_menu_items.push(bulk_assignment_rule()); + // bulk printing if (frappe.model.can_print(doctype)) { actions_menu_items.push(bulk_printing());