diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index a2b56fcb82a..db580f4d157 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -227,3 +227,16 @@ def get_employees_who_are_born_today(): from tabEmployee where day(date_of_birth) = day(%(date)s) and month(date_of_birth) = month(%(date)s) and status = 'Active'""", {"date": today()}, as_dict=True) + +def get_holiday_list_for_employee(employee, raise_exception=True): + employee = frappe.db.get_value("Employee", employee, ["holiday_list", "company"], as_dict=True) + holiday_list = employee.holiday_list + + if not holiday_list: + holiday_list = frappe.db.get_value("Company", employee.company, "default_holiday_list") + + if not holiday_list and raise_exception: + frappe.throw(_("Please set a Holiday List for either the Employee or the Company")) + + return holiday_list + diff --git a/erpnext/hr/doctype/holiday/holiday.json b/erpnext/hr/doctype/holiday/holiday.json index 091dd13ef1e..131215ca69a 100644 --- a/erpnext/hr/doctype/holiday/holiday.json +++ b/erpnext/hr/doctype/holiday/holiday.json @@ -6,32 +6,8 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", + "document_type": "Setup", "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "300px" - }, { "allow_on_submit": 0, "bold": 0, @@ -40,6 +16,7 @@ "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Date", @@ -56,6 +33,32 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "300px", + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "300px" } ], "hide_heading": 0, @@ -67,7 +70,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-01-27 11:52:46.864792", + "modified": "2016-03-11 06:39:10.913467", "modified_by": "Administrator", "module": "HR", "name": "Holiday", diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.js b/erpnext/hr/doctype/holiday_list/holiday_list.js new file mode 100644 index 00000000000..43ddfea493c --- /dev/null +++ b/erpnext/hr/doctype/holiday_list/holiday_list.js @@ -0,0 +1,14 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Holiday List', { + refresh: function(frm) { + + }, + from_date: function(frm) { + if (frm.doc.from_date && !frm.doc.to_date) { + var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12); + frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1)); + } + } +}); diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.json b/erpnext/hr/doctype/holiday_list/holiday_list.json index ed6bea47818..ea416543bdd 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.json +++ b/erpnext/hr/doctype/holiday_list/holiday_list.json @@ -35,30 +35,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Default", - "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, @@ -222,7 +198,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-03-07 05:54:39.627872", + "modified": "2016-03-11 05:26:24.819829", "modified_by": "Administrator", "module": "HR", "name": "Holiday List", diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 309e7e0f533..0412624d391 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -3,8 +3,8 @@ from __future__ import unicode_literals import frappe - -from frappe.utils import cint, getdate +import json +from frappe.utils import cint, getdate, formatdate from frappe import throw, _ from frappe.model.document import Document @@ -12,8 +12,6 @@ class OverlapError(frappe.ValidationError): pass class HolidayList(Document): def validate(self): - self.update_default_holiday_list() - self.validate_time_period() self.validate_days() def get_weekly_off_dates(self): @@ -32,31 +30,12 @@ class HolidayList(Document): def validate_days(self): - for day in self.get("holidays"): - if not self.from_date <= day.holiday_date <= self.to_date: - frappe.throw("Date not between From Date and To Date") - - def validate_time_period(self): if self.from_date > self.to_date: throw(_("To Date cannot be before From Date")) - existing = frappe.db.sql("""select holiday_list_name, from_date, to_date from `tabHoliday List` - where - ( - (%(from_date)s > from_date and %(from_date)s < to_date) or - (%(to_date)s > from_date and %(to_date)s < to_date) or - (%(from_date)s <= from_date and %(to_date)s >= to_date)) - and name!=%(name)s""", - { - "from_date": self.from_date, - "to_date": self.to_date, - "name": self.holiday_list_name - }, as_dict=True) - - if existing: - frappe.throw(_("This Time Period conflicts with {0} ({1} to {2})").format(existing[0].holiday_list_name, - existing[0].from_date, existing[0].to_date, OverlapError)) - + for day in self.get("holidays"): + if not (self.from_date <= day.holiday_date <= self.to_date): + frappe.throw(_("The holiday on {0} is not between From Date and To Date").format(formatdate(day.holiday_date))) def get_weekly_off_date_list(self, start_date, end_date): start_date, end_date = getdate(start_date), getdate(end_date) @@ -69,7 +48,7 @@ class HolidayList(Document): existing_date_list = [] weekday = getattr(calendar, (self.weekly_off).upper()) reference_date = start_date + relativedelta.relativedelta(weekday=weekday) - + existing_date_list = [getdate(holiday.holiday_date) for holiday in self.get("holidays")] while reference_date <= end_date: @@ -82,10 +61,6 @@ class HolidayList(Document): def clear_table(self): self.set('holidays', []) - def update_default_holiday_list(self): - frappe.db.sql("""update `tabHoliday List` set is_default = 0 - where is_default = 1""") - @frappe.whitelist() def get_events(start, end, filters=None): """Returns events for Gantt / Calendar view rendering. @@ -94,10 +69,27 @@ def get_events(start, end, filters=None): :param end: End date-time. :param filters: Filters (JSON). """ + condition = '' + values = { + "start_date": getdate(start), + "end_date": getdate(end) + } + + if filters: + if isinstance(filters, basestring): + filters = json.loads(filters) + + if filters.get('holiday_list'): + condition = 'and hlist.name=%(holiday_list)s' + values['holiday_list'] = filters['holiday_list'] + + data = frappe.db.sql("""select hlist.name, h.holiday_date, h.description + from `tabHoliday List` hlist, tabHoliday h + where h.parent = hlist.name + and h.holiday_date is not null + and h.holiday_date >= %(start_date)s + and h.holiday_date <= %(end_date)s + {condition}""".format(condition=condition), + values, as_dict=True, update={"allDay": 1}) - data = frappe.db.sql("""select hl.name, hld.holiday_date, hld.description - from `tabHoliday List` hl, tabHoliday hld - where hld.parent = hl.name - and ifnull(hld.holiday_date, "0000-00-00") != "0000-00-00" - """, as_dict=True, update={"allDay": 1}) return data diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js b/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js index cc24eb0000f..3cc8dd5036f 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js +++ b/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js @@ -9,5 +9,13 @@ frappe.views.calendar["Holiday List"] = { "title": "description", "allDay": "allDay" }, - get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events" + get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events", + filters: [ + { + 'fieldtype': 'Link', + 'fieldname': 'holiday_list', + 'options': 'Holiday List', + 'label': __('Holiday List') + } + ] } diff --git a/erpnext/hr/doctype/holiday_list/test_records.json b/erpnext/hr/doctype/holiday_list/test_records.json index 0a0afe81758..0bd096c415a 100644 --- a/erpnext/hr/doctype/holiday_list/test_records.json +++ b/erpnext/hr/doctype/holiday_list/test_records.json @@ -5,19 +5,18 @@ "to_date":"2013-12-31", "holidays": [ { - "description": "New Year", + "description": "New Year", "holiday_date": "2013-01-01" }, { - "description": "Republic Day", + "description": "Republic Day", "holiday_date": "2013-01-26" }, { - "description": "Test Holiday", + "description": "Test Holiday", "holiday_date": "2013-02-01" } - ], - "holiday_list_name": "_Test Holiday List", - "is_default": 1 + ], + "holiday_list_name": "_Test Holiday List" } -] \ No newline at end of file +] diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 705ace7c0dc..950400df444 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -8,6 +8,7 @@ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_li comma_or, get_fullname from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class LeaveDayBlockedError(frappe.ValidationError): pass @@ -422,7 +423,7 @@ def add_block_dates(events, start, end, employee, company): cnt+=1 def add_holidays(events, start, end, employee, company): - applicable_holiday_list = frappe.db.get_value("Employee", employee, "holiday_list") + applicable_holiday_list = get_holiday_list_for_employee(employee, company) if not applicable_holiday_list: return diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 21ee0ab7a62..f9f7377aa2b 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -11,7 +11,7 @@ from frappe import msgprint, _ from erpnext.setup.utils import get_company_currency from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details - +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase class SalarySlip(TransactionBase): @@ -20,11 +20,11 @@ class SalarySlip(TransactionBase): def get_emp_and_leave_details(self): if self.employee: - joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, + joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) - + self.get_leave_details(joining_date, relieving_date) - + struct = self.check_sal_struct(joining_date, relieving_date) if struct: self.set("earnings", []) @@ -33,10 +33,10 @@ class SalarySlip(TransactionBase): def check_sal_struct(self, joining_date, relieving_date): m = get_month_details(self.fiscal_year, self.month) - + struct = frappe.db.sql("""select name from `tabSalary Structure` where employee=%s and is_active = 'Yes' - and (from_date <= %s or from_date <= %s) + and (from_date <= %s or from_date <= %s) and (to_date is null or to_date >= %s or to_date >= %s)""", (self.employee, m.month_start_date, joining_date, m.month_end_date, relieving_date)) @@ -62,9 +62,9 @@ class SalarySlip(TransactionBase): self.fiscal_year = frappe.db.get_default("fiscal_year") if not self.month: self.month = "%02d" % getdate(nowdate()).month - + if not joining_date: - joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, + joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) m = get_month_details(self.fiscal_year, self.month) @@ -82,7 +82,7 @@ class SalarySlip(TransactionBase): self.leave_without_pay = lwp payment_days = flt(self.get_payment_days(m, joining_date, relieving_date)) - flt(lwp) self.payment_days = payment_days > 0 and payment_days or 0 - + def get_payment_days(self, month, joining_date, relieving_date): start_date = month['month_start_date'] if joining_date: @@ -90,15 +90,15 @@ class SalarySlip(TransactionBase): start_date = joining_date elif joining_date > month['month_end_date']: return - + end_date = month['month_end_date'] if relieving_date: if relieving_date > start_date and relieving_date < month['month_end_date']: end_date = relieving_date elif relieving_date < month['month_start_date']: frappe.throw(_("Employee relieved on {0} must be set as 'Left'") - .format(relieving_date)) - + .format(relieving_date)) + payment_days = date_diff(end_date, start_date) + 1 if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")): @@ -108,20 +108,19 @@ class SalarySlip(TransactionBase): return payment_days def get_holidays_for_employee(self, start_date, end_date): - holidays = frappe.db.sql("""select t1.holiday_date - from `tabHoliday` t1, tabEmployee t2 - where t1.parent = t2.holiday_list and t2.name = %s - and t1.holiday_date between %s and %s""", - (self.employee, start_date, end_date)) - - if not holidays: - holidays = frappe.db.sql("""select t1.holiday_date - from `tabHoliday` t1, `tabHoliday List` t2 - where t1.parent = t2.name and t2.is_default = 1 - and t1.holiday_date between %s and %s""", - (start_date, end_date)) - - holidays = [cstr(i[0]) for i in holidays] + holiday_list = get_holiday_list_for_employee(self.employee) + holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` + where + parent=%(holiday_list)s + and holiday_date >= %(start_date)s + and holiday_date <= %(end_date)s''', { + "holiday_list": holiday_list, + "start_date": start_date, + "end_date": end_date + }) + + holidays = [cstr(i) for i in holidays] + return holidays def calculate_lwp(self, holidays, m): diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 387c5931290..2383affa0af 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -13,11 +13,11 @@ class TestSalarySlip(unittest.TestCase): def setUp(self): for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: frappe.db.sql("delete from `tab%s`" % dt) - + make_allocation_record(leave_type="_Test Leave Type LWP") - - frappe.db.set_value("Holiday List", "_Test Holiday List", "is_default", 1) - + + frappe.db.set_value("Company", "_Test Company", "default_holiday_list", "_Test Holiday List") + from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications la = frappe.copy_doc(leave_applications[2]) la.insert() @@ -32,7 +32,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1) ss = frappe.copy_doc(test_records[0]) ss.insert() - + self.assertEquals(ss.total_days_in_month, 31) self.assertEquals(ss.payment_days, 30) self.assertEquals(ss.earnings[0].e_modified_amount, 14516.13) @@ -46,7 +46,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) ss = frappe.copy_doc(test_records[0]) ss.insert() - + self.assertEquals(ss.total_days_in_month, 29) self.assertEquals(ss.payment_days, 28) self.assertEquals(ss.earnings[0].e_modified_amount, 14482.76) @@ -55,32 +55,32 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.deductions[1].d_modified_amount, 48.28) self.assertEquals(ss.gross_pay, 14982.76) self.assertEquals(ss.net_pay, 14834.48) - + def test_payment_days(self): # Holidays not included in working days frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) - + # set joinng date in the same month frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2013-01-11") - + ss = frappe.copy_doc(test_records[0]) ss.insert() - + self.assertEquals(ss.total_days_in_month, 29) self.assertEquals(ss.payment_days, 19) - + # set relieving date in the same month frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", "2013-01-28") ss.save() self.assertEquals(ss.total_days_in_month, 29) self.assertEquals(ss.payment_days, 16) - + # Holidays included in working days frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1) ss.save() self.assertEquals(ss.total_days_in_month, 31) self.assertEquals(ss.payment_days, 17) - + frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2001-01-11") frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", None) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d6ebf5bd65a..e53a506ac49 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -254,3 +254,4 @@ erpnext.patches.v6_24.repost_valuation_rate_for_serialized_items erpnext.patches.v6_24.set_recurring_id erpnext.patches.v6_20x.set_compact_print execute:frappe.delete_doc_if_exists("Web Form", "contact") #2016-03-10 +erpnext.patches.v6_20x.remove_fiscal_year_from_holiday_list diff --git a/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py b/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py new file mode 100644 index 00000000000..c8c62036e2d --- /dev/null +++ b/erpnext/patches/v6_20x/remove_fiscal_year_from_holiday_list.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + default_holiday_list = frappe.db.get_value("Holiday List", {"is_default": 1}) + if default_holiday_list: + for company in frappe.get_all("Company", fields=["name", "default_holiday_list"]): + if not company.default_holiday_list: + frappe.db.set_value("Company", company.name, "default_holiday_list", default_holiday_list) + + + fiscal_years = frappe._dict((fy.name, fy) for fy in frappe.get_all("Fiscal Year", fields=["name", "year_start_date", "year_end_date"])) + + for holiday_list in frappe.get_all("Holiday List", fields=["name", "fiscal_year"]): + fy = fiscal_years[holiday_list.fiscal_year] + frappe.db.set_value("Holiday List", holiday_list.name, "from_date", fy.year_start_date) + frappe.db.set_value("Holiday List", holiday_list.name, "to_date", fy.year_end_date) diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index b6918b3afaa..7e26ca32074 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -6,7 +6,8 @@ "default_currency": "INR", "doctype": "Company", "domain": "Manufacturing", - "chart_of_accounts": "Standard" + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" }, { "abbr": "_TC1", @@ -15,7 +16,8 @@ "default_currency": "USD", "doctype": "Company", "domain": "Retail", - "chart_of_accounts": "Standard" + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" }, { "abbr": "_TC2", @@ -24,6 +26,7 @@ "country": "Germany", "doctype": "Company", "domain": "Retail", - "chart_of_accounts": "Standard" + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" } ] diff --git a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py index 38a88c90746..04699729352 100644 --- a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py @@ -9,6 +9,7 @@ from frappe.utils import add_days, getdate, cint, cstr from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events from erpnext.stock.utils import get_valid_serial_nos +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class MaintenanceSchedule(TransactionBase): def generate_schedule(self): @@ -92,22 +93,19 @@ class MaintenanceSchedule(TransactionBase): def validate_schedule_date_for_holiday_list(self, schedule_date, sales_person): validated = False - # check holiday list in employee master - holiday_list = frappe.db.sql_list("""select h.holiday_date from `tabEmployee` emp, - `tabSales Person` sp, `tabHoliday` h, `tabHoliday List` hl - where h.parent=hl.name and sp.name=%s and emp.name=sp.employee - and hl.name=emp.holiday_list - """, (sales_person)) - if not holiday_list: - # check global holiday list - holiday_list = frappe.db.sql_list("""select h.holiday_date from - `tabHoliday` h, `tabHoliday List` hl - where h.parent=hl.name and hl.is_default = 1""") - if not validated and holiday_list: - if schedule_date in holiday_list: - schedule_date = add_days(schedule_date, -1) - else: - validated = True + employee = frappe.db.get_value("Sales Person", sales_person, "employee") + holiday_list = get_holiday_list_for_employee(employee) + holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list) + + if not validated and holidays: + + # max iterations = len(holidays) + for i in xrange(len(holidays)): + if schedule_date in holidays: + schedule_date = add_days(schedule_date, -1) + else: + validated = True + break return schedule_date