[fix] fixes birthday reminders #2021

This commit is contained in:
Rushabh Mehta 2015-01-23 15:22:13 +05:30
parent 88a1515b46
commit 5bceebc6a1
14 changed files with 270 additions and 248 deletions

View File

@ -1,244 +1,244 @@
{ {
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-01-10 16:34:11", "creation": "2013-01-10 16:34:11",
"description": "Supplier of Goods or Services.", "description": "Supplier of Goods or Services.",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Master",
"fields": [ "fields": [
{ {
"fieldname": "basic_info", "fieldname": "basic_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Basic Info", "label": "Basic Info",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "icon-user", "options": "icon-user",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "SUPP-", "options": "SUPP-",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "supplier_name", "fieldname": "supplier_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 0, "in_list_view": 0,
"label": "Supplier Name", "label": "Supplier Name",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "supplier_name", "oldfieldname": "supplier_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"width": "50%" "width": "50%"
}, },
{ {
"fieldname": "supplier_type", "fieldname": "supplier_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Supplier Type", "label": "Supplier Type",
"oldfieldname": "supplier_type", "oldfieldname": "supplier_type",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Supplier Type", "options": "Supplier Type",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "address_contacts", "fieldname": "address_contacts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Address & Contacts", "label": "Address & Contacts",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"options": "icon-map-marker", "options": "icon-map-marker",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "address_html", "fieldname": "address_html",
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Address HTML", "label": "Address HTML",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"width": "50%" "width": "50%"
}, },
{ {
"fieldname": "contact_html", "fieldname": "contact_html",
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Contact HTML", "label": "Contact HTML",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "default_payable_accounts", "fieldname": "default_payable_accounts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Default Payable Accounts", "label": "Default Payable Accounts",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "accounts", "fieldname": "accounts",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Accounts", "label": "Accounts",
"options": "Party Account", "options": "Party Account",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "More Info", "label": "More Info",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "icon-file-text", "options": "icon-file-text",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Default Currency", "label": "Default Currency",
"no_copy": 1, "no_copy": 1,
"options": "Currency", "options": "Currency",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "default_price_list", "fieldname": "default_price_list",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Price List", "label": "Price List",
"options": "Price List", "options": "Price List",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "default_taxes_and_charges", "fieldname": "default_taxes_and_charges",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Taxes and Charges", "label": "Taxes and Charges",
"options": "Purchase Taxes and Charges Master", "options": "Purchase Taxes and Charges Master",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "credit_days", "fieldname": "credit_days",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Credit Days", "label": "Credit Days",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "column_break2", "fieldname": "column_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0, "permlevel": 0,
"width": "50%" "width": "50%"
}, },
{ {
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website", "label": "Website",
"oldfieldname": "website", "oldfieldname": "website",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0 "permlevel": 0
}, },
{ {
"description": "Statutory info and other general information about your Supplier", "description": "Statutory info and other general information about your Supplier",
"fieldname": "supplier_details", "fieldname": "supplier_details",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Supplier Details", "label": "Supplier Details",
"oldfieldname": "supplier_details", "oldfieldname": "supplier_details",
"oldfieldtype": "Code", "oldfieldtype": "Code",
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "communications", "fieldname": "communications",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 1, "hidden": 1,
"label": "Communications", "label": "Communications",
"options": "Communication", "options": "Communication",
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
} }
], ],
"icon": "icon-user", "icon": "icon-user",
"idx": 1, "idx": 1,
"modified": "2015-01-06 17:32:39.936580", "modified": "2015-01-20 17:35:41.423211",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Purchase User" "role": "Purchase User"
}, },
{ {
"amend": 0, "amend": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Purchase Manager", "role": "Purchase Manager",
"submit": 0, "submit": 0,
"write": 0 "write": 0
}, },
{ {
"amend": 0, "amend": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Purchase Master Manager", "role": "Purchase Master Manager",
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"apply_user_permissions": 1, "apply_user_permissions": 1,
"permlevel": 0, "permlevel": 0,
"read": 1, "read": 1,
"role": "Material User" "role": "Material User"
}, },
{ {
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Material Manager" "role": "Material Manager"
}, },
{ {
"apply_user_permissions": 1, "apply_user_permissions": 1,
"permlevel": 0, "permlevel": 0,
"read": 1, "read": 1,
"role": "Accounts User" "role": "Accounts User"
}, },
{ {
"email": 1, "email": 1,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager" "role": "Accounts Manager"
} }
], ],
"search_fields": "supplier_name,supplier_type", "search_fields": "supplier_name, supplier_type",
"title_field": "supplier_name" "title_field": "supplier_name"
} }

View File

@ -55,7 +55,8 @@ scheduler_events = {
"erpnext.stock.reorder_item.reorder_item", "erpnext.stock.reorder_item.reorder_item",
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.support.doctype.issue.issue.auto_close_tickets", "erpnext.support.doctype.issue.issue.auto_close_tickets",
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year" "erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
"erpnext.hr.doctype.employee.employee.send_birthday_reminders"
], ],
"daily_long": [ "daily_long": [
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_daily" "erpnext.setup.doctype.backup_manager.backup_manager.take_backups_daily"

View File

@ -51,8 +51,6 @@ class Employee(Document):
self.update_user() self.update_user()
self.update_user_permissions() self.update_user_permissions()
self.update_dob_event()
def update_user_permissions(self): def update_user_permissions(self):
frappe.permissions.add_user_permission("Employee", self.name, self.user_id) frappe.permissions.add_user_permission("Employee", self.name, self.user_id)
frappe.permissions.set_user_permission_if_allowed("Company", self.company, self.user_id) frappe.permissions.set_user_permission_if_allowed("Company", self.company, self.user_id)
@ -136,8 +134,6 @@ class Employee(Document):
throw(_("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0])) throw(_("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]))
def validate_employee_leave_approver(self): def validate_employee_leave_approver(self):
from erpnext.hr.doctype.leave_application.leave_application import InvalidLeaveApproverError
for l in self.get("leave_approvers")[:]: for l in self.get("leave_approvers")[:]:
if "Leave Approver" not in frappe.get_roles(l.leave_approver): if "Leave Approver" not in frappe.get_roles(l.leave_approver):
self.get("leave_approvers").remove(l) self.get("leave_approvers").remove(l)
@ -147,39 +143,6 @@ class Employee(Document):
if self.reports_to == self.name: if self.reports_to == self.name:
throw(_("Employee cannot report to himself.")) throw(_("Employee cannot report to himself."))
def update_dob_event(self):
if self.status == "Active" and self.date_of_birth \
and not cint(frappe.db.get_value("HR Settings", None, "stop_birthday_reminders")):
birthday_event = frappe.db.sql("""select name from `tabEvent` where repeat_on='Every Year'
and ref_type='Employee' and ref_name=%s""", self.name)
starts_on = self.date_of_birth + " 00:00:00"
ends_on = self.date_of_birth + " 00:15:00"
if birthday_event:
event = frappe.get_doc("Event", birthday_event[0][0])
event.starts_on = starts_on
event.ends_on = ends_on
event.save()
else:
frappe.get_doc({
"doctype": "Event",
"subject": _("Birthday") + ": " + self.employee_name,
"description": _("Happy Birthday!") + " " + self.employee_name,
"starts_on": starts_on,
"ends_on": ends_on,
"event_type": "Public",
"all_day": 1,
"send_reminder": 1,
"repeat_this_event": 1,
"repeat_on": "Every Year",
"ref_type": "Employee",
"ref_name": self.name
}).insert()
else:
frappe.db.sql("""delete from `tabEvent` where repeat_on='Every Year' and
ref_type='Employee' and ref_name=%s""", self.name)
def on_trash(self): def on_trash(self):
delete_events(self.doctype, self.name) delete_events(self.doctype, self.name)
@ -217,3 +180,31 @@ def update_user_permissions(doc, method):
if "Employee" in [d.role for d in doc.get("user_roles")]: if "Employee" in [d.role for d in doc.get("user_roles")]:
employee = frappe.get_doc("Employee", {"user_id": doc.name}) employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions() employee.update_user_permissions()
def send_birthday_reminders():
"""Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set."""
if int(frappe.db.get_single_value("HR Settings", "stop_birthday_reminders") or 0):
return
from frappe.utils.user import get_enabled_system_users
users = None
birthdays = get_employees_who_are_born_today()
if birthdays:
if not users:
users = [u.email_id or u.name for u in get_enabled_system_users()]
for e in birthdays:
frappe.sendmail(recipients=filter(lambda u: u not in (e.company_email, e.personal_email), users),
subject=_("Birthday Reminder for {0}").format(e.employee_name),
message=_("""Today is {0}'s birthday!""").format(e.employee_name),
reply_to=e.company_email or e.personal_email,
bulk=True)
def get_employees_who_are_born_today():
"""Get Employee properties whose birthday is today."""
return frappe.db.sql("""select name, personal_email, company_email, employee_name
from tabEmployee where day(date_of_birth) = day(curdate())
and month(date_of_birth) = month(curdate())
and status = 'Active'""", as_dict=True)

View File

@ -3,4 +3,31 @@
import frappe import frappe
test_records = frappe.get_test_records('Employee') import unittest
import frappe.utils
test_records = frappe.get_test_records('Employee')
class TestEmployee(unittest.TestCase):
def test_birthday_reminders(self):
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
employee.date_of_birth = "1990" + frappe.utils.nowdate()[4:]
employee.company_email = "test@example.com"
employee.save()
from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders
self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()])
frappe.db.sql("delete from `tabBulk Email`")
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.stop_birthday_reminders = 0
hr_settings.save()
send_birthday_reminders()
bulk_mails = frappe.db.sql("""select * from `tabBulk Email`""", as_dict=True)
self.assertTrue("Subject: Birthday Reminder for {0}".format(employee.employee_name) \
in bulk_mails[0].message)

View File

@ -6,30 +6,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint
from frappe.model.document import Document from frappe.model.document import Document
class HRSettings(Document): class HRSettings(Document):
def validate(self): def validate(self):
self.update_birthday_reminders()
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number", set_by_naming_series("Employee", "employee_number",
self.get("emp_created_by")=="Naming Series", hide_name_field=True) self.get("emp_created_by")=="Naming Series", hide_name_field=True)
def update_birthday_reminders(self):
original_stop_birthday_reminders = cint(frappe.db.get_value("HR Settings",
None, "stop_birthday_reminders"))
# reset birthday reminders
if cint(self.stop_birthday_reminders) != original_stop_birthday_reminders:
frappe.db.sql("""delete from `tabEvent` where repeat_on='Every Year' and ref_type='Employee'""")
if not self.stop_birthday_reminders:
for employee in frappe.db.sql_list("""select name from `tabEmployee` where status='Active' and
ifnull(date_of_birth, '')!=''"""):
frappe.get_doc("Employee", employee).update_dob_event()
frappe.msgprint(frappe._("Updated Birthday Reminders"))

View File

@ -11,3 +11,7 @@ from frappe.utils import extract_email_id
class JobApplicant(Document): class JobApplicant(Document):
def validate(self): def validate(self):
self.set_status() self.set_status()
def set_sender(self, sender):
"""Will be called by **Communication** when a Job Application is created from an incoming email."""
self.email_id = sender

View File

@ -104,3 +104,4 @@ erpnext.patches.v4_1.fix_jv_remarks
erpnext.patches.v5_0.recalculate_total_amount_in_jv erpnext.patches.v5_0.recalculate_total_amount_in_jv
erpnext.patches.v5_0.remove_shopping_cart_app erpnext.patches.v5_0.remove_shopping_cart_app
erpnext.patches.v5_0.update_companywise_payment_account erpnext.patches.v5_0.update_companywise_payment_account
erpnext.patches.v5_0.remove_birthday_events

View File

@ -0,0 +1,6 @@
import frappe
def execute():
for e in frappe.db.sql_list("""select name from tabEvent where
repeat_on='Every Year' and ref_type='Employee'"""):
frappe.delete_doc("Event", e, force=True)

View File

@ -15,6 +15,10 @@ class Lead(SellingController):
def get_feed(self): def get_feed(self):
return '{0}: {1}'.format(_(self.status), self.lead_name) return '{0}: {1}'.format(_(self.status), self.lead_name)
def set_sender(self, sender):
"""Will be called by **Communication** when a Lead is created from an incoming email."""
self.email_id = sender
def onload(self): def onload(self):
customer = frappe.db.get_value("Customer", {"lead_name": self.name}) customer = frappe.db.get_value("Customer", {"lead_name": self.name})
self.get("__onload").is_customer = customer self.get("__onload").is_customer = customer

View File

@ -11,7 +11,7 @@ class SalesPerson(NestedSet):
nsm_parent_field = 'parent_sales_person'; nsm_parent_field = 'parent_sales_person';
def validate(self): def validate(self):
for d in self.get('targets'): for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount): if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory.")) frappe.throw(_("Either target qty or target amount is mandatory."))

View File

@ -14,7 +14,7 @@ class Territory(NestedSet):
nsm_parent_field = 'parent_territory' nsm_parent_field = 'parent_territory'
def validate(self): def validate(self):
for d in self.get('targets'): for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount): if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory")) frappe.throw(_("Either target qty or target amount is mandatory"))

View File

@ -187,7 +187,11 @@ def install(country=None):
{'doctype': 'Industry Type', 'industry': _('Telecommunications')}, {'doctype': 'Industry Type', 'industry': _('Telecommunications')},
{'doctype': 'Industry Type', 'industry': _('Television')}, {'doctype': 'Industry Type', 'industry': _('Television')},
{'doctype': 'Industry Type', 'industry': _('Transportation')}, {'doctype': 'Industry Type', 'industry': _('Transportation')},
{'doctype': 'Industry Type', 'industry': _('Venture Capital')} {'doctype': 'Industry Type', 'industry': _('Venture Capital')},
{'doctype': "Email Account", "email_id": "sales@example.com", "append_to": "Lead"},
{'doctype': "Email Account", "email_id": "support@example.com", "append_to": "Issue"},
{'doctype': "Email Account", "email_id": "jobs@example.com", "append_to": "Job Applicant"}
] ]
from frappe.modules import scrub from frappe.modules import scrub

View File

@ -15,6 +15,10 @@ class Issue(Document):
def get_portal_page(self): def get_portal_page(self):
return "ticket" return "ticket"
def set_sender(self, sender):
"""Will be called by **Communication** when the Issue is created from an incoming email."""
self.raised_by = sender
def validate(self): def validate(self):
self.update_status() self.update_status()
self.set_lead_contact(self.raised_by) self.set_lead_contact(self.raised_by)

View File

@ -17,7 +17,7 @@
{% endfor %} {% endfor %}
</div> </div>
{% if (items|length)==100 %} {% if (items|length)==100 %}
<div class="alert alert-info info">Showing top 100 items.</div> <div class="text-muted info">Showing top 100 items.</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-muted">No items listed.</div> <div class="text-muted">No items listed.</div>