From 5327d0253d7904419b8c21ec573c8c3875d3fb79 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 13:20:50 +0530 Subject: [PATCH 1/5] feat(issue): Create tasks from issues --- erpnext/support/doctype/issue/issue.js | 9 ++++++++- erpnext/support/doctype/issue/issue.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) mode change 100644 => 100755 erpnext/support/doctype/issue/issue.js mode change 100644 => 100755 erpnext/support/doctype/issue/issue.py diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js old mode 100644 new mode 100755 index d0a9bf3808..05c9130378 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -9,6 +9,13 @@ frappe.ui.form.on("Issue", { frm.set_value("status", "Closed"); frm.save(); }); + + frm.add_custom_button(__("Task"), function () { + frappe.model.open_mapped_doc({ + method: "erpnext.support.doctype.issue.issue.make_task", + frm: frm + }); + }, __("Make")); } else { frm.add_custom_button(__("Reopen"), function() { frm.set_value("status", "Open"); @@ -37,7 +44,7 @@ frappe.ui.form.on("Issue", { if (!frm.timeline.wrapper.find('.btn-split-issue').length) { let split_issue = __("Split Issue") $(``) .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) if (!frm.timeline.wrapper.data("split-issue-event-attached")){ diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py old mode 100644 new mode 100755 index 0b5eb539c8..3e498c8269 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -7,11 +7,13 @@ import json from frappe import _ from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc from frappe.utils import now from frappe.utils.user import is_website_user sender_field = "raised_by" + class Issue(Document): def get_feed(self): return "{0}: {1}".format(_(self.status), self.subject) @@ -97,6 +99,7 @@ class Issue(Document): doc.save(ignore_permissions=True) return replicated_issue.name + def get_list_context(context=None): return { "title": _("Issues"), @@ -107,6 +110,7 @@ def get_list_context(context=None): 'no_breadcrumbs': True } + def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None): from frappe.www.list import get_list user = frappe.session.user @@ -124,12 +128,14 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) + @frappe.whitelist() def set_status(name, status): st = frappe.get_doc("Issue", name) st.status = status st.save() + def auto_close_tickets(): """ auto close the replied support tickets after 7 days """ auto_close_after_days = frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7 @@ -150,6 +156,7 @@ def set_multiple_status(names, status): for name in names: set_status(name, status) + def has_website_permission(doc, ptype, user, verbose=False): from erpnext.controllers.website_list_for_contact import has_website_permission permission_based_on_customer = has_website_permission(doc, ptype, user, verbose) @@ -160,3 +167,18 @@ def has_website_permission(doc, ptype, user, verbose=False): def update_issue(contact, method): """Called when Contact is deleted""" frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name) + + +@frappe.whitelist() +def make_task(source_name, target_doc=None): + def set_missing_values(source, target): + if not target.project: + target.project = frappe.db.get_value("Project", {"customer": source.customer}) + + doclist = get_mapped_doc("Issue", source_name, { + "Issue": { + "doctype": "Task" + } + }, target_doc, set_missing_values) + + return doclist From 2ba21fb66c8109a93c4170c2c828960da61903cb Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 16:27:57 +0530 Subject: [PATCH 2/5] fix(issue): Add issue name in Task and fix description in Issue --- erpnext/projects/doctype/task/task.json | 126 +++++++++++++++-------- erpnext/support/doctype/issue/issue.json | 12 ++- erpnext/support/doctype/issue/issue.py | 38 ++++--- 3 files changed, 113 insertions(+), 63 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index d904d7092b..2602aef626 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -79,6 +79,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "issue", + "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": "Issue", + "length": 0, + "no_copy": 0, + "options": "Issue", + "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 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -213,6 +246,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 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 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -251,11 +316,11 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", + "collapsible": 1, + "collapsible_depends_on": "eval:doc.__islocal", "columns": 0, "depends_on": "", - "fieldname": "section_break_10", + "fieldname": "sb_timeline", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -264,6 +329,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Timeline", "length": 0, "no_copy": 0, "permlevel": 0, @@ -513,38 +579,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -554,7 +588,7 @@ "collapsible_depends_on": "", "columns": 0, "depends_on": "", - "fieldname": "section_break0", + "fieldname": "sb_details", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -563,10 +597,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Details", "length": 0, "no_copy": 0, "oldfieldtype": "Section Break", - "options": "Simple", + "options": "", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -596,7 +631,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Details", + "label": "Task Description", "length": 0, "no_copy": 0, "oldfieldname": "description", @@ -624,7 +659,7 @@ "collapsible_depends_on": "", "columns": 0, "depends_on": "", - "fieldname": "section_break", + "fieldname": "sb_depends_on", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -726,7 +761,7 @@ "columns": 0, "depends_on": "", "description": "", - "fieldname": "actual", + "fieldname": "sb_actual", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -893,10 +928,10 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, "depends_on": "", - "fieldname": "section_break_17", + "fieldname": "sb_costing", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -905,6 +940,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Costing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -1058,9 +1094,9 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, - "fieldname": "more_details", + "fieldname": "sb_more_info", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -1069,7 +1105,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "", + "label": "More Info", "length": 0, "no_copy": 0, "permlevel": 0, diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 21cf2f7848..7cb0df28a5 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, @@ -349,8 +350,9 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 1, + "collapsible_depends_on": "eval:doc.status!=\"Closed\"", "columns": 0, - "fieldname": "section_break_7", + "fieldname": "sb_details", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -416,7 +418,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "response", + "fieldname": "sb_response", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -511,7 +513,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "additional_info", + "fieldname": "sb_additional_info", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -736,7 +738,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "section_break_19", + "fieldname": "sb_resoution", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -1035,7 +1037,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:27.615004", + "modified": "2019-02-14 02:55:47.562611", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 3e498c8269..7e13947ee9 100755 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -19,10 +19,12 @@ class Issue(Document): return "{0}: {1}".format(_(self.status), self.subject) def validate(self): - if (self.get("__islocal") and self.via_customer_portal): + if self.is_new() and self.via_customer_portal: self.flags.create_communication = True + if not self.raised_by: self.raised_by = frappe.session.user + self.update_status() self.set_lead_contact(self.raised_by) @@ -32,16 +34,18 @@ class Issue(Document): def on_update(self): # create the communication email and remove the description - if (self.flags.create_communication and self.via_customer_portal): + if self.flags.create_communication and self.via_customer_portal: self.create_communication() self.flags.communication_created = None def set_lead_contact(self, email_id): import email.utils + email_id = email.utils.parseaddr(email_id)[1] if email_id: if not self.lead: self.lead = frappe.db.get_value("Lead", {"email_id": email_id}) + if not self.contact and not self.customer: self.contact = frappe.db.get_value("Contact", {"email_id": email_id}) @@ -81,22 +85,27 @@ class Issue(Document): communication.ignore_mandatory = True communication.save() - self.db_set("description", "") - def split_issue(self, subject, communication_id): # Bug: Pressing enter doesn't send subject from copy import deepcopy + replicated_issue = deepcopy(self) replicated_issue.subject = subject frappe.get_doc(replicated_issue).insert() + # Replicate linked Communications - # todo get all communications in timeline before this, and modify them to append them to new doc + # TODO: get all communications in timeline before this, and modify them to append them to new doc comm_to_split_from = frappe.get_doc("Communication", communication_id) - communications = frappe.get_all("Communication", filters={"reference_name": comm_to_split_from.reference_name, "reference_doctype": "Issue", "creation": ('>=', comm_to_split_from.creation)}) + communications = frappe.get_all("Communication", + filters={"reference_doctype": "Issue", + "reference_name": comm_to_split_from.reference_name, + "creation": ('>=', comm_to_split_from.creation)}) + for communication in communications: doc = frappe.get_doc("Communication", communication.name) doc.reference_name = replicated_issue.name doc.save(ignore_permissions=True) + return replicated_issue.name @@ -113,9 +122,11 @@ def get_list_context(context=None): def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None): from frappe.www.list import get_list + user = frappe.session.user contact = frappe.db.get_value('Contact', {'user': user}, 'name') customer = None + if contact: contact_doc = frappe.get_doc('Contact', contact) customer = contact_doc.get_link_for('Customer') @@ -129,6 +140,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) +@frappe.whitelist() +def set_multiple_status(names, status): + names = json.loads(names) + for name in names: + set_status(name, status) + + @frappe.whitelist() def set_status(name, status): st = frappe.get_doc("Issue", name) @@ -137,7 +155,7 @@ def set_status(name, status): def auto_close_tickets(): - """ auto close the replied support tickets after 7 days """ + """Auto-close replied support tickets after 7 days""" auto_close_after_days = frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7 issues = frappe.db.sql(""" select name from tabIssue where status='Replied' and @@ -150,12 +168,6 @@ def auto_close_tickets(): doc.flags.ignore_mandatory = True doc.save() -@frappe.whitelist() -def set_multiple_status(names, status): - names = json.loads(names) - for name in names: - set_status(name, status) - def has_website_permission(doc, ptype, user, verbose=False): from erpnext.controllers.website_list_for_contact import has_website_permission From fa77b591ace3929aeb373d725173fcc55a38cda2 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 16:32:11 +0530 Subject: [PATCH 3/5] fix(issue): View Tasks against an Issue --- erpnext/support/doctype/issue/issue.js | 4 ++++ erpnext/support/doctype/issue/issue.py | 0 2 files changed, 4 insertions(+) mode change 100755 => 100644 erpnext/support/doctype/issue/issue.js mode change 100755 => 100644 erpnext/support/doctype/issue/issue.py diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js old mode 100755 new mode 100644 index 05c9130378..27bb46986b --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -4,6 +4,10 @@ frappe.ui.form.on("Issue", { }, refresh: function(frm) { + frm.add_custom_button(__("Task"), function () { + frappe.set_route("List", "Task", { "issue": frm.doc.name }); + }, __("View")); + if(frm.doc.status!=="Closed") { frm.add_custom_button(__("Close"), function() { frm.set_value("status", "Closed"); diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py old mode 100755 new mode 100644 From bf7c69f3c4f5b5c268e4ed6a762e0bba3703a40f Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 15 Feb 2019 14:21:10 +0530 Subject: [PATCH 4/5] fix(issue): Don't auto-set project --- erpnext/support/doctype/issue/issue.js | 8 ++++---- erpnext/support/doctype/issue/issue.py | 12 +++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 27bb46986b..03e1aa4f87 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -3,13 +3,13 @@ frappe.ui.form.on("Issue", { frm.email_field = "raised_by"; }, - refresh: function(frm) { + refresh: function (frm) { frm.add_custom_button(__("Task"), function () { frappe.set_route("List", "Task", { "issue": frm.doc.name }); }, __("View")); - if(frm.doc.status!=="Closed") { - frm.add_custom_button(__("Close"), function() { + if (frm.doc.status !== "Closed") { + frm.add_custom_button(__("Close"), function () { frm.set_value("status", "Closed"); frm.save(); }); @@ -21,7 +21,7 @@ frappe.ui.form.on("Issue", { }); }, __("Make")); } else { - frm.add_custom_button(__("Reopen"), function() { + frm.add_custom_button(__("Reopen"), function () { frm.set_value("status", "Open"); frm.save(); }); diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7e13947ee9..de3d144a7e 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -33,7 +33,7 @@ class Issue(Document): clear(self.doctype, self.name) def on_update(self): - # create the communication email and remove the description + # Add a communication in the issue timeline if self.flags.create_communication and self.via_customer_portal: self.create_communication() self.flags.communication_created = None @@ -183,14 +183,8 @@ def update_issue(contact, method): @frappe.whitelist() def make_task(source_name, target_doc=None): - def set_missing_values(source, target): - if not target.project: - target.project = frappe.db.get_value("Project", {"customer": source.customer}) - - doclist = get_mapped_doc("Issue", source_name, { + return get_mapped_doc("Issue", source_name, { "Issue": { "doctype": "Task" } - }, target_doc, set_missing_values) - - return doclist + }, target_doc) From f4bce6a66e6701b05fed036ef5b5e4ee7295109d Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Mon, 18 Feb 2019 12:53:11 +0530 Subject: [PATCH 5/5] fix(issue): Replace Make buttons on Issue and Task with a dashboard --- erpnext/projects/doctype/task/task.js | 13 ------------- .../projects/doctype/task/task_dashboard.py | 19 +++++++++++++++++++ erpnext/support/doctype/issue/issue.js | 4 ---- .../support/doctype/issue/issue_dashboard.py | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 erpnext/projects/doctype/task/task_dashboard.py create mode 100644 erpnext/support/doctype/issue/issue_dashboard.py diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 93423db762..9a8af69426 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -35,19 +35,6 @@ frappe.ui.form.on("Task", { } if(!doc.__islocal) { - if(frappe.model.can_read("Timesheet")) { - frm.add_custom_button(__("Timesheet"), function() { - frappe.route_options = {"project": doc.project, "task": doc.name} - frappe.set_route("List", "Timesheet"); - }, __("View"), true); - } - if(frappe.model.can_read("Expense Claim")) { - frm.add_custom_button(__("Expense Claims"), function() { - frappe.route_options = {"project": doc.project, "task": doc.name} - frappe.set_route("List", "Expense Claim"); - }, __("View"), true); - } - if(frm.perm[0].write) { if(frm.doc.status!=="Completed" && frm.doc.status!=="Cancelled") { frm.add_custom_button(__("Completed"), function() { diff --git a/erpnext/projects/doctype/task/task_dashboard.py b/erpnext/projects/doctype/task/task_dashboard.py new file mode 100644 index 0000000000..b776b98f67 --- /dev/null +++ b/erpnext/projects/doctype/task/task_dashboard.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'fieldname': 'task', + 'transactions': [ + { + 'label': _('Activity'), + 'items': ['Timesheet'] + }, + { + 'label': _('Accounting'), + 'items': ['Expense Claim'] + } + ] + } diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 03e1aa4f87..ce75304e77 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -4,10 +4,6 @@ frappe.ui.form.on("Issue", { }, refresh: function (frm) { - frm.add_custom_button(__("Task"), function () { - frappe.set_route("List", "Task", { "issue": frm.doc.name }); - }, __("View")); - if (frm.doc.status !== "Closed") { frm.add_custom_button(__("Close"), function () { frm.set_value("status", "Closed"); diff --git a/erpnext/support/doctype/issue/issue_dashboard.py b/erpnext/support/doctype/issue/issue_dashboard.py new file mode 100644 index 0000000000..2ac7c81615 --- /dev/null +++ b/erpnext/support/doctype/issue/issue_dashboard.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'fieldname': 'issue', + 'transactions': [ + { + 'label': _('Activity'), + 'items': ['Task'] + } + ] + }