diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index 7042df05942..2b2b6afe79f 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -32,7 +32,7 @@ class PaymentTermsTemplate(Document): def check_duplicate_terms(self): terms = [] for term in self.terms: - term_info = (term.credit_days, term.due_date_based_on) + term_info = (term.credit_days, term.credit_months, term.due_date_based_on) if term_info in terms: frappe.msgprint( _('The Payment Term at row {0} is possibly a duplicate.').format(term.idx), diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 29ca71bd88e..442b6c2db2b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import (cstr, validate_email_add, cint, comma_and, has_gravatar, now, getdate, nowdate) +from frappe.utils import (cstr, validate_email_address, cint, comma_and, has_gravatar, now, getdate, nowdate) from frappe.model.mapper import get_mapped_doc from erpnext.controllers.selling_controller import SellingController @@ -38,7 +38,7 @@ class Lead(SellingController): if self.email_id: if not self.flags.ignore_email_validation: - validate_email_add(self.email_id, True) + validate_email_address(self.email_id, True) if self.email_id == self.lead_owner: frappe.throw(_("Lead Owner cannot be same as the Lead")) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d518cd89957..a403c393b95 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import getdate, validate_email_add, today, add_years, format_datetime +from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime from frappe.model.naming import set_name_by_naming_series from frappe import throw, _, scrub from frappe.permissions import add_user_permission, remove_user_permission, \ @@ -142,9 +142,9 @@ class Employee(NestedSet): def validate_email(self): if self.company_email: - validate_email_add(self.company_email, True) + validate_email_address(self.company_email, True) if self.personal_email: - validate_email_add(self.personal_email, True) + validate_email_address(self.personal_email, True) def validate_status(self): if self.status == 'Left': diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index ea81fe793d8..4fc7719f383 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from frappe.model.document import Document import frappe from frappe import _ -from frappe.utils import comma_and, validate_email_add +from frappe.utils import comma_and, validate_email_address sender_field = "email_id" @@ -28,7 +28,7 @@ class JobApplicant(Document): def validate(self): self.check_email_id_is_unique() if self.email_id: - validate_email_add(self.email_id, True) + validate_email_address(self.email_id, True) if not self.applicant_name and self.email_id: guess = self.email_id.split('@')[0] diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 8f7b5a876c0..6a7a80a7845 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -561,7 +561,7 @@ "label": "Earned Leave Frequency", "length": 0, "no_copy": 0, - "options": "Monthly\nQuarterly\nYearly", + "options": "Monthly\nQuarterly\nHalf-Yearly\nYearly", "permlevel": 0, "precision": "", "print_hide": 0, diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 02262012f1c..e0b6a51d1ab 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -260,7 +260,7 @@ def allocate_earned_leaves(): fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"], filters={'is_earned_leave' : 1}) today = getdate() - divide_by_frequency = {"Yearly": 1, "Quarterly": 4, "Monthly": 12} + divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} if e_leave_types: for e_leave_type in e_leave_types: leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' @@ -297,6 +297,9 @@ def check_frequency_hit(from_date, to_date, frequency): if frequency == "Quarterly": if not months % 3: return True + elif frequency == "Half-Yearly": + if not months % 6: + return True elif frequency == "Yearly": if not months % 12: return True diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index b9b2dd8fc97..9afaf90e7ab 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -17,5 +17,5 @@ class Member(Document): self.validate_email_type(self.email) def validate_email_type(self, email): - from frappe.utils import validate_email_add - validate_email_add(email.strip(), True) \ No newline at end of file + from frappe.utils import validate_email_address + validate_email_address(email.strip(), True) \ No newline at end of file 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/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 5c443d31040..5d7c5826098 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -43,11 +43,11 @@ def execute(filters=None): item_map[item]["item_group"], item_map[item]["brand"], item_map[item]["description"], warehouse, - item_map[item]["stock_uom"], qty_dict.opening_qty, + item_map[item]["stock_uom"], qty_dict.bal_qty, + qty_dict.bal_val, qty_dict.opening_qty, qty_dict.opening_val, qty_dict.in_qty, qty_dict.in_val, qty_dict.out_qty, - qty_dict.out_val, qty_dict.bal_qty, - qty_dict.bal_val, qty_dict.val_rate, + qty_dict.out_val, qty_dict.val_rate, item_reorder_level, item_reorder_qty, company @@ -79,14 +79,14 @@ def get_columns(): {"label": _("Description"), "fieldname": "description", "width": 140}, {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100}, {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90}, + {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, + {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, {"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110}, {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80}, {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80}, - {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"}, {"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, 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'] + } + ] + }