From 174299124fd34167f4d58e67104bd666e4d4bdb0 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 29 Sep 2015 16:36:57 +0530 Subject: [PATCH] [cleanup] [wip] email digest --- .../leave_application/leave_application.py | 8 +- erpnext/patches.txt | 1 + ...anagers_regarding_wrong_tax_calculation.py | 4 +- erpnext/patches/v6_4/email_digest_update.py | 6 + .../doctype/email_digest/email_digest.js | 36 +- .../doctype/email_digest/email_digest.json | 773 ++---------------- .../doctype/email_digest/email_digest.py | 522 +++++------- .../email_digest/templates/default.html | 113 +++ .../doctype/email_digest/test_email_digest.py | 12 + 9 files changed, 412 insertions(+), 1063 deletions(-) create mode 100644 erpnext/patches/v6_4/email_digest_update.py create mode 100644 erpnext/setup/doctype/email_digest/templates/default.html create mode 100644 erpnext/setup/doctype/email_digest/test_email_digest.py diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 228f2bde11..054117b55f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, json from frappe import _ -from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_url_to_form, \ +from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ comma_or, get_fullname from frappe import msgprint from erpnext.hr.utils import set_employee_name @@ -164,7 +164,7 @@ class LeaveApplication(Document): def _get_message(url=False): if url: - name = get_url_to_form(self.doctype, self.name) + name = get_link_to_form(self.doctype, self.name) else: name = self.name @@ -184,8 +184,8 @@ class LeaveApplication(Document): name = self.name employee_name = cstr(employee.employee_name) if url: - name = get_url_to_form(self.doctype, self.name) - employee_name = get_url_to_form("Employee", self.employee, label=employee_name) + name = get_link_to_form(self.doctype, self.name) + employee_name = get_link_to_form("Employee", self.employee, label=employee_name) return (_("New Leave Application") + ": %s - " + _("Employee") + ": %s") % (name, employee_name) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bf33fb3281..9a724052c1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -218,3 +218,4 @@ erpnext.patches.v6_4.fix_status_in_sales_and_purchase_order erpnext.patches.v6_4.fix_modified_in_sales_order_and_purchase_order erpnext.patches.v6_4.fix_duplicate_bins erpnext.patches.v6_4.fix_sales_order_maintenance_status +erpnext.patches.v6_4.email_digest_update diff --git a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py index 2c0c8b4e59..125b84fce1 100644 --- a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py +++ b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.email import sendmail_to_system_managers -from frappe.utils import get_url_to_form +from frappe.utils import get_link_to_form def execute(): wrong_records = [] @@ -15,7 +15,7 @@ def execute(): and modified >= '2015-02-17' and docstatus=1""".format(dt)) if records: - records = [get_url_to_form(dt, d) for d in records] + records = [get_link_to_form(dt, d) for d in records] wrong_records.append([dt, records]) if wrong_records: diff --git a/erpnext/patches/v6_4/email_digest_update.py b/erpnext/patches/v6_4/email_digest_update.py new file mode 100644 index 0000000000..54e9a04254 --- /dev/null +++ b/erpnext/patches/v6_4/email_digest_update.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + frappe.reload_doctype("Email Digest") + frappe.db.sql("""update `tabEmail Digest` set expense_year_to_date = + income_year_to_date""") diff --git a/erpnext/setup/doctype/email_digest/email_digest.js b/erpnext/setup/doctype/email_digest/email_digest.js index 01c137e2e3..0d2ea14444 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.js +++ b/erpnext/setup/doctype/email_digest/email_digest.js @@ -7,28 +7,22 @@ cur_frm.cscript.refresh = function(doc, dt, dn) { var err_msg = __("There was an error. One probable reason could be that you haven't saved the form. Please contact support@erpnext.com if the problem persists.") cur_frm.add_custom_button(__('View Now'), function() { - doc = locals[dt][dn]; - if(doc.__unsaved != 1) { - return $c_obj(doc, 'get_digest_msg', '', function(r, rt) { - if(r.exc) { - msgprint(err_msg); - console.log(r.exc); - } else { - //console.log(arguments); - var d = new frappe.ui.Dialog({ - title: __('Email Digest: ') + dn, - width: 800 - }); - - $a(d.body, 'div', '', '', r['message']); - - d.show(); - } - }); - } else { - msgprint(save_msg); - } + frappe.call({ + method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg', + args: { + name: doc.name + }, + callback: function(r) { + var d = new frappe.ui.Dialog({ + title: __('Email Digest: ') + dn, + width: 800 + }); + $(d.body).html(r.message); + d.show(); + } + }); }, "icon-eye-open", "btn-default"); + cur_frm.add_custom_button(__('Send Now'), function() { doc = locals[dt][dn]; if(doc.__unsaved != 1) { diff --git a/erpnext/setup/doctype/email_digest/email_digest.json b/erpnext/setup/doctype/email_digest/email_digest.json index 0c67ded813..5659cd90ac 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.json +++ b/erpnext/setup/doctype/email_digest/email_digest.json @@ -243,49 +243,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "Balances of Accounts of type \"Bank\" or \"Cash\"", - "fieldname": "bank_balance", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Bank/Cash Balance", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "income_year_to_date", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Income Year to Date", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -297,7 +254,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Income Booked", + "label": "Income", "no_copy": 0, "permlevel": 0, "print_hide": 0, @@ -319,7 +276,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Expenses Booked", + "label": "Expense", "no_copy": 0, "permlevel": 0, "print_hide": 0, @@ -334,7 +291,72 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "description": "Receivable / Payable account will be identified based on the field Master Type", + "description": "Balances of Accounts of type \"Bank\" or \"Cash\"", + "fieldname": "bank_balance", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Bank Balance", + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "income_year_to_date", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Annual Income", + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "expense_year_to_date", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Annual Expense", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "", "fieldname": "column_break_16", "fieldtype": "Column Break", "hidden": 0, @@ -356,7 +378,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "description": "Total amount of invoices sent to the customer during the digest period", + "description": "", "fieldname": "invoiced_amount", "fieldtype": "Check", "hidden": 0, @@ -378,7 +400,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "description": "Total amount of invoices received from suppliers during the digest period", + "description": "", "fieldname": "payables", "fieldtype": "Check", "hidden": 0, @@ -395,659 +417,6 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "Payments received during the digest period", - "fieldname": "collections", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Payments Received", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "Payments made during the digest period", - "fieldname": "payments", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Payments Made", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break_20", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Buying & Selling", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "buying_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Buying", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_purchase_requests", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Material Requests", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_supplier_quotations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Supplier Quotations", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_purchase_orders", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Purchase Orders", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "selling_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Selling", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_leads", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Leads", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_enquiries", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Enquiries", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_quotations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Quotations", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_sales_orders", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Sales Orders", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break_34", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Inventory & Support", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "stock_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Stock", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_delivery_notes", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Delivery Notes", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_purchase_receipts", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Purchase Receipts", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_stock_entries", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Stock Entries", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "support_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Support", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_support_tickets", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Support Tickets", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "open_tickets", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Open Tickets", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_communications", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Communications", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break_40", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Projects & System", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "projects_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Projects", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "new_projects", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New Projects", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "core_module", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "System", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "scheduler_errors", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Scheduler Failed Events", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "user_specific", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "User Specific", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "general", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "General", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "calendar_events", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Calendar Events", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "todo_list", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "To Do List", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "stub", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Stub", - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 } ], "hide_heading": 0, @@ -1059,7 +428,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-02-05 05:11:38.024529", + "modified": "2015-10-02 07:07:25.518002", "modified_by": "Administrator", "module": "Setup", "name": "Email Digest", diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 599e9c55de..c367584d79 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -4,52 +4,24 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import fmt_money, formatdate, now_datetime, cstr, esc, \ - get_url_to_form, get_fullname -from frappe.utils.dateutils import datetime_in_user_format +from frappe.utils import fmt_money, formatdate, format_time, now_datetime, \ + get_url_to_form, get_url_to_list, flt from datetime import timedelta from dateutil.relativedelta import relativedelta from frappe.core.doctype.user.user import STANDARD_USERS - -content_sequence = [ - ["Income / Expenses", ["income_year_to_date", "income", "expenses_booked"]], - ["Receivables / Payables", ["collections", "payments", - "invoiced_amount", "payables"]], - ["Bank Balance", ["bank_balance"]], - ["Buying", ["new_purchase_requests", "new_supplier_quotations", "new_purchase_orders"]], - ["CRM", ["new_leads", "new_enquiries"]], - ["Selling", ["new_quotations", "new_sales_orders"]], - ["Stock", ["new_delivery_notes", "new_purchase_receipts", "new_stock_entries"]], - ["Support", ["new_communications", "new_support_tickets", "open_tickets"]], - ["Projects", ["new_projects"]], - ["System", ["scheduler_errors"]], -] +import frappe.desk.notifications +from erpnext.accounts.utils import get_balance_on user_specific_content = ["calendar_events", "todo_list"] -digest_template = """ -

%(name)s

-

%(company)s

-

%(date)s

-
-%(with_value)s -%(no_value)s -
-

To change what you see here, -create more digests, go to Setup > Email Digest

""" - -row_template = """

-%(label)s: - - %(currency)s%(value)s -

""" - from frappe.model.document import Document class EmailDigest(Document): def __init__(self, arg1, arg2=None): super(EmailDigest, self).__init__(arg1, arg2) + self.from_date, self.to_date = self.get_from_to_date() - self.future_from_date, self.future_to_date = self.get_future_from_to_date() + self.set_dates() + self._accounts = {} self.currency = frappe.db.get_value("Company", self.company, "default_currency") @@ -77,325 +49,208 @@ class EmailDigest(Document): recipients = filter(lambda r: r in valid_users, self.recipient_list.split("\n")) - common_msg = self.get_common_content() + original_user = frappe.session.user + if recipients: for user_id in recipients: - msg_for_this_receipient = self.get_msg_html(self.get_user_specific_content(user_id) + \ - common_msg) + frappe.set_user(user_id) + msg_for_this_receipient = self.get_msg_html() if msg_for_this_receipient: frappe.sendmail(recipients=user_id, subject="{frequency} Digest".format(frequency=self.frequency), message=msg_for_this_receipient, bulk=True) - def get_digest_msg(self): - return self.get_msg_html(self.get_user_specific_content(frappe.session.user) + \ - self.get_common_content(), send_only_if_updates=False) + frappe.set_user(original_user) - def get_common_content(self): - out = [] - for module, content in content_sequence: - module_out = [] - for ctype in content: - if self.get(ctype) and hasattr(self, "get_"+ctype): - module_out.append(getattr(self, "get_"+ctype)()) - if any([m[0] for m in module_out]): - out += [[1, "

" + _(module) + "

"]] + module_out + [[1, "
"]] - else: - out += module_out + def get_msg_html(self): + """Build email digest content""" + context = frappe._dict() + context.update(self.__dict__) - return out + self.set_title(context) + self.set_style(context) - def get_user_specific_content(self, user_id): - original_session_user = frappe.session.user + self.set_accounting_cards(context) - # setting session user for role base event fetching - frappe.session.user = user_id + context.events = self.get_calendar_events() + context.todo_list = self.get_todo_list() + context.notifications = self.get_notifications() - out = [] - for ctype in user_specific_content: - if self.get(ctype) and hasattr(self, "get_"+ctype): - out.append(getattr(self, "get_"+ctype)(user_id)) + # style + return frappe.render_template("erpnext/setup/doctype/email_digest/templates/default.html", + context, is_path=True) - frappe.session.user = original_session_user + def set_title(self, context): + """Set digest title""" + if self.frequency=="Daily": + context.title = _("Daily Reminders") + context.subtitle = _("Pending activities for today") + elif self.frequency=="Weekly": + context.title = _("This Week's Summary") + context.subtitle = _("Summary for this week and pending activities") + elif self.frequency=="Monthly": + context.title = _("This Month's Summary") + context.subtitle = _("Summary for this month and pending activities") - return out + def set_style(self, context): + """Set standard digest style""" + context.text_muted = '#8D99A6' + context.h1 = 'margin-bottom: 30px; margin-bottom: 0' + context.label_css = '''display: inline-block; color: {text_muted}; + padding: 3px 7px; margin-right: 7px;'''.format(text_muted = context.text_muted) + context.section_head = 'margin-top: 60px;' + context.line_item = 'padding: 7px 0px; margin: 0; border-bottom: 1px solid #d1d8dd;' - def get_msg_html(self, out, send_only_if_updates=True): - with_value = [o[1] for o in out if o[0]] - if with_value: - has_updates = True - with_value = "\n".join(with_value) - else: - has_updates = False - with_value = "

" + _("There were no updates in the items selected for this digest.") + "


" + def get_notifications(self): + """Get notifications for user""" + notifications = frappe.desk.notifications.get_notifications() - if not has_updates and send_only_if_updates: - return + notifications = sorted(notifications.get("open_count_doctype", {}).items(), + lambda a, b: 1 if a[1] < b[1] else -1) - # seperate out no value items - no_value = [o[1] for o in out if not o[0]] - if no_value: - no_value = """

""" + _("No Updates For") + """:

""" + "\n".join(no_value) + notifications = [{"key": n[0], "value": n[1], + "link": get_url_to_list(n[0])} for n in notifications] - date = self.frequency == "Daily" and formatdate(self.from_date) or \ - "%s to %s" % (formatdate(self.from_date), formatdate(self.to_date)) + return notifications - msg = digest_template % { - "digest": self.frequency + " Digest", - "date": date, - "company": self.company, - "with_value": with_value, - "no_value": no_value or "", - "name": self.name - } - - return msg - - def get_income_year_to_date(self): - return self.get_income(frappe.db.get_defaults("year_start_date"), - self.meta.get_label("income_year_to_date")) - - def get_bank_balance(self): - # account is of type "Bank" or "Cash" - accounts = dict([[a["name"], [a["account_name"], 0]] for a in self.get_accounts() - if a["account_type"] in ["Bank", "Cash"]]) - ackeys = accounts.keys() - - for gle in self.get_gl_entries(None, self.to_date): - if gle["account"] in ackeys: - accounts[gle["account"]][1] += gle["debit"] - gle["credit"] - - # build html - out = self.get_html("Bank/Cash Balance as on " + formatdate(self.to_date), "", "") - for ac in ackeys: - if accounts[ac][1]: - out += "\n" + self.get_html(accounts[ac][0], self.currency, - fmt_money(accounts[ac][1]), style="margin-left: 17px") - return sum((accounts[ac][1] for ac in ackeys)), out - - def get_income(self, from_date=None, label=None): - accounts = [a["name"] for a in self.get_accounts() if a["root_type"]=="Income"] - - income = 0 - for gle in self.get_gl_entries(from_date or self.from_date, self.to_date): - if gle["account"] in accounts: - income += gle["credit"] - gle["debit"] - - return income, self.get_html(label or self.meta.get_label("income"), self.currency, - fmt_money(income)) - - def get_expenses_booked(self): - accounts = [a["name"] for a in self.get_accounts() if a["root_type"]=="Expense"] - - expense = 0 - for gle in self.get_gl_entries(self.from_date, self.to_date): - if gle["account"] in accounts: - expense += gle["debit"] - gle["credit"] - - return expense, self.get_html(self.meta.get_label("expenses_booked"), self.currency, - fmt_money(expense)) - - def get_collections(self): - return self.get_party_total("Customer", "credit", self.meta.get_label("collections")) - - def get_payments(self): - return self.get_party_total("Supplier", "debit", self.meta.get_label("payments")) - - def get_party_total(self, party_type, gle_field, label): - import re - party_list = frappe.db.sql_list("select name from `tab{0}`".format(party_type)) - - # account is "Bank" or "Cash" - bc_accounts = [esc(a["name"], "()|") for a in self.get_accounts() - if a["account_type"] in ["Bank", "Cash"]] - bc_regex = re.compile("""(%s)""" % "|".join(bc_accounts)) - - total = 0 - for gle in self.get_gl_entries(self.from_date, self.to_date): - # check that its made against a bank or cash account - if gle["party_type"]==party_type and gle["party"] in party_list and gle["against"] and \ - bc_regex.findall(gle["against"]): - val = gle["debit"] - gle["credit"] - total += (gle_field=="debit" and 1 or -1) * val - - return total, self.get_html(label, self.currency, fmt_money(total)) - - def get_invoiced_amount(self): - # aka receivables - return self.get_booked_total("Customer", "debit", self.meta.get_label("invoiced_amount")) - - def get_payables(self): - return self.get_booked_total("Supplier", "credit", self.meta.get_label("payables")) - - def get_booked_total(self, party_type, gle_field, label): - party_list = frappe.db.sql_list("select name from `tab{0}`".format(party_type)) - - total = 0 - for gle in self.get_gl_entries(self.from_date, self.to_date): - if gle["party_type"]==party_type and gle["party"] in party_list: - total += gle[gle_field] - - return total, self.get_html(label, self.currency, fmt_money(total)) - - def get_new_leads(self): - return self.get_new_count("Lead", self.meta.get_label("new_leads")) - - def get_new_enquiries(self): - return self.get_new_count("Opportunity", self.meta.get_label("new_enquiries"), docstatus=1, - date_field="transaction_date") - - def get_new_quotations(self): - return self.get_new_sum("Quotation", self.meta.get_label("new_quotations"), "base_grand_total", - date_field="transaction_date") - - def get_new_sales_orders(self): - return self.get_new_sum("Sales Order", self.meta.get_label("new_sales_orders"), "base_grand_total", - date_field="transaction_date") - - def get_new_delivery_notes(self): - return self.get_new_sum("Delivery Note", self.meta.get_label("new_delivery_notes"), "base_grand_total", - date_field="posting_date") - - def get_new_purchase_requests(self): - return self.get_new_count("Material Request", self.meta.get_label("new_purchase_requests"), docstatus=1, - date_field="transaction_date") - - def get_new_supplier_quotations(self): - return self.get_new_sum("Supplier Quotation", self.meta.get_label("new_supplier_quotations"), - "base_grand_total", date_field="transaction_date") - - def get_new_purchase_orders(self): - return self.get_new_sum("Purchase Order", self.meta.get_label("new_purchase_orders"), - "base_grand_total", date_field="transaction_date") - - def get_new_purchase_receipts(self): - return self.get_new_sum("Purchase Receipt", self.meta.get_label("new_purchase_receipts"), - "base_grand_total", date_field="posting_date") - - def get_new_stock_entries(self): - return self.get_new_sum("Stock Entry", self.meta.get_label("new_stock_entries"), "total_amount", - date_field="posting_date") - - def get_new_support_tickets(self): - return self.get_new_count("Issue", self.meta.get_label("new_support_tickets"), - filter_by_company=False) - - def get_new_communications(self): - return self.get_new_count("Communication", self.meta.get_label("new_communications"), - filter_by_company=False) - - def get_new_projects(self): - return self.get_new_count("Project", self.meta.get_label("new_projects"), - filter_by_company=False) - - def get_calendar_events(self, user_id): + def get_calendar_events(self): + """Get calendar events for given user""" from frappe.desk.doctype.event.event import get_events - events = get_events(self.future_from_date.strftime("%Y-%m-%d"), self.future_to_date.strftime("%Y-%m-%d")) + events = get_events(self.future_from_date.strftime("%Y-%m-%d"), + self.future_to_date.strftime("%Y-%m-%d")) or [] - html = "" - if events: - for i, e in enumerate(events): - if i>=10: - break - if e.all_day: - html += """
  • %s [%s (%s)]
  • """ % \ - (e.subject, datetime_in_user_format(e.starts_on), _("All Day")) - else: - html += "
  • %s [%s - %s]
  • " % \ - (e.subject, datetime_in_user_format(e.starts_on), datetime_in_user_format(e.ends_on)) + for i, e in enumerate(events): + e.starts_on_label = format_time(e.starts_on) + e.ends_on_label = format_time(e.ends_on) + e.date = formatdate(e.starts) + e.link = get_url_to_form("Event", e.name) - if html: - return 1, "

    " + _("Upcoming Calendar Events (max 10)") + ":


    " - else: - return 0, "

    " + _("Calendar Events") + "

    " + return events + + def get_todo_list(self, user_id=None): + """Get to-do list""" + if not user_id: + user_id = frappe.session.user - def get_todo_list(self, user_id): todo_list = frappe.db.sql("""select * from `tabToDo` where (owner=%s or assigned_by=%s) and status="Open" order by field(priority, 'High', 'Medium', 'Low') asc, date asc""", (user_id, user_id), as_dict=True) - html = "" - if todo_list: - for i, todo in enumerate([todo for todo in todo_list if not todo.checked]): - if i>= 10: - break - if not todo.description and todo.reference_type: - todo.description = "%s: %s - %s %s" % \ - (todo.reference_type, get_url_to_form(todo.reference_type, todo.reference_name), - _("assigned by"), get_fullname(todo.assigned_by)) + for t in todo_list: + t.link = get_url_to_form("ToDo", t.name) - html += "
  • %s [%s]
  • " % (todo.description, todo.priority) + return todo_list - if html: - return 1, "

    To Do (max 10):


    " - else: - return 0, "

    To Do

    " + def set_accounting_cards(self, context): + """Create accounting cards if checked""" - def get_new_count(self, doctype, label, docstatus=0, filter_by_company=True, date_field="creation"): - if filter_by_company: - company_condition = """and company="%s" """ % self.company.replace('"', '\"') - else: - company_condition = "" + context.cards = [] + for key in ("income", "expenses_booked", "income_year_to_date", "expense_year_to_date", + "invoiced_amount", "payables", "bank_balance"): + if self.get(key): + card = frappe._dict(getattr(self, "get_" + key)()) - count = frappe.db.sql("""select count(*) from `tab{doctype}` - where ifnull(`docstatus`, 0)=%s {company_condition} and - date(`{date_field}`)>=%s and date({date_field})<=%s""".format(doctype=doctype, - company_condition=company_condition, date_field=date_field), - (docstatus, self.from_date, self.to_date)) + # format values + if card.last_value: + card.diff = int(flt(card.value - card.last_value) / card.last_value * 100) + if card.diff < 0: + card.diff = str(card.diff) + card.gain = False + else: + card.diff = "+" + str(card.diff) + card.gain = True - count = count and count[0][0] or 0 + card.last_value = self.fmt_money(card.last_value) - return count, self.get_html(label, None, count) + card.value = self.fmt_money(card.value) - def get_new_sum(self, doctype, label, sum_field, date_field="creation"): - count_sum = frappe.db.sql("""select count(*), sum(ifnull(`{sum_field}`, 0)) - from `tab{doctype}` where docstatus=1 and company = %s and - date(`{date_field}`)>=%s and date(`{date_field}`)<=%s""".format(sum_field=sum_field, - date_field=date_field, doctype=doctype), (self.company, self.from_date, self.to_date)) + context.cards.append(card) - count, total = count_sum and count_sum[0] or (0, 0) + def get_income(self): + """Get income for given period""" + income, past_income = self.get_period_amounts(self.get_root_type_accounts("income")) - return count, self.get_html(label, self.currency, - "%s - (%s)" % (fmt_money(total), cstr(count))) + return { + "label": self.meta.get_label("income"), + "value": income, + "last_value": past_income + } - def get_html(self, label, currency, value, style=None): - """get html output""" - return row_template % { - "style": style or "", - "label": label, - "currency": currency and (currency+" ") or "", - "value": value - } + def get_income_year_to_date(self): + """Get income to date""" + return self.get_year_to_date_balance("income") - def get_gl_entries(self, from_date=None, to_date=None): - """get valid GL Entries filtered by company and posting date""" - if from_date==self.from_date and to_date==self.to_date and \ - hasattr(self, "gl_entries"): - return self.gl_entries + def get_expense_year_to_date(self): + """Get income to date""" + return self.get_year_to_date_balance("expense") - gl_entries = frappe.db.sql("""select `account`, `party_type`, `party`, - ifnull(credit, 0) as credit, ifnull(debit, 0) as debit, `against` - from `tabGL Entry` - where company=%s - and posting_date <= %s %s""" % ("%s", "%s", - from_date and "and posting_date>='%s'" % from_date or ""), - (self.company, to_date or self.to_date), as_dict=1) + def get_year_to_date_balance(self, root_type): + """Get income to date""" + balance = 0.0 - # cache if it is the normal cases - if from_date==self.from_date and to_date==self.to_date: - self.gl_entries = gl_entries + for account in self.get_root_type_accounts(root_type): + balance += get_balance_on(account, date = self.future_to_date) - return gl_entries + return { + "label": self.meta.get_label(root_type + "_year_to_date"), + "value": balance + } - def get_accounts(self): - if not hasattr(self, "accounts"): - self.accounts = frappe.db.sql("""select name, account_type, account_name, root_type - from `tabAccount` where company=%s and docstatus < 2 - and is_group = 0 order by lft""", - (self.company,), as_dict=1) - return self.accounts + def get_bank_balance(self): + # account is of type "Bank" or "Cash" + return self.get_type_balance('bank_balance', 'Bank') + + def get_payables(self): + return self.get_type_balance('payables', 'Payable') + + def get_invoiced_amount(self): + return self.get_type_balance('invoiced_amount', 'Receivable') + + def get_expenses_booked(self): + expense, past_expense = self.get_period_amounts(self.get_root_type_accounts("expense")) + + return { + "label": self.meta.get_label("expenses_booked"), + "value": expense, + "last_value": past_expense + } + + def get_period_amounts(self, accounts): + """Get amounts for current and past periods""" + balance = past_balance = 0.0 + for account in accounts: + balance += (get_balance_on(account, date = self.future_to_date) + - get_balance_on(account, date = self.future_from_date)) + + past_balance += (get_balance_on(account, date = self.past_to_date) + - get_balance_on(account, date = self.past_from_date)) + + return balance, past_balance + + def get_type_balance(self, fieldname, account_type): + accounts = [d.name for d in \ + frappe.db.get_all("Account", filters={"account_type": account_type, + "company": self.company, "is_group": 0})] + + balance = prev_balance = 0.0 + for account in accounts: + balance += get_balance_on(account, date=self.future_from_date) + prev_balance += get_balance_on(account, date=self.past_from_date) + + return { + 'label': self.meta.get_label(fieldname), + 'value': balance, + 'last_value': prev_balance + } + + def get_root_type_accounts(self, root_type): + if not root_type in self._accounts: + self._accounts[root_type] = [d.name for d in \ + frappe.db.get_all("Account", filters={"root_type": root_type.title(), + "company": self.company, "is_group": 0})] + return self._accounts[root_type] def get_from_to_date(self): today = now_datetime().date() @@ -417,25 +272,33 @@ class EmailDigest(Document): return from_date, to_date - def get_future_from_to_date(self): + def set_dates(self): today = now_datetime().date() # decide from date based on email digest frequency if self.frequency == "Daily": # from date, to_date is today - from_date = to_date = today + self.future_from_date = self.future_to_date = today + self.past_from_date = self.past_to_date = today - relativedelta(days = 1) + elif self.frequency == "Weekly": # from date is the current week's monday - from_date = today - timedelta(days=today.weekday()) + self.future_from_date = today - relativedelta(days=today.weekday()) + # to date is the current week's sunday - to_date = from_date + timedelta(days=6) + self.future_to_date = self.future_from_date + relativedelta(days=6) + + self.past_from_date = self.future_from_date - relativedelta(days=7) + self.past_to_date = self.future_to_date - relativedelta(days=7) else: # from date is the 1st day of the current month - from_date = today - relativedelta(days=today.day-1) - # to date is the last day of the current month - to_date = from_date + relativedelta(days=-1, months=1) + self.future_from_date = today - relativedelta(days=today.day-1) - return from_date, to_date + # to date is the last day of the current month + self.future_to_date = self.future_from_date + relativedelta(days=-1, months=1) + + self.past_from_date = self.future_from_date - relativedelta(month=1) + self.past_to_date = self.future_to_date - relativedelta(month=1) def get_next_sending(self): from_date, to_date = self.get_from_to_date() @@ -452,25 +315,12 @@ class EmailDigest(Document): return send_date - def get_open_tickets(self): - open_tickets = frappe.db.sql("""select name, subject, modified, raised_by - from `tabIssue` where status='Open' - order by modified desc limit 10""", as_dict=True) - - if open_tickets: - return 1, """

    Latest Open Tickets (max 10):

    %s""" % \ - "".join(["

    %(name)s: %(subject)s
    by %(raised_by)s on %(modified)s

    " % \ - t for t in open_tickets]) - else: - return 0, "No Open Tickets!" - - def get_scheduler_errors(self): - import frappe.utils.scheduler - return frappe.utils.scheduler.get_error_report(self.from_date, self.to_date) - def onload(self): self.get_next_sending() + def fmt_money(self, value): + return fmt_money(value, currency = self.currency) + def send(): now_date = now_datetime().date() @@ -479,3 +329,7 @@ def send(): ed_obj = frappe.get_doc('Email Digest', ed[0]) if (now_date == ed_obj.get_next_sending()): ed_obj.send() + +@frappe.whitelist() +def get_digest_msg(name): + return frappe.get_doc("Email Digest", name).get_msg_html() diff --git a/erpnext/setup/doctype/email_digest/templates/default.html b/erpnext/setup/doctype/email_digest/templates/default.html new file mode 100644 index 0000000000..4b377742e5 --- /dev/null +++ b/erpnext/setup/doctype/email_digest/templates/default.html @@ -0,0 +1,113 @@ +{% macro show_card(card) %} +
    +
    {{ card.label }}
    +

    {{ card.value }}

    + {% if card.diff %} +

    {{ card.diff }}%

    + {% endif %} +
    +{% endmacro %} + +
    + +{% if cards %} +

    {{ title }}

    +

    +

    {% if frequency == "Daily "%} + {{ frappe.format_date(future_from_date) }} + {% else %} + {{ frappe.format_date(future_from_date) }} - {{ frappe.format_date(future_to_date) }} + {% endif %}

    +

    + + +
    +{% for card in cards %} +{{ show_card(card) }} +{% endfor %} +
    + +
    +{% endif %} + + +

    {{ _("Pending Activities") }}

    + + +{% if events %} +

    {{ _("Upcoming Events") }}

    +
    +{% for e in events %} + {% if loop.index==1 or events[loop.index-1].date != e.date %} +

    {{ e.date }}

    + {% endif %} +
    + + + + + +
    + {{ e.subject }} + + + {% if e.all_day %} + {{ _("All Day") }} + {% else %} + {{ e.starts_on_label }} - {{ e.ends_on_label }} + {% endif %} + +
    +
    +{% endfor %} +
    +{% endif %} + + +{% if todo_list %} +

    {{ _("To Do List") }}

    +
    +{% for t in todo_list %} +
    + + + + + +
    + {{ t.description }} + + + {{ _(t.status) }} + +
    +
    +{% endfor %} +
    +{% endif %} + + +{% if notifications %} +

    {{ _("Pending Activities") }}

    +
    +{% for n in notifications %} + {% if n.value %} +
    + + + + + +
    + {{ n.key }} + + + {{ n.value }} + +
    +
    + {% endif %} +{% endfor %} +
    +{% endif %} +
    diff --git a/erpnext/setup/doctype/email_digest/test_email_digest.py b/erpnext/setup/doctype/email_digest/test_email_digest.py new file mode 100644 index 0000000000..afe693afd2 --- /dev/null +++ b/erpnext/setup/doctype/email_digest/test_email_digest.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Email Digest') + +class TestEmailDigest(unittest.TestCase): + pass