diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 0a5d85636f..bf45347c4f 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -44,7 +44,7 @@ class AssignmentRule(Document): user = self.get_user() assign_to.add(dict( - assign_to = user, + assign_to = [user], doctype = doc.get('doctype'), name = doc.get('name'), description = frappe.render_template(self.description, doc), diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 6d1e865a45..dcfb38bd08 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -71,7 +71,7 @@ class TestEvent(unittest.TestCase): ev = frappe.get_doc(self.test_records[0]).insert() add({ - "assign_to": "test@example.com", + "assign_to": ["test@example.com"], "doctype": "Event", "name": ev.name, "description": "Test Assignment" @@ -83,7 +83,7 @@ class TestEvent(unittest.TestCase): # add another one add({ - "assign_to": self.test_user, + "assign_to": [self.test_user], "doctype": "Event", "name": ev.name, "description": "Test Assignment" diff --git a/frappe/desk/doctype/notification_log/test_notification_log.py b/frappe/desk/doctype/notification_log/test_notification_log.py index fe7d56c081..e59aee30c9 100644 --- a/frappe/desk/doctype/notification_log/test_notification_log.py +++ b/frappe/desk/doctype/notification_log/test_notification_log.py @@ -13,7 +13,7 @@ class TestNotificationLog(unittest.TestCase): user = get_user() assign_task({ - "assign_to": user, + "assign_to": [user], "doctype": 'ToDo', "name": todo.name, "description": todo.description diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 76c7caa63d..a916cbca82 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -11,6 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import enqueue_create get_title, get_title_html import frappe.utils import frappe.share +import json class DuplicateToDoError(frappe.ValidationError): pass @@ -19,17 +20,17 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict( + return frappe.get_all('ToDo', fields=['owner', 'name'], filters=dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') - ), limit = 5) + ), limit=5) @frappe.whitelist() def add(args=None): """add in someone's to do list args = { - "assign_to": , + "assign_to": [], "doctype": , "name": , "description": , @@ -40,56 +41,68 @@ def add(args=None): if not args: args = frappe.local.form_dict - if frappe.db.sql("""SELECT `owner` - FROM `tabToDo` - WHERE `reference_type`=%(doctype)s - AND `reference_name`=%(name)s - AND `status`='Open' - AND `owner`=%(assign_to)s""", args): - frappe.throw(_("Already in user's To Do list"), DuplicateToDoError) - else: - from frappe.utils import nowdate + users_with_duplicate_todo = [] + shared_with_users = [] - if not args.get('description'): - args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - - d = frappe.get_doc({ - "doctype":"ToDo", - "owner": args['assign_to'], + for assign_to in frappe.parse_json(args.get("assign_to")): + filters = { "reference_type": args['doctype'], "reference_name": args['name'], - "description": args.get('description'), - "priority": args.get("priority", "Medium"), "status": "Open", - "date": args.get('date', nowdate()), - "assigned_by": args.get('assigned_by', frappe.session.user), - 'assignment_rule': args.get('assignment_rule') - }).insert(ignore_permissions=True) + "owner": assign_to + } - # set assigned_to if field exists - if frappe.get_meta(args['doctype']).get_field("assigned_to"): - frappe.db.set_value(args['doctype'], args['name'], "assigned_to", args['assign_to']) + if frappe.get_all("ToDo", filters=filters): + users_with_duplicate_todo.append(assign_to) + else: + from frappe.utils import nowdate - doc = frappe.get_doc(args['doctype'], args['name']) + if not args.get('description'): + args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - # if assignee does not have permissions, share - if not frappe.has_permission(doc=doc, user=args['assign_to']): - frappe.share.add(doc.doctype, doc.name, args['assign_to']) - frappe.msgprint(_('Shared with user {0} with read access').format(args['assign_to']), alert=True) + d = frappe.get_doc({ + "doctype": "ToDo", + "owner": assign_to, + "reference_type": args['doctype'], + "reference_name": args['name'], + "description": args.get('description'), + "priority": args.get("priority", "Medium"), + "status": "Open", + "date": args.get('date', nowdate()), + "assigned_by": args.get('assigned_by', frappe.session.user), + 'assignment_rule': args.get('assignment_rule') + }).insert(ignore_permissions=True) - # make this document followed by assigned user - follow_document(args['doctype'], args['name'], args['assign_to']) + # set assigned_to if field exists + if frappe.get_meta(args['doctype']).get_field("assigned_to"): + frappe.db.set_value(args['doctype'], args['name'], "assigned_to", assign_to) - # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ - description=args.get("description")) + doc = frappe.get_doc(args['doctype'], args['name']) + + # if assignee does not have permissions, share + if not frappe.has_permission(doc=doc, user=assign_to): + frappe.share.add(doc.doctype, doc.name, assign_to) + shared_with_users.append(assign_to) + + # make this document followed by assigned user + follow_document(args['doctype'], args['name'], assign_to) + + # notify + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', + description=args.get("description")) + + if shared_with_users: + user_list = format_message_for_assign_to(shared_with_users) + frappe.msgprint(_("Shared with the following Users with Read access:{0}").format(user_list, alert=True)) + + if users_with_duplicate_todo: + user_list = format_message_for_assign_to(users_with_duplicate_todo) + frappe.msgprint(_("Already in the following Users ToDo list:{0}").format(user_list, alert=True)) return get(args) @frappe.whitelist() def add_multiple(args=None): - import json - if not args: args = frappe.local.form_dict @@ -183,3 +196,5 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', enqueue_create_notification(owner, notification_doc) +def format_message_for_assign_to(users): + return "

" + "
".join(users) \ No newline at end of file diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 61d1789518..95ceb246e6 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -87,23 +87,17 @@ frappe.ui.form.AssignTo = Class.extend({ if(!me.assign_to) { me.assign_to = new frappe.ui.form.AssignToDialog({ - obj: me, - method: 'frappe.desk.form.assign_to.add', + method: "frappe.desk.form.assign_to.add", doctype: me.frm.doctype, docname: me.frm.docname, - callback: function(r) { + frm: me.frm, + callback: function (r) { me.render(r.message); } }); } me.assign_to.dialog.clear(); - - if(me.frm.meta.title_field) { - me.assign_to.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]) - } - me.assign_to.dialog.show(); - me.assign_to = null; }, remove: function(owner) { var me = this; @@ -130,81 +124,126 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ - var me = this - var dialog = new frappe.ui.Dialog({ - title: __('Add to To Do'), - fields: [ - { fieldtype: 'Link', fieldname: 'assign_to', options: 'User', label: __("Assign To"), reqd: true, filters: { 'user_type': 'System User' }}, - { fieldtype: 'Check', fieldname: 'myself', label: __("Assign to me"), "default": 0 }, - { fieldtype: 'Small Text', fieldname: 'description', label: __("Comment") }, - { fieldtype: 'Section Break' }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Date', fieldname: 'date', label: __("Complete By") }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Select', fieldname: 'priority', label: __("Priority"), - options: [ - { value: 'Low', label: __('Low') }, - { value: 'Medium', label: __('Medium') }, - { value: 'High', label: __('High') } - ], - // Pick up priority from the source document, if it exists and is available in ToDo - 'default': ["Low", "Medium", "High"].includes(opts.obj.frm && opts.obj.frm.doc.priority - ? opts.obj.frm.doc.priority : 'Medium') - }, - ], - primary_action: function() { frappe.ui.add_assignment(opts, this) }, - primary_action_label: __("Add") - }) - $.extend(me, dialog); + $.extend(this, opts); - me.dialog = dialog; - - me.dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query"; - - var myself = me.dialog.get_input("myself").on("click", function() { - me.toggle_myself(this); - }); - me.toggle_myself(myself); - }, - toggle_myself: function(myself) { - var me = this; - if($(myself).prop("checked")) { - me.dialog.set_value("assign_to", frappe.session.user); - me.dialog.get_field("notify").$wrapper.toggle(false); - me.dialog.get_field("assign_to").$wrapper.toggle(false); - } else { - me.dialog.set_value("assign_to", ""); - me.dialog.get_field("assign_to").$wrapper.toggle(true); - } + this.make(); + this.set_description_from_doc(); }, + make: function() { + let me = this; -}); + me.dialog = new frappe.ui.Dialog({ + title: __('Add to ToDo'), + fields: me.get_fields(), + primary_action_label: __("Add"), + primary_action: function() { + let args = me.dialog.get_values(); -frappe.ui.add_assignment = function(opts, dialog) { - var assign_to = dialog.fields_dict.assign_to.get_value(); - var args = dialog.get_values(); - if(args && assign_to) { - dialog.set_message('Assigning...'); - return frappe.call({ - method: opts.method, - args: $.extend(args, { - doctype: opts.doctype, - name: opts.docname, - assign_to: assign_to, - bulk_assign: opts.bulk_assign || false, - re_assign: opts.re_assign || false - }), - btn: dialog.get_primary_btn(), - callback: function(r) { - if(!r.exc) { - if(opts.callback){ - opts.callback(r); - } - dialog && dialog.hide(); - } else { - dialog.clear_message(); + if (args && args.assign_to) { + me.dialog.set_message("Assigning..."); + + frappe.call({ + method: me.method, + args: $.extend(args, { + doctype: me.doctype, + name: me.docname, + assign_to: args.assign_to, + bulk_assign: me.bulk_assign || false, + re_assign: me.re_assign || false + }), + btn: me.dialog.get_primary_btn(), + callback: function(r) { + if (!r.exc) { + if (me.callback) { + me.callback(r); + } + me.dialog && me.dialog.hide(); + } else { + me.dialog.clear_message(); + } + }, + }); } }, }); + }, + assign_to_me: function() { + let me = this; + let assign_to = []; + + if (me.dialog.get_value("assign_to_me")) { + assign_to.push(frappe.session.user); + } + + me.dialog.set_value("assign_to", assign_to); + }, + set_description_from_doc: function() { + let me = this; + + if (me.frm && me.frm.meta.title_field) { + me.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]); + } + }, + get_fields: function() { + let me = this; + + return [ + { + fieldtype: 'MultiSelectPills', + fieldname: 'assign_to', + label: __("Assign To"), + reqd: true, + get_data: function(txt) { + return frappe.db.get_link_options("User", txt, {user_type: "System User", enabled: 1}); + } + }, + { + label: __("Assign to me"), + fieldtype: 'Check', + fieldname: 'assign_to_me', + default: 0, + onchange: () => me.assign_to_me() + }, + { + label: __("Comment"), + fieldtype: 'Small Text', + fieldname: 'description' + }, + { + fieldtype: 'Section Break' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Complete By"), + fieldtype: 'Date', + fieldname: 'date' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Priority"), + fieldtype: 'Select', + fieldname: 'priority', + options: [ + { + value: 'Low', + label: __('Low') + }, + { + value: 'Medium', + label: __('Medium') + }, + { + value: 'High', + label: __('High') + } + ], + // Pick up priority from the source document, if it exists and is available in ToDo + default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') + } + ]; } -} +}); diff --git a/frappe/social/doctype/energy_point_log/test_energy_point_log.py b/frappe/social/doctype/energy_point_log/test_energy_point_log.py index 9e4d0e6425..1ecaba9cd5 100644 --- a/frappe/social/doctype/energy_point_log/test_energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/test_energy_point_log.py @@ -306,7 +306,7 @@ def get_points(user, point_type='energy_points'): def assign_users_to_todo(todo_name, users): for user in users: assign_to({ - 'assign_to': user, + 'assign_to': [user], 'doctype': 'ToDo', 'name': todo_name }) \ No newline at end of file diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index f32e3c9272..439e1546c0 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -60,7 +60,7 @@ class TestAssign(unittest.TestCase): def assign(doc, user): return frappe.desk.form.assign_to.add({ - "assign_to": user, + "assign_to": [user], "doctype": doc.doctype, "name": doc.name, "description": 'test',