diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 93423db7624..9a8af694264 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.json b/erpnext/projects/doctype/task/task.json index d904d7092b5..2602aef626a 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/projects/doctype/task/task_dashboard.py b/erpnext/projects/doctype/task/task_dashboard.py new file mode 100644 index 00000000000..b776b98f676 --- /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 d0a9bf38084..ce75304e77f 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -3,14 +3,21 @@ frappe.ui.form.on("Issue", { frm.email_field = "raised_by"; }, - refresh: function(frm) { - if(frm.doc.status!=="Closed") { - frm.add_custom_button(__("Close"), function() { + refresh: function (frm) { + if (frm.doc.status !== "Closed") { + frm.add_custom_button(__("Close"), function () { 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.add_custom_button(__("Reopen"), function () { frm.set_value("status", "Open"); frm.save(); }); @@ -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.json b/erpnext/support/doctype/issue/issue.json index 21cf2f78486..7cb0df28a59 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 0b5eb539c8d..de3d144a7ef 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -7,20 +7,24 @@ 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) 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) @@ -29,17 +33,19 @@ class Issue(Document): clear(self.doctype, self.name) def on_update(self): - # create the communication email and remove the description - if (self.flags.create_communication and self.via_customer_portal): + # 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 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}) @@ -79,24 +85,30 @@ 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 + def get_list_context(context=None): return { "title": _("Issues"), @@ -107,11 +119,14 @@ 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 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') @@ -124,14 +139,23 @@ 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) st.status = status st.save() + 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 @@ -144,11 +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 @@ -160,3 +179,12 @@ 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): + return get_mapped_doc("Issue", source_name, { + "Issue": { + "doctype": "Task" + } + }, target_doc) diff --git a/erpnext/support/doctype/issue/issue_dashboard.py b/erpnext/support/doctype/issue/issue_dashboard.py new file mode 100644 index 00000000000..2ac7c816156 --- /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'] + } + ] + }