diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1cede9047f..3ebe6f0095 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -194,7 +194,8 @@ scheduler_events = { "erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year", "erpnext.hr.doctype.employee.employee.send_birthday_reminders", "erpnext.projects.doctype.task.task.set_tasks_as_overdue", - "erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries" + "erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries", + 'erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary' ] } diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.json b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.json index d4de3b2e7c..43cef68203 100644 --- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.json +++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.json @@ -22,6 +22,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Company", @@ -52,12 +53,42 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Status", "length": 0, "no_copy": 0, - "options": "Open\nSummary Sent", + "options": "Open\nSent", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "email_sent_to", + "fieldtype": "Code", + "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": "Email Sent To", + "length": 0, + "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, @@ -81,7 +112,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-10 16:09:11.619822", + "modified": "2016-11-18 12:09:01.580414", "modified_by": "Administrator", "module": "HR", "name": "Daily Work Summary", diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py index 45b0c9a2fe..fe5fd8e561 100644 --- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py @@ -8,24 +8,30 @@ from frappe.model.document import Document from frappe import _ from email_reply_parser import EmailReplyParser from erpnext.hr.doctype.employee.employee import is_holiday +from frappe.utils import formatdate class DailyWorkSummary(Document): def send_mails(self, settings, emails): '''Send emails to get daily work summary to all employees''' incoming_email_account = frappe.db.get_value('Email Account', dict(enable_incoming=1, default_incoming=1), 'email_id') + + self.db_set('email_sent_to', '\n'.join(emails)) frappe.sendmail(recipients = emails, message = settings.message, subject = settings.subject, reference_doctype=self.doctype, reference_name=self.name, reply_to = incoming_email_account) def send_summary(self): - '''Send summary of all replies''' + '''Send summary of all replies. Called at midnight''' message = self.get_summary_message() - frappe.sendmail(recipients = get_employee_emails(self.company, False), message = message, + frappe.sendmail(recipients = get_employee_emails(self.company, False), + message = message, subject = _('Daily Work Summary for {0}').format(self.company), reference_doctype=self.doctype, reference_name=self.name) + self.db_set('status', 'Sent') + def get_summary_message(self): '''Return summary of replies as HTML''' settings = frappe.get_doc('Daily Work Summary Settings') @@ -34,15 +40,42 @@ class DailyWorkSummary(Document): filters=dict(reference_doctype=self.doctype, reference_name=self.name, communication_type='Communication', sent_or_received='Received')) - if not replies: - return None + did_not_reply = self.email_sent_to.split() for d in replies: + if d.sender in did_not_reply: + did_not_reply.remove(d.sender) if d.text_content: d.content = EmailReplyParser.parse_reply(d.text_content) - return frappe.render_template(template, dict(replies=replies, - original_message=settings.message)) + + did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email) + for email in did_not_reply] + + return frappe.render_template(self.get_summary_template(), + dict(replies=replies, + original_message=settings.message, + title=_('Daily Work Summary for {0}'.format(formatdate(self.creation))), + did_not_reply= ', '.join(did_not_reply) or '', + did_not_reply_title = _('No replies from'))) + + def get_summary_template(self): + return ''' +

{{ title }}

+ +{% for reply in replies %} +
{{ frappe.db.get_value("Employee", {"user_id": reply.sender}, "employee_name") or reply.sender }}
+

+ {{ reply.content }} +

+{% endfor %} + +{% if did_not_reply %} +
+

{{ did_not_reply_title }}: {{ did_not_reply }}

+{% endif %} + +''' def get_employee_emails(company, only_working=True): '''Returns list of Employee user ids for the given company who are working today @@ -62,13 +95,3 @@ def get_employee_emails(company, only_working=True): return out -template = ''' -

Summary of replies:

- -{% for reply in replies %} -
{{ frappe.db.get_value("Employee", {"user_id": reply.sender}, "employee_name") or reply.sender }}
-
- {{ reply.content }} -
-{% endfor %} -''' \ No newline at end of file diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py index 12b3e2f072..b8e70e2431 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py @@ -3,6 +3,7 @@ # See license.txt from __future__ import unicode_literals +import os import frappe import unittest import frappe.utils @@ -11,7 +12,7 @@ import frappe.utils class TestDailyWorkSummary(unittest.TestCase): def test_email_trigger(self): - settings, employees, emails = self.setup_and_prepare_test(frappe.utils.nowtime().split(':')[0]) + settings, employees, emails = self.setup_and_prepare_test() for d in employees: # check that email is sent to this employee @@ -30,11 +31,32 @@ class TestDailyWorkSummary(unittest.TestCase): self.assertFalse(d.user_id in [d.recipient for d in emails if settings.subject in d.message]) - def test_summary(self): - pass + def test_incoming(self): + settings, employees, emails = self.setup_and_prepare_test() - def setup_and_prepare_test(self, hour): + # get test mail with message-id as in-reply-to + with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f: + test_mails = [f.read().replace('{{ sender }}', employees[-1].user_id)\ + .replace('{{ message_id }}', emails[-1].message_id)] + + # pull the mail + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.db_set('enable_incoming', 1) + email_account.receive(test_mails=test_mails) + + daily_work_summary = frappe.get_doc('Daily Work Summary', + frappe.get_all('Daily Work Summary')[0].name) + + summary = daily_work_summary.get_summary_message() + + self.assertTrue('I built Daily Work Summary!' in summary) + + def setup_and_prepare_test(self, hour=None): + if not hour: + hour = frappe.utils.nowtime().split(':')[0] + frappe.db.sql('delete from `tabDaily Work Summary`') frappe.db.sql('delete from `tabEmail Queue`') + frappe.db.sql('delete from `tabCommunication`') # setup email to trigger at this our settings = frappe.get_doc('Daily Work Summary Settings') @@ -53,6 +75,6 @@ class TestDailyWorkSummary(unittest.TestCase): employees = frappe.get_all('Employee', fields = ['user_id'], filters=dict(company='_Test Company', status='Active')) - emails = frappe.get_all('Email Queue', fields=['recipient', 'message']) + emails = frappe.get_all('Email Queue', fields=['recipient', 'message', 'message_id']) return settings, employees, emails \ No newline at end of file diff --git a/erpnext/hr/doctype/daily_work_summary/test_data/test-reply.raw b/erpnext/hr/doctype/daily_work_summary/test_data/test-reply.raw new file mode 100644 index 0000000000..ba01bc2827 --- /dev/null +++ b/erpnext/hr/doctype/daily_work_summary/test_data/test-reply.raw @@ -0,0 +1,75 @@ +From: {{ sender }} +Content-Type: multipart/alternative; + boundary="Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361" +Message-Id: <07D687F6-10AA-4B9F-82DE-27753096164E@gmail.com> +Mime-Version: 1.0 (Mac OS X Mail 9.3 \(3124\)) +X-Smtp-Server: 73CC8281-7E8F-4B47-8324-D5DA86EEDD4F +Subject: Re: What did you work on today? +Date: Thu, 10 Nov 2016 16:04:43 +0530 +X-Universally-Unique-Identifier: A4D9669F-179C-42D8-A3D3-AA6A8C49A6F2 +References: <{{ message_id }}> +To: test_in@iwebnotes.com +In-Reply-To: <{{ message_id }}> + + +--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=us-ascii + +I built Daily Work Summary! + +> On 10-Nov-2016, at 3:20 PM, Frappe wrote: +>=20 +> Please share what did you do today. If you reply by midnight, your = +response will be recorded! +>=20 +> This email was sent to rmehta@gmail.com +> Unsubscribe from this list = + +> Sent via ERPNext + + +--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361 +Content-Transfer-Encoding: 7bit +Content-Type: text/html; + charset=us-ascii + +I built Daily Work Summary!

On 10-Nov-2016, at 3:20 PM, Frappe <test@erpnext.com> wrote:

+ + + + +What did you work on today? + +
+ +

Please share what did you do today. If you reply by midnight, your response will be recorded!

+ +
+ + + + + + +
+

+--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361-- diff --git a/erpnext/hr/doctype/daily_work_summary_settings/daily_work_summary_settings.py b/erpnext/hr/doctype/daily_work_summary_settings/daily_work_summary_settings.py index 259b976bb5..aea4c354ed 100644 --- a/erpnext/hr/doctype/daily_work_summary_settings/daily_work_summary_settings.py +++ b/erpnext/hr/doctype/daily_work_summary_settings/daily_work_summary_settings.py @@ -12,16 +12,26 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_employe class DailyWorkSummarySettings(Document): def validate(self): if self.companies: - if not frappe.db.get_value('Email Account', dict(enable_incoming=1, default_incoming=1)): + if not frappe.flags.in_test and not frappe.db.get_value('Email Account', dict(enable_incoming=1, + default_incoming=1)): frappe.throw(_('There must be a default incoming Email Account enabled for this to work. Please setup a default incoming Email Account (POP/IMAP) and try again.')) def trigger_emails(): + '''Send emails to Employees of the enabled companies at the give hour asking + them what did they work on today''' settings = frappe.get_doc('Daily Work Summary Settings') for d in settings.companies: # if current hour if frappe.utils.nowtime().split(':')[0] == d.send_emails_at.split(':')[0]: emails = get_employee_emails(d.company) + # find emails relating to a company if emails: - work_summary = frappe.get_doc(dict(doctype='Daily Work Summary', + daily_work_summary = frappe.get_doc(dict(doctype='Daily Work Summary', company=d.company)).insert() - work_summary.send_mails(settings, emails) \ No newline at end of file + daily_work_summary.send_mails(settings, emails) + +def send_summary(): + '''Send summary to everyone''' + for d in frappe.get_all('Daily Work Summary', dict(status='Open')): + daily_work_summary = frappe.get_doc('Daily Work Summary', d.name) + daily_work_summary.send_summary() \ No newline at end of file