diff --git a/erpnext/docs/assets/img/human-resources/payroll-frequency.png b/erpnext/docs/assets/img/human-resources/payroll-frequency.png new file mode 100644 index 0000000000..e6399037d8 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/payroll-frequency.png differ diff --git a/erpnext/docs/assets/img/human-resources/process-payroll.png b/erpnext/docs/assets/img/human-resources/process-payroll.png index 2e2fbed923..0901cbe10c 100644 Binary files a/erpnext/docs/assets/img/human-resources/process-payroll.png and b/erpnext/docs/assets/img/human-resources/process-payroll.png differ diff --git a/erpnext/docs/assets/img/human-resources/salary-structure.png b/erpnext/docs/assets/img/human-resources/salary-structure.png index 71f6013f94..83e4820001 100644 Binary files a/erpnext/docs/assets/img/human-resources/salary-structure.png and b/erpnext/docs/assets/img/human-resources/salary-structure.png differ diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.js b/erpnext/hr/doctype/process_payroll/process_payroll.js index 35c097ac0d..3265f88f82 100644 --- a/erpnext/hr/doctype/process_payroll/process_payroll.js +++ b/erpnext/hr/doctype/process_payroll/process_payroll.js @@ -2,43 +2,43 @@ // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Process Payroll", { - refresh: function(frm) { - frm.disable_save(); - frm.trigger("toggle_fields"); - frm.trigger("set_month_dates"); - }, - - month: function(frm) { - frm.trigger("set_month_dates"); - }, - - fiscal_year: function(frm) { - frm.trigger("set_month_dates"); - }, - - salary_slip_based_on_timesheet: function(frm) { - frm.trigger("toggle_fields") + onload: function(frm) { + frm.doc.posting_date = frm.doc.start_date = frm.doc.end_date = frappe.datetime.nowdate() }, - toggle_fields: function(frm) { - frm.toggle_display(['from_date','to_date'], - cint(frm.doc.salary_slip_based_on_timesheet)==1); - frm.toggle_display(['fiscal_year', 'month'], - cint(frm.doc.salary_slip_based_on_timesheet)==0); + refresh: function(frm) { + frm.disable_save(); }, - set_month_dates: function(frm) { + payroll_frequency: function(frm) { + frm.trigger("set_start_end_dates"); + }, + + start_date: function(frm) { + frm.trigger("set_start_end_dates"); + }, + + end_date: function(frm) { + frm.trigger("set_start_end_dates"); + }, + + payment_account: function(frm) { + frm.toggle_display(['make_bank_entry'], (frm.doc.payment_account!="" && frm.doc.payment_account!="undefined")); + }, + + set_start_end_dates: function(frm) { if (!frm.doc.salary_slip_based_on_timesheet){ frappe.call({ - method:'erpnext.hr.doctype.process_payroll.process_payroll.get_month_details', + method:'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates', args:{ - year: frm.doc.fiscal_year, - month: frm.doc.month + payroll_frequency: frm.doc.payroll_frequency, + start_date: frm.doc.start_date, + end_date: frm.doc.end_date }, callback: function(r){ if (r.message){ - frm.set_value('from_date', r.message.month_start_date); - frm.set_value('to_date', r.message.month_end_date); + frm.set_value('start_date', r.message.start_date); + frm.set_value('end_date', r.message.end_date); } } }) @@ -56,17 +56,6 @@ frappe.ui.form.on("Process Payroll", { } }) -cur_frm.cscript.onload = function(doc,cdt,cdn){ - if(!doc.month) { - var today=new Date(); - month = (today.getMonth()+01).toString(); - if(month.length>1) doc.month = month; - else doc.month = '0'+month; - } - if(!doc.fiscal_year) doc.fiscal_year = sys_defaults['fiscal_year']; - refresh_many(['month', 'fiscal_year']); -} - cur_frm.cscript.display_activity_log = function(msg) { if(!cur_frm.ss_html) cur_frm.ss_html = $a(cur_frm.fields_dict['activity_log'].wrapper,'div'); @@ -92,7 +81,7 @@ cur_frm.cscript.create_salary_slip = function(doc, cdt, cdn) { cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) { cur_frm.cscript.display_activity_log(""); - frappe.confirm(__("Do you really want to Submit all Salary Slip from {0} to {1}", [doc.from_date, doc.to_date]), function() { + frappe.confirm(__("Do you really want to Submit all Salary Slip from {0} to {1}", [doc.start_date, doc.end_date]), function() { // clear all in locals if(locals["Salary Slip"]) { $.each(locals["Salary Slip"], function(name, d) { @@ -110,7 +99,7 @@ cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) { } cur_frm.cscript.make_bank_entry = function(doc,cdt,cdn){ - if(doc.company && doc.from_date && doc.to_date){ + if(doc.company && doc.start_date && doc.end_date){ return cur_frm.cscript.reference_entry(doc,cdt,cdn); } else { msgprint(__("Company, From Date and To Date is mandatory")); diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.json b/erpnext/hr/doctype/process_payroll/process_payroll.json index ae86c2ddbc..8e0d39577a 100644 --- a/erpnext/hr/doctype/process_payroll/process_payroll.json +++ b/erpnext/hr/doctype/process_payroll/process_payroll.json @@ -22,6 +22,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Select Employees", "length": 0, "no_copy": 0, @@ -48,6 +49,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -67,6 +69,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "", "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -74,6 +77,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Company", "length": 0, "no_copy": 0, @@ -102,6 +106,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Posting Date", "length": 0, "no_copy": 0, @@ -117,6 +122,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Monthly", + "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0", + "fieldname": "payroll_frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payroll Frequency", + "length": 0, + "no_copy": 0, + "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -129,6 +165,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -155,6 +192,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Branch", "length": 0, "no_copy": 0, @@ -182,6 +220,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Department", "length": 0, "no_copy": 0, @@ -209,6 +248,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Designation", "length": 0, "no_copy": 0, @@ -236,6 +276,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -262,6 +303,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Salary Slip Based on Timesheet", "length": 0, "no_copy": 0, @@ -289,6 +331,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Select Payroll Period", "length": 0, "no_copy": 0, @@ -309,14 +352,16 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "from_date", - "fieldtype": "Data", + "default": "Today", + "fieldname": "start_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "From", + "in_standard_filter": 0, + "label": "Start Date", "length": 0, "no_copy": 0, "permlevel": 0, @@ -331,33 +376,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -370,6 +388,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -389,14 +408,16 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "to_date", - "fieldtype": "Data", + "default": "Today", + "fieldname": "end_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "To", + "in_standard_filter": 0, + "label": "End Date", "length": 0, "no_copy": 0, "permlevel": 0, @@ -411,33 +432,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "month", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Month", - "length": 0, - "no_copy": 0, - "options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -450,6 +444,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Process Payroll", "length": 0, "no_copy": 0, @@ -477,6 +472,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -504,6 +500,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Create Salary Slip", "length": 0, "no_copy": 0, @@ -530,6 +527,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -557,6 +555,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Submit Salary Slip", "length": 0, "no_copy": 0, @@ -583,6 +582,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Account", "length": 0, "no_copy": 0, @@ -611,6 +611,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Payment Account", "length": 0, "no_copy": 0, @@ -632,15 +633,16 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.payment_account", + "depends_on": "", "description": "Create Bank Entry for the total salary paid for the above selected criteria", "fieldname": "make_bank_entry", "fieldtype": "Button", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Make Bank Entry", "length": 0, "no_copy": 0, @@ -667,6 +669,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -692,6 +695,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Activity Log", "length": 0, "no_copy": 0, @@ -718,7 +722,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-11-03 16:02:34.040851", + "modified": "2016-11-26 01:14:51.691057", "modified_by": "Administrator", "module": "HR", "name": "Process Payroll", diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py index 2077982f13..97e3876e02 100644 --- a/erpnext/hr/doctype/process_payroll/process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/process_payroll.py @@ -3,10 +3,12 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, flt, nowdate +from frappe.utils import cint, flt, nowdate, add_days, getdate from frappe import _ import collections from collections import defaultdict +from calendar import monthrange +from erpnext.accounts.utils import get_fiscal_year from frappe.model.document import Document @@ -19,7 +21,12 @@ class ProcessPayroll(Document): """ cond = self.get_filter_condition() cond += self.get_joining_releiving_condition() - + + + struct_cond = '' + if self.payroll_frequency: + struct_cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency} + sal_struct = frappe.db.sql(""" select name from `tabSalary Structure` where docstatus != 2 and is_active = 'Yes' and company = %(company)s and @@ -28,13 +35,11 @@ class ProcessPayroll(Document): if sal_struct: cond += "and t2.parent IN %(sal_struct)s " - emp_list = frappe.db.sql(""" select t1.name from `tabEmployee` t1, `tabSalary Structure Employee` t2 where t1.docstatus!=2 and t1.name = t2.employee %s """% cond, {"sal_struct": sal_struct}) - return emp_list @@ -51,16 +56,16 @@ class ProcessPayroll(Document): def get_joining_releiving_condition(self): cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(to_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(from_date)s' - """ % {"from_date": self.from_date, "to_date": self.to_date} + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": self.start_date, "end_date": self.end_date} return cond def check_mandatory(self): - for f in ['company', 'from_date', 'to_date']: - if not self.get(f): - frappe.throw(_("Please set {0}").format(f)) + for fieldname in ['company', 'payroll_frequency', 'start_date', 'end_date']: + if not self.get(fieldname): + frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname))) def create_sal_slip(self): """ @@ -74,28 +79,28 @@ class ProcessPayroll(Document): for emp in emp_list: if not frappe.db.sql("""select name from `tabSalary Slip` where docstatus!= 2 and employee = %s and start_date >= %s and end_date <= %s and company = %s - """, (emp[0], self.from_date, self.to_date, self.company)): - if self.salary_slip_based_on_timesheet: + """, (emp[0], self.start_date, self.end_date, self.company)): + if self.payroll_frequency == "Monthly" or self.payroll_frequency == '': ss = frappe.get_doc({ "doctype": "Salary Slip", "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet, - "start_date": self.from_date, - "end_date": self.to_date, "employee": emp[0], "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"), "company": self.company, - "posting_date": self.posting_date + "posting_date": self.posting_date, + "payroll_frequency": self.payroll_frequency }) else: ss = frappe.get_doc({ "doctype": "Salary Slip", "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet, - "fiscal_year": self.fiscal_year, - "month": self.month, + "start_date": self.start_date, + "end_date": self.end_date, "employee": emp[0], "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"), "company": self.company, - "posting_date": self.posting_date + "posting_date": self.posting_date, + "payroll_frequency": self.payroll_frequency }) ss.insert() ss_list.append(ss.name) @@ -120,7 +125,7 @@ class ProcessPayroll(Document): select t1.name, t1.salary_structure from `tabSalary Slip` t1 where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s - """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.from_date, self.to_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) + """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) return ss_list @@ -183,7 +188,7 @@ class ProcessPayroll(Document): tot = frappe.db.sql(""" select sum(rounded_total) from `tabSalary Slip` t1 where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s - """ % ('%s', '%s', cond), (self.from_date, self.to_date)) + """ % ('%s', '%s', cond), (self.start_date, self.end_date)) return flt(tot[0][0]) @@ -231,8 +236,8 @@ class ProcessPayroll(Document): if earnings or deductions: journal_entry = frappe.new_doc('Journal Entry') journal_entry.voucher_type = 'Bank Entry' - journal_entry.user_remark = _('Payment of salary from {0} to {1}').format(self.from_date, - self.to_date) + journal_entry.user_remark = _('Payment of salary from {0} to {1}').format(self.start_date, + self.end_date) journal_entry.company = self.company journal_entry.posting_date = nowdate() @@ -257,6 +262,7 @@ class ProcessPayroll(Document): journal_entry.set("accounts", account_amt_list) journal_entry.cheque_no = reference_number journal_entry.cheque_date = reference_date + journal_entry.multi_currency = 1 journal_entry.save() try: journal_entry.submit() @@ -279,7 +285,7 @@ class ProcessPayroll(Document): for ss in ss_list: ss_obj = frappe.get_doc("Salary Slip",ss[0]) frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid") - frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) + frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) @frappe.whitelist() @@ -293,12 +299,45 @@ def get_month_details(year, month): diff_mnt = 12-int(ysd.month)+cint(month) msd = ysd + relativedelta(months=diff_mnt) # month start date month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month + mid_start = datetime.date(msd.year, cint(month), 16) # month mid start date + mid_end = datetime.date(msd.year, cint(month), 15) # month mid end date med = datetime.date(msd.year, cint(month), month_days) # month end date return frappe._dict({ 'year': msd.year, 'month_start_date': msd, 'month_end_date': med, + 'month_mid_start_date': mid_start, + 'month_mid_end_date': mid_end, 'month_days': month_days }) else: - frappe.throw(_("Fiscal Year {0} not found").format(year)) \ No newline at end of file + frappe.throw(_("Fiscal Year {0} not found").format(year)) + +@frappe.whitelist() +def get_start_end_dates(payroll_frequency, start_date, end_date): + if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly": + fiscal_year = get_fiscal_year(start_date)[0] or get_fiscal_year(end_date)[0] + month = "%02d" % getdate(start_date).month or "%02d" % getdate(end_date).month + m = get_month_details(fiscal_year, month) + if payroll_frequency == "Bimonthly": + if getdate(start_date).day <= 15: + start_date = m['month_start_date'] + end_date = m['month_mid_end_date'] + else: + start_date = m['month_mid_start_date'] + end_date = m['month_end_date'] + else: + start_date = m['month_start_date'] + end_date = m['month_end_date'] + + if payroll_frequency == "Weekly": + end_date = add_days(start_date, 6) + + if payroll_frequency == "Fortnightly": + end_date = add_days(start_date, 13) + + if payroll_frequency == "Daily": + end_date = start_date + return frappe._dict({ + 'start_date': start_date, 'end_date': end_date + }) \ No newline at end of file diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.py b/erpnext/hr/doctype/process_payroll/test_process_payroll.py index 3be4b4c5f8..5167365eb0 100644 --- a/erpnext/hr/doctype/process_payroll/test_process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/test_process_payroll.py @@ -18,15 +18,14 @@ class TestProcessPayroll(unittest.TestCase): get_salary_component_account(data.name) payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") - if not frappe.db.get_value("Salary Slip", {"fiscal_year": fiscal_year, "month": month}): + if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}): process_payroll = frappe.get_doc("Process Payroll", "Process Payroll") process_payroll.company = erpnext.get_default_company() - process_payroll.month = month - process_payroll.fiscal_year = fiscal_year - process_payroll.from_date = "2016-11-01" - process_payroll.to_date = "2016-11-30" + process_payroll.start_date = "2016-11-01" + process_payroll.end_date = "2016-11-30" process_payroll.payment_account = payment_account process_payroll.posting_date = nowdate() + process_payroll.payroll_frequency = "Monthly" process_payroll.create_sal_slip() process_payroll.submit_salary_slip() if process_payroll.get_sal_slip_list(ss_status = 1): diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js index e1120e8826..8b0dd1655c 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.js +++ b/erpnext/hr/doctype/salary_slip/salary_slip.js @@ -46,59 +46,44 @@ frappe.ui.form.on("Salary Slip", { salary_slip_based_on_timesheet: function(frm) { frm.trigger("toggle_fields") }, + + payroll_frequency: function(frm) { + frm.trigger("toggle_fields") + }, toggle_fields: function(frm) { - frm.toggle_display(['start_date', 'end_date', 'hourly_wages', 'timesheets'], + frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)==1); - frm.toggle_display(['fiscal_year', 'month', 'total_days_in_month', 'leave_without_pay', 'payment_days'], - cint(frm.doc.salary_slip_based_on_timesheet)==0); + + frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'], + frm.doc.payroll_frequency!=""); } }) -frappe.ui.form.on("Salary Slip Timesheet", { - time_sheet: function(frm, cdt, cdn) { - doc = frm.doc; - cur_frm.cscript.fiscal_year(doc, cdt, cdn) - } -}) - - -// On load -// ------------------------------------------------------------------- -cur_frm.cscript.onload = function(doc,dt,dn){ - if((cint(doc.__islocal) == 1) && !doc.amended_from){ - if(!doc.month) { - var today=new Date(); - month = (today.getMonth()+01).toString(); - if(month.length>1) doc.month = month; - else doc.month = '0'+month; - } - if(!doc.fiscal_year) doc.fiscal_year = sys_defaults['fiscal_year']; - refresh_many(['month', 'fiscal_year']); - } -} - // Get leave details //--------------------------------------------------------------------- -cur_frm.cscript.fiscal_year = function(doc,dt,dn){ - return $c_obj(doc, 'get_emp_and_leave_details','',function(r, rt) { - var doc = locals[dt][dn]; +cur_frm.cscript.start_date = function(doc, dt, dn){ + return frappe.call({ + method: 'get_emp_and_leave_details', + doc: locals[dt][dn], + callback: function(r, rt) { cur_frm.refresh(); calculate_all(doc, dt, dn); - }); + } + }); } -cur_frm.cscript.month = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.fiscal_year; -cur_frm.cscript.start_date = cur_frm.cscript.end_date = cur_frm.cscript.fiscal_year; +cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date; +cur_frm.cscript.end_date = cur_frm.cscript.start_date; cur_frm.cscript.employee = function(doc,dt,dn){ doc.salary_structure = '' - cur_frm.cscript.fiscal_year(doc, dt, dn) + cur_frm.cscript.start_date(doc, dt, dn) } cur_frm.cscript.leave_without_pay = function(doc,dt,dn){ - if (doc.employee && doc.fiscal_year && doc.month) { + if (doc.employee && doc.start_date && doc.end_date) { return $c_obj(doc, 'get_leave_details', {"lwp": doc.leave_without_pay}, function(r, rt) { var doc = locals[dt][dn]; cur_frm.refresh(); @@ -135,7 +120,7 @@ var calculate_earning_total = function(doc, dt, dn, reset_amount) { for(var i = 0; i < tbl.length; i++){ if(cint(tbl[i].depends_on_lwp) == 1) { tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) / - cint(doc.total_days_in_month)*100)/100; + cint(doc.total_working_days)*100)/100; refresh_field('amount', tbl[i].name, 'earnings'); } else if(reset_amount) { tbl[i].amount = tbl[i].default_amount; @@ -155,7 +140,7 @@ var calculate_ded_total = function(doc, dt, dn, reset_amount) { var total_ded = 0; for(var i = 0; i < tbl.length; i++){ if(cint(tbl[i].depends_on_lwp) == 1) { - tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_days_in_month)*100)/100; + tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100; refresh_field('amount', tbl[i].name, 'deductions'); } else if(reset_amount) { tbl[i].amount = tbl[i].default_amount; diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index 4d69b01d05..062f41f8f3 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -421,69 +421,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "", - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Data", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "month", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Month", - "length": 0, - "no_copy": 0, - "oldfieldname": "month", - "oldfieldtype": "Select", - "options": "\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "unique": 0, - "width": "37%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "Today", "fieldname": "start_date", "fieldtype": "Date", "hidden": 0, @@ -594,13 +532,44 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)", + "fieldname": "payroll_frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payroll Frequency", + "length": 0, + "no_copy": 0, + "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "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, "depends_on": "", - "fieldname": "total_days_in_month", + "fieldname": "total_working_days", "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, @@ -1392,7 +1361,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-07 05:58:37.151587", + "modified": "2016-12-08 12:03:31.602913", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 34022bd713..f0733b7f5c 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -10,10 +10,12 @@ from frappe.model.naming import make_autoname from frappe import msgprint, _ from erpnext.accounts.utils import get_fiscal_year from erpnext.setup.utils import get_company_currency -from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details +from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details, get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase +from datetime import timedelta + class SalarySlip(TransactionBase): def autoname(self): self.name = make_autoname('Sal Slip/' +self.employee + '/.#####') @@ -22,8 +24,7 @@ class SalarySlip(TransactionBase): self.status = self.get_status() self.validate_dates() self.check_existing() - self.set_month_dates() - + self.get_date_details() if not (len(self.get("earnings")) or len(self.get("deductions"))): # get details from salary structure self.get_emp_and_leave_details() @@ -120,7 +121,7 @@ class SalarySlip(TransactionBase): self.set("earnings", []) self.set("deductions", []) - self.set_month_dates() + self.get_date_details() self.validate_dates() joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -146,20 +147,24 @@ class SalarySlip(TransactionBase): 'working_hours': data.total_hours }) - def set_month_dates(self): - if self.month and not self.salary_slip_based_on_timesheet: - m = get_month_details(self.fiscal_year, self.month) - self.start_date = m['month_start_date'] - self.end_date = m['month_end_date'] + def get_date_details(self): + date_details = get_start_end_dates(self.payroll_frequency, self.start_date, self.end_date) + self.start_date = date_details.start_date + self.end_date = date_details.end_date + def check_sal_struct(self, joining_date, relieving_date): + cond = '' + if self.payroll_frequency: + cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency} + st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee` where employee=%s and parent in (select name from `tabSalary Structure` where is_active = 'Yes' and (from_date <= %s or from_date <= %s) - and (to_date is null or to_date >= %s or to_date >= %s)) - """,(self.employee, self.start_date, joining_date, self.end_date, relieving_date)) + and (to_date is null or to_date >= %s or to_date >= %s) %s) + """% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date)) if st_name: if len(st_name) > 1: @@ -185,9 +190,11 @@ class SalarySlip(TransactionBase): def process_salary_structure(self): '''Calculate salary after salary structure details have been updated''' + self.get_date_details() self.pull_emp_details() self.get_leave_details() self.calculate_net_pay() + def add_earning_for_hourly_wages(self, salary_component): default_type = False @@ -210,14 +217,6 @@ class SalarySlip(TransactionBase): def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None): - if not self.fiscal_year: - # if default fiscal year is not set, get from nowdate - self.fiscal_year = get_fiscal_year(nowdate())[0] - - if not self.month: - self.month = "%02d" % getdate(nowdate()).month - self.set_month_dates() - if not joining_date: joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -236,7 +235,7 @@ class SalarySlip(TransactionBase): elif lwp != actual_lwp: frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records")) - self.total_days_in_month = working_days + self.total_working_days = working_days self.leave_without_pay = lwp payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp) @@ -306,9 +305,9 @@ class SalarySlip(TransactionBase): def check_existing(self): if not self.salary_slip_based_on_timesheet: ret_exist = frappe.db.sql("""select name from `tabSalary Slip` - where month = %s and fiscal_year = %s and docstatus != 2 + where start_date = %s and end_date = %s and docstatus != 2 and employee = %s and name != %s""", - (self.month, self.fiscal_year, self.employee, self.name)) + (self.start_date, self.end_date, self.employee, self.name)) if ret_exist: self.employee = '' frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee)) @@ -321,7 +320,7 @@ class SalarySlip(TransactionBase): for d in self.get(component_type): if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet: d.amount = rounded((flt(d.default_amount) * flt(self.payment_days) - / cint(self.total_days_in_month)), self.precision("amount", component_type)) + / cint(self.total_working_days)), self.precision("amount", component_type)) elif not self.payment_days and not self.salary_slip_based_on_timesheet: d.amount = 0 elif not d.amount: @@ -361,7 +360,7 @@ class SalarySlip(TransactionBase): receiver = frappe.db.get_value("Employee", self.employee, "prefered_email") if receiver: - subj = 'Salary Slip - from {0} to {1}, fiscal year {2}'.format(self.start_date, self.end_date, self.fiscal_year) + subj = 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date) frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"), attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name) else: diff --git a/erpnext/hr/doctype/salary_slip/test_records.json b/erpnext/hr/doctype/salary_slip/test_records.json index 20cef3db18..da6365fa27 100644 --- a/erpnext/hr/doctype/salary_slip/test_records.json +++ b/erpnext/hr/doctype/salary_slip/test_records.json @@ -40,6 +40,6 @@ "fiscal_year": "_Test Fiscal Year 2013", "month": "01", "payment_days": 31, - "total_days_in_month": 31 + "total_working_days": 31 } ] \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 57a3711fc0..89dced4753 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -6,18 +6,22 @@ import unittest import frappe import erpnext from frappe.utils.make_random import get_random -from frappe.utils import today, now_datetime, getdate, cstr, add_years, nowdate +from erpnext.accounts.utils import get_fiscal_year +from frappe.utils import today, now_datetime, getdate, cstr, add_years, nowdate, add_days from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.process_payroll.test_process_payroll import get_salary_component_account +from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details class TestSalarySlip(unittest.TestCase): def setUp(self): - make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"]) + make_earning_salary_component(["Basic Salary", "Allowance", "HRA"]) + make_deduction_salary_component(["Professional Tax", "TDS"]) for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: frappe.db.sql("delete from `tab%s`" % dt) self.make_holiday_list() + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications @@ -36,9 +40,9 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None) frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") ss = frappe.get_doc("Salary Slip", - self.make_employee_salary_slip("test_employee@salary.com")) + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) - self.assertEquals(ss.total_days_in_month, 31) + self.assertEquals(ss.total_working_days, 31) self.assertEquals(ss.payment_days, 31) self.assertEquals(ss.earnings[0].amount, 5000) self.assertEquals(ss.earnings[1].amount, 3000) @@ -53,9 +57,9 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None) frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") ss = frappe.get_doc("Salary Slip", - self.make_employee_salary_slip("test_employee@salary.com")) + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) - self.assertEquals(ss.total_days_in_month, 28) + self.assertEquals(ss.total_working_days, 28) self.assertEquals(ss.payment_days, 28) self.assertEquals(ss.earnings[0].amount, 5000) self.assertEquals(ss.earnings[0].default_amount, 5000) @@ -74,16 +78,16 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2013-01-11") ss = frappe.get_doc("Salary Slip", - self.make_employee_salary_slip("test_employee@salary.com")) + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) - self.assertEquals(ss.total_days_in_month, 28) + self.assertEquals(ss.total_working_days, 28) self.assertEquals(ss.payment_days, 28) # set relieving date in the same month frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", "12-12-2016") frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left") - self.assertEquals(ss.total_days_in_month, 28) + self.assertEquals(ss.total_working_days, 28) self.assertEquals(ss.payment_days, 28) ss.save() @@ -91,7 +95,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") # Holidays included in working days frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1) - self.assertEquals(ss.total_days_in_month, 28) + self.assertEquals(ss.total_working_days, 28) self.assertEquals(ss.payment_days, 28) ss.save() # @@ -102,7 +106,7 @@ class TestSalarySlip(unittest.TestCase): self.make_employee("test_employee@salary.com") salary_slip_test_employee = frappe.get_doc("Salary Slip", - self.make_employee_salary_slip("test_employee@salary.com")) + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) frappe.set_user("test_employee@salary.com") self.assertTrue(salary_slip_test_employee.has_permission("read")) @@ -115,11 +119,33 @@ class TestSalarySlip(unittest.TestCase): self.make_employee("test_employee@salary.com") ss = frappe.get_doc("Salary Slip", - self.make_employee_salary_slip("test_employee@salary.com")) + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) ss.submit() email_queue = frappe.db.sql("""select name from `tabEmail Queue`""") self.assertTrue(email_queue) + def test_payroll_frequency(self): + fiscal_year = get_fiscal_year(nowdate())[0] + month = "%02d" % getdate(nowdate()).month or "%02d" % getdate(nowdate()).month + m = get_month_details(fiscal_year, month) + + for payroll_frequncy in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]: + self.make_employee(payroll_frequncy + "_test_employee@salary.com") + ss = frappe.get_doc("Salary Slip", + self.make_employee_salary_slip(payroll_frequncy + "_test_employee@salary.com", payroll_frequncy)) + if payroll_frequncy == "Monthly": + self.assertEqual(ss.end_date, m['month_end_date']) + elif payroll_frequncy == "Bimonthly": + if getdate(ss.start_date).day <= 15: + self.assertEqual(ss.end_date, m['month_mid_end_date']) + else: + self.assertEqual(ss.end_date, m['month_end_date']) + elif payroll_frequncy == "Fortnightly": + self.assertEqual(ss.end_date, getdate(add_days(nowdate(),13))) + elif payroll_frequncy == "Weekly": + self.assertEqual(ss.end_date, getdate(add_days(nowdate(),6))) + elif payroll_frequncy == "Daily": + self.assertEqual(ss.end_date, getdate(nowdate())) def make_employee(self, user): if not frappe.db.get_value("User", user): @@ -148,7 +174,7 @@ class TestSalarySlip(unittest.TestCase): "status": "Active", "employment_type": "Intern" }).insert() - + def make_holiday_list(self): if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): holiday_list = frappe.get_doc({ @@ -160,17 +186,17 @@ class TestSalarySlip(unittest.TestCase): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() - - def make_employee_salary_slip(self, user): + + def make_employee_salary_slip(self, user, payroll_frequency): employee = frappe.db.get_value("Employee", {"user_id": user}) - salary_structure = make_salary_structure("Salary Structure Test for Salary Slip") + salary_structure = make_salary_structure(payroll_frequency + " Salary Structure Test for Salary Slip", payroll_frequency, employee) salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) if not salary_slip: salary_slip = make_salary_slip(salary_structure, employee = employee) salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name") - salary_slip.month = "12" - salary_slip.fiscal_year = "_Test Fiscal Year 2016" + salary_slip.payroll_frequency = payroll_frequency + salary_slip.start_date = nowdate() salary_slip.posting_date = nowdate() salary_slip.insert() # salary_slip.submit() @@ -185,38 +211,60 @@ class TestSalarySlip(unittest.TestCase): activity_type.wage_rate = 25 activity_type.save() -def make_salary_component(salary_components): +def make_earning_salary_component(salary_components): for salary_component in salary_components: if not frappe.db.exists('Salary Component', salary_component): sal_comp = frappe.get_doc({ "doctype": "Salary Component", - "salary_component": salary_component + "salary_component": salary_component, + "type": "Earning" }) sal_comp.insert() get_salary_component_account(salary_component) -def make_salary_structure(sal_struct): +def make_deduction_salary_component(salary_components): + for salary_component in salary_components: + if not frappe.db.exists('Salary Component', salary_component): + sal_comp = frappe.get_doc({ + "doctype": "Salary Component", + "salary_component": salary_component, + "type": "Deduction" + }) + sal_comp.insert() + get_salary_component_account(salary_component) + +def make_salary_structure(sal_struct, payroll_frequency, employee): if not frappe.db.exists('Salary Structure', sal_struct): frappe.get_doc({ "doctype": "Salary Structure", "name": sal_struct, "company": erpnext.get_default_company(), "from_date": nowdate(), - "employees": get_employee_details(), + "employees": get_employee_details(employee), "earnings": get_earnings_component(), "deductions": get_deductions_component(), + "payroll_frequency": payroll_frequency, "payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") }).insert() + + elif not frappe.db.get_value("Salary Structure Employee",{'parent':sal_struct, 'employee':employee},'name'): + sal_struct = frappe.get_doc("Salary Structure", sal_struct) + sal_struct.append("employees", {"employee": employee, + "employee_name": employee, + "base": 32000, + "variable": 3200 + }) + sal_struct.save() + sal_struct = sal_struct.name return sal_struct - - -def get_employee_details(): - return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), + +def get_employee_details(employee): + return [{"employee": employee, "base": 25000, "variable": 5000 } ] - + def get_earnings_component(): return [ { diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index d3ab809963..0bc67a7e6d 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -15,6 +15,8 @@ cur_frm.cscript.onload = function(doc, dt, dn){ frappe.ui.form.on('Salary Structure', { onload: function(frm) { + frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet), + frm.set_query("salary_component", "earnings", function() { return { filters: { @@ -142,6 +144,7 @@ frappe.ui.form.on('Salary Structure', { toggle_fields: function(frm) { frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet); frm.toggle_reqd(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet); + frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); } }); diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.json b/erpnext/hr/doctype/salary_structure/salary_structure.json index 8a9068da00..5c0a635901 100644 --- a/erpnext/hr/doctype/salary_structure/salary_structure.json +++ b/erpnext/hr/doctype/salary_structure/salary_structure.json @@ -95,6 +95,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)", + "fieldname": "payroll_frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payroll Frequency", + "length": 0, + "no_copy": 0, + "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "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, @@ -863,7 +894,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-07 05:13:46.584365", + "modified": "2016-12-07 14:57:22.083825", "modified_by": "Administrator", "module": "HR", "name": "Salary Structure", diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index 81f674391d..83ea12e352 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -8,7 +8,7 @@ import erpnext from frappe.utils.make_random import get_random from frappe.utils import nowdate, add_days, add_years from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.hr.doctype.salary_slip.test_salary_slip import make_salary_component +from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component, make_deduction_salary_component # test_records = frappe.get_test_records('Salary Structure') class TestSalaryStructure(unittest.TestCase): @@ -24,7 +24,8 @@ class TestSalaryStructure(unittest.TestCase): self.make_holiday_list() frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") - make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"]) + make_earning_salary_component(["Basic Salary", "Allowance", "HRA"]) + make_deduction_salary_component(["Professional Tax", "TDS"]) employee1 = self.make_employee("test_employee@salary.com") employee2 = self.make_employee("test_employee_2@salary.com") @@ -90,6 +91,7 @@ def make_salary_slip_from_salary_structure(employee): sal_slip.month = "11" sal_slip.fiscal_year = "_Test Fiscal Year 2016" sal_slip.posting_date = nowdate() + sal_slip.payroll_frequency = "Monthly" sal_slip.insert() sal_slip.submit() return sal_slip @@ -104,6 +106,7 @@ def make_salary_structure(sal_struct): "employees": get_employee_details(), "earnings": get_earnings_component(), "deductions": get_deductions_component(), + "payroll_frequency": "Monthly", "payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") }).insert() return sal_struct diff --git a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json index 1789c75e85..0c38d4541a 100644 --- a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json +++ b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json @@ -6,7 +6,7 @@ "docstatus": 0, "doctype": "Print Format", "font": "Default", - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \"

{{doc.name}}

\\n
\\n
\\n
\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_days_in_month\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \"

{{doc.name}}

\\n
\\n
\\n
\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_working_days\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]", "idx": 0, "modified": "2016-08-22 00:21:42.600548", "modified_by": "Administrator", diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 98866abc76..11d0b1ce6e 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -7,7 +7,7 @@ import frappe import unittest import datetime from frappe.utils.make_random import get_random -from frappe.utils import now_datetime, nowdate +from frappe.utils import now_datetime, nowdate, add_days from erpnext.projects.doctype.timesheet.timesheet import OverlapError from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -76,7 +76,7 @@ def make_salary_structure(employee): salary_structure = frappe.new_doc("Salary Structure") salary_structure.name = "Timesheet Salary Structure Test" salary_structure.salary_slip_based_on_timesheet = 1 - salary_structure.from_date = nowdate() + salary_structure.from_date = add_days(nowdate(), -30) salary_structure.salary_component = "Basic" salary_structure.hour_rate = 50.0 salary_structure.company = "_Test Company"