From e79d57ca19998ca0fce1617ea6e22335f4c42a46 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 2 Aug 2013 19:31:41 +0530 Subject: [PATCH] [salary slip] total no. of working days calculation [issue] webnotes/erpnext#285 --- controllers/accounts_controller.py | 5 +- .../deduction_type/test_deduction_type.py | 10 +++ hr/doctype/earning_type/test_earning_type.py | 12 +++ hr/doctype/holiday_list/test_holiday_list.py | 3 +- hr/doctype/hr_settings/__init__.py | 0 hr/doctype/hr_settings/hr_settings.py | 8 ++ hr/doctype/hr_settings/hr_settings.txt | 59 +++++++++++++ .../test_leave_application.py | 10 +++ hr/doctype/leave_type/test_leave_type.py | 5 ++ hr/doctype/salary_manager/salary_manager.py | 30 +++---- hr/doctype/salary_slip/salary_slip.py | 44 +++++++--- hr/doctype/salary_slip/salary_slip.txt | 6 +- hr/doctype/salary_slip/test_salary_slip.py | 85 +++++++++++++++++++ hr/page/hr_home/hr_home.js | 12 +++ patches/august_2013/__init__.py | 0 patches/august_2013/p01_hr_settings.py | 16 ++++ patches/patch_list.py | 1 + .../global_defaults/global_defaults.txt | 11 +-- 18 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 hr/doctype/deduction_type/test_deduction_type.py create mode 100644 hr/doctype/earning_type/test_earning_type.py create mode 100644 hr/doctype/hr_settings/__init__.py create mode 100644 hr/doctype/hr_settings/hr_settings.py create mode 100644 hr/doctype/hr_settings/hr_settings.txt create mode 100644 hr/doctype/salary_slip/test_salary_slip.py create mode 100644 patches/august_2013/__init__.py create mode 100644 patches/august_2013/p01_hr_settings.py diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index e3e5d5eaf3..0a2a20be4f 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -141,8 +141,9 @@ class AccountsController(TransactionBase): def calculate_taxes_and_totals(self): # validate conversion rate - if not self.doc.currency: - self.doc.currency = get_company_currency(self.doc.company) + company_currency = get_company_currency(self.doc.company) + if not self.doc.currency or self.doc.currency == company_currency: + self.doc.currency = company_currency self.doc.conversion_rate = 1.0 else: validate_conversion_rate(self.doc.currency, self.doc.conversion_rate, diff --git a/hr/doctype/deduction_type/test_deduction_type.py b/hr/doctype/deduction_type/test_deduction_type.py new file mode 100644 index 0000000000..5960aace2d --- /dev/null +++ b/hr/doctype/deduction_type/test_deduction_type.py @@ -0,0 +1,10 @@ +test_records = [ + [{ + "doctype": "Deduction Type", + "deduction_name": "_Test Professional Tax" + }], + [{ + "doctype": "Deduction Type", + "deduction_name": "_Test TDS" + }] +] \ No newline at end of file diff --git a/hr/doctype/earning_type/test_earning_type.py b/hr/doctype/earning_type/test_earning_type.py new file mode 100644 index 0000000000..ea245a2e0a --- /dev/null +++ b/hr/doctype/earning_type/test_earning_type.py @@ -0,0 +1,12 @@ +test_records = [ + [{ + "doctype": "Earning Type", + "earning_name": "_Test Basic Salary", + "taxable": "Yes" + }], + [{ + "doctype": "Earning Type", + "earning_name": "_Test Allowance", + "taxable": "Yes" + }] +] \ No newline at end of file diff --git a/hr/doctype/holiday_list/test_holiday_list.py b/hr/doctype/holiday_list/test_holiday_list.py index e3a2d82980..07b7fe5771 100644 --- a/hr/doctype/holiday_list/test_holiday_list.py +++ b/hr/doctype/holiday_list/test_holiday_list.py @@ -1,7 +1,8 @@ test_records = [[{ "doctype": "Holiday List", "holiday_list_name": "_Test Holiday List", - "fiscal_year": "_Test Fiscal Year 2013" + "fiscal_year": "_Test Fiscal Year 2013", + "is_default": 1 }, { "doctype": "Holiday", "parent": "_Test Holiday List", diff --git a/hr/doctype/hr_settings/__init__.py b/hr/doctype/hr_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hr/doctype/hr_settings/hr_settings.py b/hr/doctype/hr_settings/hr_settings.py new file mode 100644 index 0000000000..928aa9ff9f --- /dev/null +++ b/hr/doctype/hr_settings/hr_settings.py @@ -0,0 +1,8 @@ +# For license information, please see license.txt + +from __future__ import unicode_literals +import webnotes + +class DocType: + def __init__(self, d, dl): + self.doc, self.doclist = d, dl \ No newline at end of file diff --git a/hr/doctype/hr_settings/hr_settings.txt b/hr/doctype/hr_settings/hr_settings.txt new file mode 100644 index 0000000000..e3694d0572 --- /dev/null +++ b/hr/doctype/hr_settings/hr_settings.txt @@ -0,0 +1,59 @@ +[ + { + "creation": "2013-08-02 13:45:23", + "docstatus": 0, + "modified": "2013-08-02 14:22:26", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "DocType", + "document_type": "Other", + "icon": "icon-cog", + "issingle": 1, + "module": "HR", + "name": "__common__" + }, + { + "doctype": "DocField", + "name": "__common__", + "parent": "HR Settings", + "parentfield": "fields", + "parenttype": "DocType", + "permlevel": 0 + }, + { + "create": 1, + "doctype": "DocPerm", + "name": "__common__", + "parent": "HR Settings", + "parentfield": "permissions", + "parenttype": "DocType", + "permlevel": 0, + "read": 1, + "role": "System Manager", + "write": 1 + }, + { + "doctype": "DocType", + "name": "HR Settings" + }, + { + "description": "Employee record is created using selected field. ", + "doctype": "DocField", + "fieldname": "emp_created_by", + "fieldtype": "Select", + "label": "Employee Records to be created by", + "options": "Naming Series\nEmployee Number" + }, + { + "description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day", + "doctype": "DocField", + "fieldname": "include_holidays_in_total_working_days", + "fieldtype": "Check", + "label": "Include holidays in Total no. of Working Days" + }, + { + "doctype": "DocPerm" + } +] \ No newline at end of file diff --git a/hr/doctype/leave_application/test_leave_application.py b/hr/doctype/leave_application/test_leave_application.py index 338225c45e..4acabda263 100644 --- a/hr/doctype/leave_application/test_leave_application.py +++ b/hr/doctype/leave_application/test_leave_application.py @@ -236,5 +236,15 @@ test_records = [ "fiscal_year": "_Test Fiscal Year 2013", "employee": "_T-Employee-0002", "company": "_Test Company" + }], + [{ + "doctype": "Leave Application", + "leave_type": "_Test Leave Type LWP", + "from_date": "2013-01-02", + "to_date": "2013-01-02", + "posting_date": "2013-01-02", + "fiscal_year": "_Test Fiscal Year 2013", + "employee": "_T-Employee-0001", + "company": "_Test Company", }] ] diff --git a/hr/doctype/leave_type/test_leave_type.py b/hr/doctype/leave_type/test_leave_type.py index 4d789758a4..9b3b240174 100644 --- a/hr/doctype/leave_type/test_leave_type.py +++ b/hr/doctype/leave_type/test_leave_type.py @@ -2,5 +2,10 @@ test_records = [ [{ "leave_type_name": "_Test Leave Type", "doctype": "Leave Type" + }], + [{ + "leave_type_name": "_Test Leave Type LWP", + "doctype": "Leave Type", + "is_lwp": 1 }] ] \ No newline at end of file diff --git a/hr/doctype/salary_manager/salary_manager.py b/hr/doctype/salary_manager/salary_manager.py index e9b3dd719e..7a8f1da6af 100644 --- a/hr/doctype/salary_manager/salary_manager.py +++ b/hr/doctype/salary_manager/salary_manager.py @@ -112,26 +112,16 @@ class DocType: if not sql("""select name from `tabSalary Slip` where docstatus!= 2 and employee = %s and month = %s and fiscal_year = %s and company = %s """, (emp[0], self.doc.month, self.doc.fiscal_year, self.doc.company)): - ss = Document('Salary Slip') - ss.fiscal_year = self.doc.fiscal_year - ss.employee = emp[0] - ss.month = self.doc.month - ss.email_check = self.doc.send_email - ss.company = self.doc.company - ss.save(1) - - ss_obj = get_obj('Salary Slip', ss.name, with_children=1) - ss_obj.get_emp_and_leave_details() - ss_obj.calculate_net_pay() - ss_obj.validate() - ss_obj.doc.save() - - for d in getlist(ss_obj.doclist, 'earning_details'): - d.save() - for d in getlist(ss_obj.doclist, 'deduction_details'): - d.save() - - ss_list.append(ss.name) + ss = webnotes.bean({ + "doctype": "Salary Slip", + "fiscal_year": self.doc.fiscal_year, + "employee": emp[0], + "month": self.doc.month, + "email_check": self.doc.send_email, + "company": self.doc.company, + }) + ss.insert() + ss_list.append(ss.doc.name) return self.create_log(ss_list) diff --git a/hr/doctype/salary_slip/salary_slip.py b/hr/doctype/salary_slip/salary_slip.py index 09bd602da1..3e943bc94f 100644 --- a/hr/doctype/salary_slip/salary_slip.py +++ b/hr/doctype/salary_slip/salary_slip.py @@ -61,7 +61,7 @@ class DocType(TransactionBase): ["bank_name", "bank_ac_no", "esic_card_no", "pf_number"], as_dict=1) if emp: self.doc.bank_name = emp.bank_name - self.doc.bank_ac_no = emp.bank_ac_no + self.doc.bank_account_no = emp.bank_ac_no self.doc.esic_no = emp.esic_card_no self.doc.pf_no = emp.pf_number @@ -72,9 +72,17 @@ class DocType(TransactionBase): self.doc.month = "%02d" % getdate(nowdate()).month m = get_obj('Salary Manager').get_month_details(self.doc.fiscal_year, self.doc.month) + holidays = self.get_holidays_for_employee(m) + if not cint(webnotes.conn.get_value("HR Settings", "HR Settings", + "include_holidays_in_total_working_days")): + m["month_days"] -= len(holidays) + if m["month_days"] < 0: + msgprint(_("Bummer! There are more holidays than working days this month."), + raise_exception=True) + if not lwp: - lwp = self.calculate_lwp(m) + lwp = self.calculate_lwp(holidays, m) self.doc.total_days_in_month = m['month_days'] self.doc.leave_without_pay = lwp payment_days = flt(self.get_payment_days(m)) - flt(lwp) @@ -103,11 +111,8 @@ class DocType(TransactionBase): payment_days = 0 return payment_days - - - - - def calculate_lwp(self, m): + + def get_holidays_for_employee(self, m): holidays = sql("""select t1.holiday_date from `tabHoliday` t1, tabEmployee t2 where t1.parent = t2.holiday_list and t2.name = %s @@ -117,8 +122,13 @@ class DocType(TransactionBase): holidays = sql("""select t1.holiday_date from `tabHoliday` t1, `tabHoliday List` t2 where t1.parent = t2.name and ifnull(t2.is_default, 0) = 1 - and t2.fiscal_year = %s""", self.doc.fiscal_year) + and t2.fiscal_year = %s + and t1.holiday_date between %s and %s""", (self.doc.fiscal_year, + m['month_start_date'], m['month_end_date'])) holidays = [cstr(i[0]) for i in holidays] + return holidays + + def calculate_lwp(self, holidays, m): lwp = 0 for d in range(m['month_days']): dt = add_days(cstr(m['month_start_date']), d) @@ -133,7 +143,7 @@ class DocType(TransactionBase): and %s between from_date and to_date """, (self.doc.employee, dt)) if leave: - lwp = cint(leave[0][1]) and lwp + 0.5 or lwp + 1 + lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) return lwp def check_existing(self): @@ -150,17 +160,29 @@ class DocType(TransactionBase): def validate(self): from webnotes.utils import money_in_words self.check_existing() + + if not (len(self.doclist.get({"parentfield": "earning_details"})) or + len(self.doclist.get({"parentfield": "deduction_details"}))): + self.get_emp_and_leave_details() + else: + self.get_leave_details(self.doc.leave_without_pay) + + if not self.doc.net_pay: + self.calculate_net_pay() + company_currency = get_company_currency(self.doc.company) self.doc.total_in_words = money_in_words(self.doc.rounded_total, company_currency) def calculate_earning_total(self): self.doc.gross_pay = flt(self.doc.arrear_amount) + flt(self.doc.leave_encashment_amount) - for d in getlist(self.doclist, 'earning_details'): + for d in self.doclist.get({"parentfield": "earning_details"}): if cint(d.e_depends_on_lwp) == 1: d.e_modified_amount = round(flt(d.e_amount) * flt(self.doc.payment_days) / cint(self.doc.total_days_in_month), 2) elif not self.doc.payment_days: d.e_modified_amount = 0 + else: + d.e_modified_amount = d.e_amount self.doc.gross_pay += flt(d.e_modified_amount) def calculate_ded_total(self): @@ -171,6 +193,8 @@ class DocType(TransactionBase): / cint(self.doc.total_days_in_month), 2) elif not self.doc.payment_days: d.d_modified_amount = 0 + else: + d.d_modified_amount = d.d_amount self.doc.total_deduction += flt(d.d_modified_amount) diff --git a/hr/doctype/salary_slip/salary_slip.txt b/hr/doctype/salary_slip/salary_slip.txt index 871c6b850b..641adae946 100644 --- a/hr/doctype/salary_slip/salary_slip.txt +++ b/hr/doctype/salary_slip/salary_slip.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-10 16:34:15", "docstatus": 0, - "modified": "2013-07-05 14:53:44", + "modified": "2013-08-02 19:23:13", "modified_by": "Administrator", "owner": "Administrator" }, @@ -190,7 +190,7 @@ "doctype": "DocField", "fieldname": "total_days_in_month", "fieldtype": "Data", - "label": "Total days in month", + "label": "Total Working Days In The Month", "oldfieldname": "total_days_in_month", "oldfieldtype": "Int", "read_only": 1, @@ -208,7 +208,7 @@ "doctype": "DocField", "fieldname": "payment_days", "fieldtype": "Float", - "label": "Payment days", + "label": "Payment Days", "oldfieldname": "payment_days", "oldfieldtype": "Float", "read_only": 1, diff --git a/hr/doctype/salary_slip/test_salary_slip.py b/hr/doctype/salary_slip/test_salary_slip.py new file mode 100644 index 0000000000..e48bf6216f --- /dev/null +++ b/hr/doctype/salary_slip/test_salary_slip.py @@ -0,0 +1,85 @@ +import webnotes +import unittest + +class TestSalarySlip(unittest.TestCase): + def setUp(self): + webnotes.conn.sql("""delete from `tabLeave Application`""") + webnotes.conn.sql("""delete from `tabSalary Slip`""") + from hr.doctype.leave_application.test_leave_application import test_records as leave_applications + la = webnotes.bean(copy=leave_applications[4]) + la.insert() + la.doc.status = "Approved" + la.submit() + + def tearDown(self): + webnotes.conn.set_value("HR Settings", "HR Settings", "include_holidays_in_total_working_days", 0) + + def test_salary_slip_with_holidays_included(self): + webnotes.conn.set_value("HR Settings", "HR Settings", "include_holidays_in_total_working_days", 1) + ss = webnotes.bean(copy=test_records[0]) + ss.insert() + self.assertEquals(ss.doc.total_days_in_month, 31) + self.assertEquals(ss.doc.payment_days, 30) + self.assertEquals(ss.doclist[1].e_modified_amount, 14516.13) + self.assertEquals(ss.doclist[2].e_modified_amount, 500) + self.assertEquals(ss.doclist[3].d_modified_amount, 100) + self.assertEquals(ss.doclist[4].d_modified_amount, 48.39) + self.assertEquals(ss.doc.gross_pay, 15016.13) + self.assertEquals(ss.doc.net_pay, 14867.74) + + def test_salary_slip_with_holidays_excluded(self): + ss = webnotes.bean(copy=test_records[0]) + ss.insert() + self.assertEquals(ss.doc.total_days_in_month, 30) + self.assertEquals(ss.doc.payment_days, 29) + self.assertEquals(ss.doclist[1].e_modified_amount, 14500) + self.assertEquals(ss.doclist[2].e_modified_amount, 500) + self.assertEquals(ss.doclist[3].d_modified_amount, 100) + self.assertEquals(ss.doclist[4].d_modified_amount, 48.33) + self.assertEquals(ss.doc.gross_pay, 15000) + self.assertEquals(ss.doc.net_pay, 14851.67) + +test_dependencies = ["Leave Application"] + +test_records = [ + [ + { + "doctype": "Salary Slip", + "employee": "_T-Employee-0001", + "employee_name": "_Test Employee", + "company": "_Test Company", + "fiscal_year": "_Test Fiscal Year 2013", + "month": "01", + "total_days_in_month": 31, + "payment_days": 31 + }, + { + "doctype": "Salary Slip Earning", + "parentfield": "earning_details", + "e_type": "_Test Basic Salary", + "e_amount": 15000, + "e_depends_on_lwp": 1 + }, + { + "doctype": "Salary Slip Earning", + "parentfield": "earning_details", + "e_type": "_Test Allowance", + "e_amount": 500, + "e_depends_on_lwp": 0 + }, + { + "doctype": "Salary Slip Deduction", + "parentfield": "deduction_details", + "d_type": "_Test Professional Tax", + "d_amount": 100, + "d_depends_on_lwp": 0 + }, + { + "doctype": "Salary Slip Deduction", + "parentfield": "deduction_details", + "d_type": "_Test TDS", + "d_amount": 50, + "d_depends_on_lwp": 1 + }, + ] +] \ No newline at end of file diff --git a/hr/page/hr_home/hr_home.js b/hr/page/hr_home/hr_home.js index 21ec1e1fe9..54c3d6685f 100644 --- a/hr/page/hr_home/hr_home.js +++ b/hr/page/hr_home/hr_home.js @@ -161,6 +161,18 @@ wn.module_page["HR"] = [ }, ] }, + { + title: wn._("Setup"), + icon: "icon-cog", + items: [ + { + "label": wn._("HR Settings"), + "route": "Form/HR Settings", + "doctype":"HR Settings", + "description": "Settings for HR Module" + } + ] + }, { title: wn._("Reports"), right: true, diff --git a/patches/august_2013/__init__.py b/patches/august_2013/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/patches/august_2013/p01_hr_settings.py b/patches/august_2013/p01_hr_settings.py new file mode 100644 index 0000000000..408f1e4357 --- /dev/null +++ b/patches/august_2013/p01_hr_settings.py @@ -0,0 +1,16 @@ +import webnotes + +def execute(): + webnotes.reload_doc("hr", "doctype", "hr_settings") + webnotes.reload_doc("setup", "doctype", "global_defaults") + + hr = webnotes.bean("HR Settings", "HR Settings") + hr.doc.emp_created_by = webnotes.conn.get_value("Global Defaults", "Global Defaults", "emp_created_by") + + if webnotes.conn.sql("""select name from `tabSalary Slip` where docstatus=1 limit 1"""): + hr.doc.include_holidays_in_total_working_days = 1 + + hr.save() + + webnotes.conn.sql("""delete from `tabSingles` where doctype = 'Global Defaults' + and field = 'emp_created_by'""") \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index 1426539bbe..c4c4a8324a 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -264,4 +264,5 @@ patch_list = [ "patches.july_2013.p10_change_partner_user_to_website_user", "patches.july_2013.p11_update_price_list_currency", "execute:webnotes.bean('Selling Settings').save() #2013-07-29", + "patches.august_2013.p01_hr_settings", ] \ No newline at end of file diff --git a/setup/doctype/global_defaults/global_defaults.txt b/setup/doctype/global_defaults/global_defaults.txt index b59ca94c25..8f5b940fb1 100644 --- a/setup/doctype/global_defaults/global_defaults.txt +++ b/setup/doctype/global_defaults/global_defaults.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-02 17:53:24", "docstatus": 0, - "modified": "2013-07-15 15:03:01", + "modified": "2013-08-02 13:45:12", "modified_by": "Administrator", "owner": "Administrator" }, @@ -181,15 +181,6 @@ "label": "HR", "read_only": 0 }, - { - "description": "Employee record is created using selected field. ", - "doctype": "DocField", - "fieldname": "emp_created_by", - "fieldtype": "Select", - "label": "Employee Records to be created by ", - "options": "Naming Series\nEmployee Number", - "read_only": 0 - }, { "doctype": "DocPerm" }