From 8d43b32c2cb1125765ff44df1783a82922c639a7 Mon Sep 17 00:00:00 2001 From: RobertSchouten Date: Tue, 20 Sep 2016 13:41:39 +0800 Subject: [PATCH] improvements to email digest (#6323) --- erpnext/accounts/utils.py | 77 +++ .../doctype/email_digest/email_digest.json | 486 ++++++++++++++++-- .../doctype/email_digest/email_digest.py | 262 ++++++++-- .../email_digest/templates/default.html | 61 ++- 4 files changed, 816 insertions(+), 70 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9b28cbddb1..eefdc1dfd9 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -127,6 +127,83 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company # if bal is None, return 0 return flt(bal) +def get_count_on(account, fieldname, date): + + cond = [] + if date: + cond.append("posting_date <= '%s'" % frappe.db.escape(cstr(date))) + else: + # get balance of all entries that exist + date = nowdate() + + try: + year_start_date = get_fiscal_year(date, verbose=0)[1] + except FiscalYearError: + if getdate(date) > getdate(nowdate()): + # if fiscal year not found and the date is greater than today + # get fiscal year for today's date and its corresponding year start date + year_start_date = get_fiscal_year(nowdate(), verbose=1)[1] + else: + # this indicates that it is a date older than any existing fiscal year. + # hence, assuming balance as 0.0 + return 0.0 + + if account: + acc = frappe.get_doc("Account", account) + + if not frappe.flags.ignore_account_permission: + acc.check_permission("read") + + # for pl accounts, get balance within a fiscal year + if acc.report_type == 'Profit and Loss': + cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \ + % year_start_date) + + # different filter for group and ledger - improved performance + if acc.is_group: + cond.append("""exists ( + select name from `tabAccount` ac where ac.name = gle.account + and ac.lft >= %s and ac.rgt <= %s + )""" % (acc.lft, acc.rgt)) + + # If group and currency same as company, + # always return balance based on debit and credit in company currency + if acc.account_currency == frappe.db.get_value("Company", acc.company, "default_currency"): + in_account_currency = False + else: + cond.append("""gle.account = "%s" """ % (frappe.db.escape(account, percent=False), )) + + entries = frappe.db.sql(""" + SELECT name, posting_date, account, party_type, party,debit,credit, + voucher_type, voucher_no, against_voucher_type, against_voucher + FROM `tabGL Entry` gle + WHERE {0}""".format(" and ".join(cond)), as_dict=True) + + count = 0 + for gle in entries: + if fieldname not in ('invoiced_amount','payables'): + count += 1 + else: + dr_or_cr = "debit" if fieldname == "invoiced_amount" else "credit" + cr_or_dr = "credit" if fieldname == "invoiced_amount" else "debit" + select_fields = "ifnull(sum(credit-debit),0)" if fieldname == "invoiced_amount" else "ifnull(sum(debit-credit),0)" + + if ((not gle.against_voucher) or (gle.against_voucher_type in ["Sales Order", "Purchase Order"]) or + (gle.against_voucher==gle.voucher_no and gle.get(dr_or_cr) > 0)): + payment_amount = frappe.db.sql(""" + SELECT {0} + FROM `tabGL Entry` gle + WHERE docstatus < 2 and posting_date <= %(date)s and against_voucher = %(voucher_no)s + and party = %(party)s and name != %(name)s""".format(select_fields), + {"date": date, "voucher_no": gle.voucher_no, "party": gle.party, "name": gle.name})[0][0] + + outstanding_amount = flt(gle.get(dr_or_cr)) - flt(gle.get(cr_or_dr)) - payment_amount + currency_precision = get_currency_precision() or 2 + if abs(flt(outstanding_amount)) > 0.1/10**currency_precision: + count += 1 + + return count + @frappe.whitelist() def add_ac(args=None): if not args: diff --git a/erpnext/setup/doctype/email_digest/email_digest.json b/erpnext/setup/doctype/email_digest/email_digest.json index dfc5edb89d..00fac42373 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.json +++ b/erpnext/setup/doctype/email_digest/email_digest.json @@ -264,7 +264,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Income / Expense", + "label": "Profit & Loss", "length": 0, "no_copy": 0, "permlevel": 0, @@ -289,7 +289,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Income", + "label": "New Income", "length": 0, "no_copy": 0, "permlevel": 0, @@ -314,32 +314,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Expense", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 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": "bank_balance", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Bank Balance", + "label": "New Expenses", "length": 0, "no_copy": 0, "permlevel": 0, @@ -387,7 +362,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Annual Expense", + "label": "Annual Expenses", "length": 0, "no_copy": 0, "permlevel": 0, @@ -413,7 +388,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Receivables / Payables", + "label": "Balance Sheet", "length": 0, "no_copy": 0, "permlevel": 0, @@ -426,6 +401,56 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "description": "", + "fieldname": "bank_balance", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Bank Balance", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 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": "credit_balance", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Credit Balance", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -476,6 +501,280 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "operation", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Operations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "column_break_21", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "sales_order", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "New Sales Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "pending_sales_orders", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Pending Sales Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "purchase_order", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "New Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "pending_purchase_orders", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Pending Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "column_break_operation", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "New Quotations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "pending_quotations", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Pending Quotations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "issue", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Open Issues", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "project", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Open Projects", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -501,6 +800,131 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "tools", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Tools", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Upcoming Calendar Events", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Open To Do", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "notifications", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Open Notifications", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 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": "column_break_32", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": " ", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -538,7 +962,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-02-22 09:22:28.877187", + "modified": "2016-06-23 11:38:50.729998", "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 4f70cd8314..7ec04c8b27 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -10,7 +10,7 @@ from datetime import timedelta from dateutil.relativedelta import relativedelta from frappe.core.doctype.user.user import STANDARD_USERS import frappe.desk.notifications -from erpnext.accounts.utils import get_balance_on +from erpnext.accounts.utils import get_balance_on, get_count_on, get_currency_precision user_specific_content = ["calendar_events", "todo_list"] @@ -77,10 +77,20 @@ class EmailDigest(Document): self.set_title(context) self.set_style(context) self.set_accounting_cards(context) - - context.events = self.get_calendar_events() - context.todo_list = self.get_todo_list() - context.notifications = self.get_notifications() + + if self.get("calendar_events"): + context.events, context.event_count = self.get_calendar_events() + if self.get("todo_list"): + context.todo_list = self.get_todo_list() + context.todo_count = self.get_todo_count() + if self.get("notifications"): + context.notifications = self.get_notifications() + if self.get("issue"): + context.issue_list = self.get_issue_list() + context.issue_count = self.get_issue_count() + if self.get("project"): + context.project_list = self.get_project_list() + context.project_count = self.get_project_count() quote = get_random_quote() context.quote = {"text": quote[0], "author": quote[1]} @@ -136,14 +146,16 @@ class EmailDigest(Document): 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")) or [] - + + event_count = 0 for i, e in enumerate(events): e.starts_on_label = format_time(e.starts_on) e.ends_on_label = format_time(e.ends_on) if e.ends_on else None e.date = formatdate(e.starts) e.link = get_url_to_form("Event", e.name) + event_count += 1 - return events + return events, event_count def get_todo_list(self, user_id=None): """Get to-do list""" @@ -159,14 +171,67 @@ class EmailDigest(Document): t.link = get_url_to_form("ToDo", t.name) return todo_list + + def get_todo_count(self, user_id=None): + """Get count of Todo""" + if not user_id: + user_id = frappe.session.user + + return frappe.db.sql("""select count(*) from `tabToDo` + where status='Open' and (owner=%s or assigned_by=%s)""", + (user_id, user_id))[0][0] + + def get_issue_list(self, user_id=None): + """Get issue list""" + if not user_id: + user_id = frappe.session.user + + meta = frappe.get_meta("Issue") + role_permissions = frappe.permissions.get_role_permissions(meta, user_id) + if not role_permissions.get("read"): + return None + + issue_list = frappe.db.sql("""select * + from `tabIssue` where status in ("Replied","Open") + order by modified asc limit 10""", as_dict=True) + + for t in issue_list: + t.link = get_url_to_form("Issue", t.name) + + return issue_list + + def get_issue_count(self): + """Get count of Issue""" + return frappe.db.sql("""select count(*) from `tabIssue` + where status in ('Open','Replied') """)[0][0] + + def get_project_list(self, user_id=None): + """Get project list""" + if not user_id: + user_id = frappe.session.user + + project_list = frappe.db.sql("""select * + from `tabProject` where status='Open' and project_type='External' + order by modified asc limit 10""", as_dict=True) + + for t in project_list: + t.link = get_url_to_form("Issue", t.name) + + return project_list + + def get_project_count(self): + """Get count of Project""" + return frappe.db.sql("""select count(*) from `tabProject` + where status='Open' and project_type='External'""")[0][0] def set_accounting_cards(self, context): """Create accounting cards if checked""" cache = frappe.cache() context.cards = [] - for key in ("income", "expenses_booked", "income_year_to_date", "expense_year_to_date", - "invoiced_amount", "payables", "bank_balance"): + for key in ("income", "expenses_booked", "income_year_to_date","expense_year_to_date", + "new_quotations","pending_quotations","sales_order","purchase_order","pending_sales_orders","pending_purchase_orders", + "invoiced_amount", "payables", "bank_balance", "credit_balance"): if self.get(key): cache_key = "email_digest:card:{0}:{1}".format(self.company, key) card = cache.get(cache_key) @@ -187,9 +252,25 @@ class EmailDigest(Document): card.diff = "+" + str(card.diff) card.gain = True - card.last_value = self.fmt_money(card.last_value) + if key == "credit_balance": + card.last_value = card.last_value * -1 + card.last_value = self.fmt_money(card.last_value,False if key in ("bank_balance", "credit_balance") else True) - card.value = self.fmt_money(card.value) + + if card.billed_value: + card.billed = int(flt(card.billed_value) / card.value * 100) + card.billed = "% Billed " + str(card.billed) + + if card.delivered_value: + card.delivered = int(flt(card.delivered_value) / card.value * 100) + if key == "pending_sales_orders": + card.delivered = "% Delivered " + str(card.delivered) + else: + card.delivered = "% Received " + str(card.delivered) + + if key =="credit_balance": + card.value = card.value *-1 + card.value = self.fmt_money(card.value,False if key in ("bank_balance", "credit_balance") else True) cache.setex(cache_key, card, 24 * 60 * 60) @@ -197,37 +278,45 @@ class EmailDigest(Document): def get_income(self): """Get income for given period""" - income, past_income = self.get_period_amounts(self.get_root_type_accounts("income")) + income, past_income, count = self.get_period_amounts(self.get_root_type_accounts("income"),'income') return { "label": self.meta.get_label("income"), "value": income, - "last_value": past_income + "last_value": past_income, + "count": count } def get_income_year_to_date(self): """Get income to date""" - return self.get_year_to_date_balance("income") + return self.get_year_to_date_balance("income", "income") def get_expense_year_to_date(self): """Get income to date""" - return self.get_year_to_date_balance("expense") + return self.get_year_to_date_balance("expense","expenses_booked") - def get_year_to_date_balance(self, root_type): + def get_year_to_date_balance(self, root_type, fieldname): """Get income to date""" balance = 0.0 + count = 0 for account in self.get_root_type_accounts(root_type): balance += get_balance_on(account, date = self.future_to_date) + count += get_count_on(account, fieldname, date = self.future_to_date) return { "label": self.meta.get_label(root_type + "_year_to_date"), - "value": balance + "value": balance, + "count": count } def get_bank_balance(self): - # account is of type "Bank" or "Cash" - return self.get_type_balance('bank_balance', 'Bank') + # account is of type "Bank" and root_type is Asset + return self.get_type_balance('bank_balance', 'Bank', root_type='Asset') + + def get_credit_balance(self): + # account is of type "Bank" and root_type is Liability + return self.get_type_balance('credit_balance', 'Bank', root_type='Liability') def get_payables(self): return self.get_type_balance('payables', 'Payable') @@ -236,41 +325,62 @@ class EmailDigest(Document): 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")) + expense, past_expense, count = self.get_period_amounts(self.get_root_type_accounts("expense"), 'expenses_booked') return { "label": self.meta.get_label("expenses_booked"), "value": expense, - "last_value": past_expense + "last_value": past_expense, + "count": count } - def get_period_amounts(self, accounts): + def get_period_amounts(self, accounts, fieldname): """Get amounts for current and past periods""" balance = past_balance = 0.0 + count = 0 for account in accounts: balance += (get_balance_on(account, date = self.future_to_date) - get_balance_on(account, date = self.future_from_date)) + count += (get_count_on(account,fieldname, date = self.future_to_date ) + - get_count_on(account,fieldname, 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 + return balance, past_balance, count - def get_type_balance(self, fieldname, account_type): - accounts = [d.name for d in \ - frappe.db.get_all("Account", filters={"account_type": account_type, + def get_type_balance(self, fieldname, account_type, root_type=None): + + if root_type: + accounts = [d.name for d in \ + frappe.db.get_all("Account", filters={"account_type": account_type, + "company": self.company, "is_group": 0, "root_type": root_type})] + else: + 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 + count = 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 - } + balance += get_balance_on(account, date=self.future_to_date) + count += get_count_on(account, fieldname, date=self.future_to_date) + prev_balance += get_balance_on(account, date=self.past_to_date) + + if fieldname in ("bank_balance","credit_balance"): + return { + 'label': self.meta.get_label(fieldname), + 'value': balance, + 'last_value': prev_balance } + else: + return { + 'label': self.meta.get_label(fieldname), + 'value': balance, + 'last_value': prev_balance, + 'count': count + } + def get_root_type_accounts(self, root_type): if not root_type in self._accounts: @@ -279,6 +389,83 @@ class EmailDigest(Document): "company": self.company, "is_group": 0})] return self._accounts[root_type] + def get_purchase_order(self): + + return self.get_summary_of_doc("Purchase Order","purchase_order") + + def get_sales_order(self): + + return self.get_summary_of_doc("Sales Order","sales_order") + + def get_pending_purchase_orders(self): + + return self.get_summary_of_pending("Purchase Order","pending_purchase_orders","per_received") + + def get_pending_sales_orders(self): + + return self.get_summary_of_pending("Sales Order","pending_sales_orders","per_delivered") + + def get_new_quotations(self): + + return self.get_summary_of_doc("Quotation","new_quotations") + + def get_pending_quotations(self): + + return self.get_summary_of_pending_quotations("pending_quotations") + + def get_summary_of_pending(self, doc_type, fieldname, getfield): + + value, count, billed_value, delivered_value = frappe.db.sql("""select ifnull(sum(grand_total),0), count(*), + ifnull(sum(grand_total*per_billed/100),0), ifnull(sum(grand_total*{0}/100),0) from `tab{1}` + where (transaction_date <= %(to_date)s) + and status not in ('Closed','Cancelled', 'Completed') """.format(getfield, doc_type), + {"to_date": self.future_to_date})[0] + + return { + "label": self.meta.get_label(fieldname), + "value": value, + "billed_value": billed_value, + "delivered_value": delivered_value, + "count": count + } + + def get_summary_of_pending_quotations(self, fieldname): + + value, count = frappe.db.sql("""select ifnull(sum(grand_total),0), count(*) from `tabQuotation` + where (transaction_date <= %(to_date)s) + and status not in ('Ordered','Cancelled', 'Lost') """,{"to_date": self.future_to_date})[0] + + last_value = frappe.db.sql("""select ifnull(sum(grand_total),0) from `tabQuotation` + where (transaction_date <= %(to_date)s) + and status not in ('Ordered','Cancelled', 'Lost') """,{"to_date": self.past_to_date})[0][0] + + return { + "label": self.meta.get_label(fieldname), + "value": value, + "last_value": last_value, + "count": count + } + + def get_summary_of_doc(self, doc_type, fieldname): + + value = self.get_total_on(doc_type, self.future_from_date, self.future_to_date)[0] + count = self.get_total_on(doc_type, self.future_from_date, self.future_to_date)[1] + + last_value =self.get_total_on(doc_type, self.past_from_date, self.past_to_date)[0] + + return { + "label": self.meta.get_label(fieldname), + "value": value, + "last_value": last_value, + "count": count + } + + def get_total_on(self, doc_type, from_date, to_date): + + return frappe.db.sql("""select ifnull(sum(grand_total),0), count(*) from `tab{0}` + where (transaction_date between %(from_date)s and %(to_date)s) and status not in ('Cancelled')""".format(doc_type), + {"from_date": from_date, "to_date": to_date})[0] + def get_from_to_date(self): today = now_datetime().date() @@ -332,8 +519,11 @@ class EmailDigest(Document): def onload(self): self.get_next_sending() - def fmt_money(self, value): - return fmt_money(abs(value), currency = self.currency) + def fmt_money(self, value,absol=True): + if absol: + return fmt_money(abs(value), currency = self.currency) + else: + return fmt_money(value, currency=self.currency) def send(): now_date = now_datetime().date() diff --git a/erpnext/setup/doctype/email_digest/templates/default.html b/erpnext/setup/doctype/email_digest/templates/default.html index 7ed9d1faaf..0500dc1483 100644 --- a/erpnext/setup/doctype/email_digest/templates/default.html +++ b/erpnext/setup/doctype/email_digest/templates/default.html @@ -1,10 +1,20 @@ {% macro show_card(card) %}
-
{{ card.label }}
+
{{ card.label }} + {% if card.count %} + ({{ card.count }}) + {% endif %}

{{ card.value }}

{% if card.diff %}

{{ card.diff }}%

{% endif %} + {% if card.billed %} +

{{ card.billed }}%

+ {% endif %} + {% if card.delivered %} +

{{ card.delivered }}%

+ {% endif %} +
{% endmacro %} @@ -31,13 +41,58 @@
{% endif %} + +{% if issue_list %} +

{{ _("Open Issues ") }}({{ issue_count }})

+
+{% for t in issue_list %} +
+ + + + + +
+ {{ _(t.subject) }} + + + {{ _(t.status) }} + +
+
+{% endfor %} +
+{% endif %} + + +{% if project_list %} +

{{ _("Open Projects ") }}({{ project_count }})

+
+{% for t in project_list %} +
+ + + + + +
+ {{ _(t.project_name) }} + + + {{ _(t.status) }} + +
+
+{% endfor %} +
+{% endif %} {% if events or todo_list or notifications %}

{{ _("Pending Activities") }}

{% if events %} -

{{ _("Upcoming Events") }}

+

{{ _("Upcoming Calendar Events ") }}({{ event_count }})

{% for e in events %} {% if loop.index==1 or events[loop.index-1].date != e.date %} @@ -69,7 +124,7 @@ {% if todo_list %} -

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

+

{{ _("Open To Do ") }}({{ todo_count }})

{% for t in todo_list %}