diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js index 8cf6909a125..7859a470cc8 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js @@ -2,7 +2,55 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Benefit Application', { - refresh: function(frm) { - + setup: function(frm) { + frm.set_query("earning_component", "employee_benefits", function() { + return { + filters: { + type: "Earning", + is_flexible_benefit: true, + disabled: false + } + }; + }); + }, + employee: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits", + args:{ + employee: frm.doc.employee, + on_date: frm.doc.date + }, + callback: function (data) { + if(!data.exc){ + if(data.message){ + frm.set_value("max_benefits", data.message); + } + } + } + }); } }); + +frappe.ui.form.on("Employee Benefit Application Detail",{ + amount: function(frm, cdt, cdn) { + calculate_all(frm.doc, cdt, cdn); + } +}); + +var calculate_all = function(doc, dt, dn) { + var tbl = doc.employee_benefits || []; + var pro_rata_dispensed_amount = 0; + var total_amount = 0; + for(var i = 0; i < tbl.length; i++){ + if(cint(tbl[i].amount) > 0) { + total_amount += flt(tbl[i].amount); + } + if(tbl[i].is_pro_rata_applicable == 1){ + pro_rata_dispensed_amount += flt(tbl[i].amount) + } + } + doc.total_amount = total_amount; + doc.remainig_benefits = doc.max_benefits - total_amount; + doc.pro_rata_dispensed_amount = pro_rata_dispensed_amount; + refresh_many(['pro_rata_dispensed_amount', 'total_amount','remainig_benefits']); +}; diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.json index 0fff4fdab23..3c1c8aaee21 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.json +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.json @@ -77,6 +77,68 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "max_benefits", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Max Benefits (Yearly)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "remainig_benefits", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Remainig Benefits (Yearly)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -107,6 +169,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Date", + "length": 0, + "no_copy": 0, + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -139,6 +233,68 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "pro_rata_dispensed_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Pro Rata Dispensed Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -243,7 +399,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-14 15:35:20.748301", + "modified": "2018-05-11 16:58:31.662866", "modified_by": "Administrator", "module": "HR", "name": "Employee Benefit Application", diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py index 9282d984755..8e59bf556b9 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py @@ -4,7 +4,132 @@ from __future__ import unicode_literals import frappe +from frappe import _ +from frappe.utils import nowdate, date_diff, getdate from frappe.model.document import Document +from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days class EmployeeBenefitApplication(Document): - pass + def validate(self): + if self.max_benefits <= 0: + frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) + self.validate_max_benefit_for_component() + + def before_submit(self): + self.validate_duplicate_on_payroll_period() + + def validate_max_benefit_for_component(self): + if self.employee_benefits: + max_benefit_amount = 0 + for employee_benefit in self.employee_benefits: + self.validate_max_benefit(employee_benefit.earning_component) + max_benefit_amount += employee_benefit.amount + if max_benefit_amount > self.max_benefits: + frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, self.max_benefits)) + + def validate_max_benefit(self, earning_component_name): + max_benefit_amount = frappe.db.get_value("Salary Component", earning_component_name, "max_benefit_amount") + benefit_amount = 0 + for employee_benefit in self.employee_benefits: + if employee_benefit.earning_component == earning_component_name: + benefit_amount += employee_benefit.amount + if benefit_amount > max_benefit_amount: + frappe.throw(_("Maximum benefit amount of component {0} exceeds {1}").format(earning_component_name, max_benefit_amount)) + + def validate_duplicate_on_payroll_period(self): + application = frappe.db.exists( + "Employee Benefit Application", + { + 'employee': self.employee, + 'payroll_period': self.payroll_period, + 'docstatus': 1 + } + ) + if application: + frappe.throw(_("Employee {0} already submited an apllication {1} for the payroll period {2}").format(self.employee, application, self.payroll_period)) + +@frappe.whitelist() +def get_max_benefits(employee, on_date): + sal_struct = get_assigned_salary_sturecture(employee, on_date) + if sal_struct: + max_benefits = frappe.db.get_value("Salary Structure", sal_struct[0][0], "max_benefits") + if max_benefits > 0: + return max_benefits + else: + frappe.throw(_("Employee {0} has no max benefits in salary structure {1}").format(employee, sal_struct[0][0])) + else: + frappe.throw(_("Employee {0} has no salary structure assigned").format(employee)) + + +@frappe.whitelist() +def get_assigned_salary_sturecture(employee, _date): + if not _date: + _date = nowdate() + salary_structure = frappe.db.sql(""" + select salary_structure from `tabSalary Structure Assignment` + where employee=%(employee)s + and docstatus = 1 + and ( + (%(_date)s between from_date and ifnull(to_date, '2199-12-31')) + )""", { + 'employee': employee, + '_date': _date, + }) + if salary_structure: + return salary_structure + +def get_employee_benefit_application(salary_slip): + employee_benefits = frappe.db.sql(""" + select name from `tabEmployee Benefit Application` + where employee=%(employee)s + and docstatus = 1 + and (date between %(start_date)s and %(end_date)s) + """, { + 'employee': salary_slip.employee, + 'start_date': salary_slip.start_date, + 'end_date': salary_slip.end_date + }) + + if employee_benefits: + for employee_benefit in employee_benefits: + employee_benefit_obj = frappe.get_doc("Employee Benefit Application", employee_benefit[0]) + return get_components(employee_benefit_obj, salary_slip) + +def get_components(employee_benefit_application, salary_slip): + salary_components_array = [] + group_component_amount = {} + payroll_period_days = get_payroll_period_days(salary_slip.start_date, salary_slip.end_date, salary_slip.company) + for employee_benefit in employee_benefit_application.employee_benefits: + if employee_benefit.is_pro_rata_applicable == 1: + struct_row = {} + salary_components_dict = {} + amount = get_amount(payroll_period_days, salary_slip.start_date, salary_slip.end_date, employee_benefit.amount) + sc = frappe.get_doc("Salary Component", employee_benefit.earning_component) + salary_component = sc + if sc.earning_component_group and not sc.is_group and not sc.flexi_default: + salary_component = frappe.get_doc("Salary Component", sc.earning_component_group) + if group_component_amount and group_component_amount.has_key(sc.earning_component_group): + group_component_amount[sc.earning_component_group] += amount + else: + group_component_amount[sc.earning_component_group] = amount + amount = group_component_amount[sc.earning_component_group] + struct_row['depends_on_lwp'] = salary_component.depends_on_lwp + struct_row['salary_component'] = salary_component.name + struct_row['abbr'] = salary_component.salary_component_abbr + struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total + salary_components_dict['amount'] = amount + salary_components_dict['struct_row'] = struct_row + salary_components_array.append(salary_components_dict) + + if len(salary_components_array) > 0: + return salary_components_array + return False + +def get_amount(payroll_period_days, start_date, end_date, amount): + salary_slip_days = date_diff(getdate(end_date), getdate(start_date)) + 1 + amount_per_day = amount / payroll_period_days + total_amount = amount_per_day * salary_slip_days + if total_amount > amount: + return amount + else: + return total_amount diff --git a/erpnext/hr/doctype/employee_benefit_application/test_employee_benefit_application.py b/erpnext/hr/doctype/employee_benefit_application/test_employee_benefit_application.py index 9b915a66b9d..34e1a8fbc1d 100644 --- a/erpnext/hr/doctype/employee_benefit_application/test_employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/test_employee_benefit_application.py @@ -2,8 +2,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals - -import frappe import unittest class TestEmployeeBenefitApplication(unittest.TestCase): diff --git a/erpnext/hr/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json b/erpnext/hr/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json index 35918e17228..37f32429e04 100644 --- a/erpnext/hr/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json +++ b/erpnext/hr/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json @@ -45,6 +45,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_pro_rata_applicable", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Pro-rata Applicable", + "length": 0, + "no_copy": 0, + "options": "earning_component.is_pro_rata_applicable", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -87,7 +119,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 19:32:35.064272", + "modified": "2018-05-15 12:03:25.545041", "modified_by": "Administrator", "module": "HR", "name": "Employee Benefit Application Detail", diff --git a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.js b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.js index 816b1bd90af..64fde037633 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.js +++ b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.js @@ -2,7 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Benefit Claim', { - refresh: function(frm) { - + setup: function(frm) { + frm.set_query("earning_component", function() { + return { + filters: { + type: "Earning", + is_flexible_benefit: true, + disabled: false + } + }; + }); } }); diff --git a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.json index 00d5159c512..9da7cdb53b5 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.json +++ b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.json @@ -188,6 +188,7 @@ "label": "Max Amount Eligible", "length": 0, "no_copy": 0, + "options": "earning_component.max_benefit_amount", "permlevel": 0, "precision": "", "print_hide": 0, @@ -201,6 +202,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_pro_rata_applicable", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Pro-Rata Applicable", + "length": 0, + "no_copy": 0, + "options": "earning_component.is_pro_rata_applicable", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -367,7 +400,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-14 15:38:41.538646", + "modified": "2018-05-16 17:21:25.598531", "modified_by": "Administrator", "module": "HR", "name": "Employee Benefit Claim", @@ -460,4 +493,4 @@ "title_field": "employee_name", "track_changes": 1, "track_seen": 0 -} \ No newline at end of file +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py index 551d1accd5a..39b3540e06a 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -4,7 +4,62 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits class EmployeeBenefitClaim(Document): - pass + def validate(self): + if not self.is_pro_rata_applicable: + self.validate_max_benefit_for_sal_struct() + # TODO: Validate all cases + + def validate_max_benefit_for_sal_struct(self): + max_benefits = get_max_benefits(self.employee, self.claim_date) + if self.claimed_amount > max_benefits: + frappe.throw(_("Maximum benefit amount of employee {0} exceeds {1}").format(self.employee, max_benefits)) + + +def get_employee_benefit_claim(salary_slip): + employee_benefits = frappe.db.sql(""" + select name from `tabEmployee Benefit Claim` + where employee=%(employee)s + and docstatus = 1 and is_pro_rata_applicable = 0 + and (claim_date between %(start_date)s and %(end_date)s) + """, { + 'employee': salary_slip.employee, + 'start_date': salary_slip.start_date, + 'end_date': salary_slip.end_date + }) + + if employee_benefits: + salary_components_array = [] + for employee_benefit in employee_benefits: + struct_row = {} + salary_components_dict = {} + group_component_amount = {} + + employee_benefit_claim = frappe.get_doc("Employee Benefit Claim", employee_benefit[0]) + amount = employee_benefit_claim.claimed_amount + sc = frappe.get_doc("Salary Component", employee_benefit_claim.earning_component) + + salary_component = sc + if sc.earning_component_group and not sc.is_group and not sc.flexi_default: + salary_component = frappe.get_doc("Salary Component", sc.earning_component_group) + if group_component_amount and group_component_amount.has_key(sc.earning_component_group): + group_component_amount[sc.earning_component_group] += amount + else: + group_component_amount[sc.earning_component_group] = amount + amount = group_component_amount[sc.earning_component_group] + + struct_row['depends_on_lwp'] = salary_component.depends_on_lwp + struct_row['salary_component'] = salary_component.name + struct_row['abbr'] = salary_component.salary_component_abbr + struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total + salary_components_dict['amount'] = amount + salary_components_dict['struct_row'] = struct_row + salary_components_array.append(salary_components_dict) + + if len(salary_components_array) > 0: + return salary_components_array + return False diff --git a/erpnext/hr/doctype/employee_benefit_claim/test_employee_benefit_claim.py b/erpnext/hr/doctype/employee_benefit_claim/test_employee_benefit_claim.py index e8dc0da47ca..aff73e5c816 100644 --- a/erpnext/hr/doctype/employee_benefit_claim/test_employee_benefit_claim.py +++ b/erpnext/hr/doctype/employee_benefit_claim/test_employee_benefit_claim.py @@ -2,8 +2,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals - -import frappe import unittest class TestEmployeeBenefitClaim(unittest.TestCase): diff --git a/erpnext/hr/doctype/payroll_period/payroll_period.py b/erpnext/hr/doctype/payroll_period/payroll_period.py index 1e18388e16c..a818bc42765 100644 --- a/erpnext/hr/doctype/payroll_period/payroll_period.py +++ b/erpnext/hr/doctype/payroll_period/payroll_period.py @@ -4,7 +4,26 @@ from __future__ import unicode_literals import frappe +from frappe.utils import date_diff, getdate from frappe.model.document import Document class PayrollPeriod(Document): pass + +def get_payroll_period_days(start_date, end_date, company): + payroll_period_dates = frappe.db.sql(""" + select ppd.start_date, ppd.end_date from `tabPayroll Period Date` ppd, `tabPayroll Period` pp + where pp.company=%(company)s + and ppd.parent = pp.name + and ( + (%(start_date)s between ppd.start_date and ppd.end_date) + or (%(end_date)s between ppd.start_date and ppd.end_date) + or (ppd.start_date between %(start_date)s and %(end_date)s) + )""", { + 'company': company, + 'start_date': start_date, + 'end_date': end_date + }) + + if len(payroll_period_dates) > 0: + return date_diff(getdate(payroll_period_dates[0][1]), getdate(payroll_period_dates[0][0])) + 1 diff --git a/erpnext/hr/doctype/salary_component/salary_component.js b/erpnext/hr/doctype/salary_component/salary_component.js index 86203ab44e7..e58a05e7546 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.js +++ b/erpnext/hr/doctype/salary_component/salary_component.js @@ -12,7 +12,15 @@ frappe.ui.form.on('Salary Component', { "is_group": 0, "company": d.company } - } - }) + }; + }); + frm.set_query("earning_component_group", function(frm) { + return { + filters: { + "is_group": 1, + "is_flexible_benefit": 1 + } + }; + }); } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json index cf85af4a769..6764e0ca922 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.json +++ b/erpnext/hr/doctype/salary_component/salary_component.json @@ -108,70 +108,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.type==\"Earning\"", - "fieldname": "is_tax_applicable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Tax Applicable", - "length": 0, - "no_copy": 0, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "is_payable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Payable", - "length": 0, - "no_copy": 0, - "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, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -366,6 +302,38 @@ "collapsible": 0, "columns": 0, "depends_on": "is_flexible_benefit", + "fieldname": "is_group", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Group", + "length": 0, + "no_copy": 0, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.is_flexible_benefit && !doc.is_group && !doc.flexi_default", "fieldname": "earning_component_group", "fieldtype": "Link", "hidden": 0, @@ -454,6 +422,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.is_flexible_benefit && doc.is_pro_rata_applicable", + "fieldname": "flexi_default", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Flexible Component", + "length": 0, + "no_copy": 0, + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -618,7 +618,6 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "is_payable", "fieldname": "section_break_5", "fieldtype": "Section Break", "hidden": 0, @@ -1002,7 +1001,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-16 12:15:43.117948", + "modified": "2018-05-16 12:27:03.005070", "modified_by": "Administrator", "module": "HR", "name": "Salary Component", @@ -1037,4 +1036,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/salary_component/salary_component.py b/erpnext/hr/doctype/salary_component/salary_component.py index beffaec21f7..132930f5635 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.py +++ b/erpnext/hr/doctype/salary_component/salary_component.py @@ -4,12 +4,27 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from frappe.model.naming import append_number_if_name_exists class SalaryComponent(Document): def validate(self): self.validate_abbr() + self.validate_flexi_default() + + def validate_flexi_default(self): + if self.is_flexible_benefit and self.is_pro_rata_applicable and self.flexi_default: + salary_component = frappe.db.exists( + 'Salary Component', + { + 'is_flexible_benefit': 1, + 'is_pro_rata_applicable': 1, + 'flexi_default': 1 + } + ) + if salary_component and salary_component != self.name: + frappe.throw(_("{0} is already marked as default flexible component").format(salary_component)) def validate_abbr(self): if not self.salary_component_abbr: diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 53e6aa437e7..87511b6e9da 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -13,6 +13,9 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue from erpnext.hr.doctype.additional_salary_component.additional_salary_component import get_additional_salary_component +from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_employee_benefit_application, get_amount +from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days +from erpnext.hr.doctype.employee_benefit_claim.employee_benefit_claim import get_employee_benefit_claim from erpnext.hr.utils import get_payroll_period class SalarySlip(TransactionBase): @@ -68,7 +71,57 @@ class SalarySlip(TransactionBase): if additional_components: for additional_component in additional_components: additional_component = frappe._dict(additional_component) - self.update_component_row(frappe._dict(additional_component.struct_row), additional_component.amount, "earnings") + amount = self.update_amount_for_other_component(frappe._dict(additional_component.struct_row).salary_component, additional_component.amount) + self.update_component_row(frappe._dict(additional_component.struct_row), amount, "earnings") + + max_benefits = self._salary_structure_doc.get("max_benefits") + if max_benefits > 0: + employee_benefits = get_employee_benefit_application(self) + if employee_benefits: + for employee_benefit in employee_benefits: + benefit_component = frappe._dict(employee_benefit) + amount = self.update_amount_for_other_component(frappe._dict(benefit_component.struct_row).salary_component, benefit_component.amount) + self.update_component_row(frappe._dict(benefit_component.struct_row), amount, "earnings") + else: + default_flexi_compenent = frappe.db.exists( + 'Salary Component', + { + 'is_flexible_benefit': 1, + 'is_pro_rata_applicable': 1, + 'flexi_default': 1 + } + ) + if default_flexi_compenent: + flexi_struct_row = self.create_flexi_struct_row(default_flexi_compenent) + payroll_period_days = get_payroll_period_days(self.start_date, self.end_date, self.company) + amount = self.update_amount_for_other_component(default_flexi_compenent, get_amount(payroll_period_days, self.start_date, self.end_date, max_benefits)) + self.update_component_row(flexi_struct_row, amount, "earnings") + else: + frappe.throw(_("Configure default flexible benefit salary component for apply pro-rata benefit")) + + benefit_claims = get_employee_benefit_claim(self) + if benefit_claims: + for benefit_claim in benefit_claims: + benefit_component = frappe._dict(benefit_claim) + amount = self.update_amount_for_other_component(frappe._dict(benefit_component.struct_row).salary_component, benefit_component.amount) + self.update_component_row(frappe._dict(benefit_component.struct_row), amount, "earnings") + + def update_amount_for_other_component(self, salary_component, new_amount): + amount = new_amount + for d in self.get("earnings"): + if d.salary_component == salary_component: + d.amount += new_amount + amount = d.amount + return amount + + def create_flexi_struct_row(self, default_flexi_compenent): + salary_component = frappe.get_doc("Salary Component", default_flexi_compenent) + flexi_struct_row = {} + flexi_struct_row['depends_on_lwp'] = salary_component.depends_on_lwp + flexi_struct_row['salary_component'] = salary_component.name + flexi_struct_row['abbr'] = salary_component.salary_component_abbr + flexi_struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total + return frappe._dict(flexi_struct_row) def update_component_row(self, struct_row, amount, key): component_row = None @@ -181,6 +234,7 @@ class SalarySlip(TransactionBase): st_name = frappe.db.sql("""select salary_structure from `tabSalary Structure Assignment` where employee=%s and (from_date <= %s or from_date <= %s) and (to_date is null or to_date >= %s or to_date >= %s) + and docstatus = 1 and salary_structure in (select name from `tabSalary Structure` where is_active = 'Yes'%s) """% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date)) diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index 8fd51d990f9..9e62b6640e6 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -123,15 +123,6 @@ frappe.ui.form.on('Salary Structure', { } }); -frappe.ui.form.on('Salary Structure Employee', { - from_date: function(frm, cdt, cdn) { - validate_date(frm, cdt, cdn); - }, - to_date: function(frm, cdt, cdn) { - validate_date(frm, cdt, cdn); - } -}); - var validate_date = function(frm, cdt, cdn) { var doc = locals[cdt][cdn]; if(doc.to_date && doc.from_date) {