diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json index 9dee848029..c5691f72ee 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json @@ -139,6 +139,37 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_exemption_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 Exemption 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 +274,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-15 16:16:46.075493", + "modified": "2018-05-16 19:03:57.624215", "modified_by": "Administrator", "module": "HR", "name": "Employee Tax Exemption Declaration", diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index 52746d4cff..22e1638751 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -19,3 +19,6 @@ class EmployeeTaxExemptionDeclaration(Document): "docstatus": 1}): frappe.throw(_("Tax Declaration of {0} for period {1} already submitted.")\ .format(self.employee, self.payroll_period), frappe.DocstatusTransitionError) + self.total_exemption_amount = 0 + for item in self.declarations: + self.total_exemption_amount += item.amount diff --git a/erpnext/hr/doctype/salary_component/salary_component.py b/erpnext/hr/doctype/salary_component/salary_component.py index 9108f31f9b..beffaec21f 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.py +++ b/erpnext/hr/doctype/salary_component/salary_component.py @@ -18,4 +18,13 @@ class SalaryComponent(Document): self.salary_component_abbr = self.salary_component_abbr.strip() self.salary_component_abbr = append_number_if_name_exists('Salary Component', self.salary_component_abbr, - 'salary_component_abbr', separator='_', filters={"name": ["!=", self.name]}) \ No newline at end of file + 'salary_component_abbr', separator='_', filters={"name": ["!=", self.name]}) + + def calculate_tax(self, annual_earning): + taxable_amount = 0 + for slab in self.taxable_salary_slabs: + if annual_earning > slab.from_amount and annual_earning < slab.to_amount: + taxable_amount += (annual_earning - slab.from_amount) * slab.percent_deduction *.01 + elif annual_earning > slab.from_amount and annual_earning > slab.to_amount: + taxable_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01 + return taxable_amount diff --git a/erpnext/hr/doctype/salary_detail/salary_detail.json b/erpnext/hr/doctype/salary_detail/salary_detail.json index 01d02779b7..862728afb7 100644 --- a/erpnext/hr/doctype/salary_detail/salary_detail.json +++ b/erpnext/hr/doctype/salary_detail/salary_detail.json @@ -40,6 +40,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -72,6 +73,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -101,6 +103,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -132,6 +135,100 @@ "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": "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, + "fieldname": "is_flexible_benefit", + "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 Flexible Benefit", + "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, + "fieldname": "variable_based_on_taxable_salary", + "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": "Variable Based On Taxable Salary", + "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 }, { @@ -161,6 +258,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -192,6 +290,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -225,6 +324,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -258,6 +358,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -290,6 +391,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -321,6 +423,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -351,6 +454,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -383,6 +487,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -413,6 +518,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -445,6 +551,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -458,7 +565,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-10-02 13:57:22.769751", + "modified": "2018-05-16 22:42:59.974450", "modified_by": "Administrator", "module": "HR", "name": "Salary Detail", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 984a78c75f..53e6aa437e 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -13,6 +13,7 @@ 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.utils import get_payroll_period class SalarySlip(TransactionBase): def autoname(self): @@ -58,6 +59,10 @@ class SalarySlip(TransactionBase): amount = self.eval_condition_and_formula(struct_row, data) if amount and struct_row.statistical_component == 0: self.update_component_row(struct_row, amount, key) + if key=="deductions" and struct_row.variable_based_on_taxable_salary: + tax_row, amount = self.calculate_pro_rata_tax(struct_row.salary_component) + if tax_row and amount: + self.update_component_row(frappe._dict(tax_row), amount, key) additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) if additional_components: @@ -464,6 +469,52 @@ class SalarySlip(TransactionBase): status = "Cancelled" return status + def calculate_pro_rata_tax(self, salary_component): + # Calculate total tax payable earnings + tax_applicable_components = [] + for earning in self._salary_structure_doc.earnings: + #all tax applicable earnings which are not flexi + if earning.is_tax_applicable and not earning.is_flexible_benefit: + tax_applicable_components.append(earning.salary_component) + total_taxable_earning = 0 + for earning in self.earnings: + if earning.salary_component in tax_applicable_components: + total_taxable_earning += earning.amount + + # Get payroll period, prorata frequency + days = date_diff(self.end_date, self.start_date) + 1 + payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) + if not payroll_period: + frappe.throw(_("Start and end dates not in a valid Payroll Period")) + total_days = date_diff(payroll_period.end_date, payroll_period.start_date) + 1 + prorata_frequency = flt(total_days)/flt(days) + annual_earning = total_taxable_earning * prorata_frequency + + # Calculate total exemption declaration + exemption_amount = 0 + if frappe.db.exists("Employee Tax Exemption Declaration", {"employee": self.employee, + "payroll_period": payroll_period.name, "docstatus": 1}): + exemption_amount = frappe.db.get_value("Employee Tax Exemption Declaration", + {"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, #fix period + "total_exemption_amount") + annual_earning = annual_earning - exemption_amount + + # Get tax calc by component + component = frappe.get_doc("Salary Component", salary_component) + annual_tax = component.calculate_tax(annual_earning) + + # Calc prorata tax + pro_rata_tax = annual_tax/prorata_frequency + + # Data for update_component_row + struct_row = {} + struct_row['depends_on_lwp'] = 0 + struct_row['salary_component'] = component.name + struct_row['abbr'] = component.salary_component_abbr + struct_row['do_not_include_in_total'] = 0 + + return struct_row, pro_rata_tax + def unlink_ref_doc_from_salary_slip(ref_no): linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` where journal_entry=%s and docstatus < 2""", (ref_no)) diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index ca92234461..8fd51d990f 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -201,17 +201,20 @@ frappe.ui.form.on('Salary Detail', { callback: function(data) { if(data.message){ var result = data.message; - frappe.model.set_value(cdt, cdn, 'condition',result.condition); - frappe.model.set_value(cdt, cdn, 'amount_based_on_formula',result.amount_based_on_formula); + frappe.model.set_value(cdt, cdn, 'condition', result.condition); + frappe.model.set_value(cdt, cdn, 'amount_based_on_formula', result.amount_based_on_formula); if(result.amount_based_on_formula == 1){ - frappe.model.set_value(cdt, cdn, 'formula',result.formula); + frappe.model.set_value(cdt, cdn, 'formula', result.formula); } else{ - frappe.model.set_value(cdt, cdn, 'amount',result.amount); + frappe.model.set_value(cdt, cdn, 'amount', result.amount); } - frappe.model.set_value(cdt, cdn, 'statistical_component',result.statistical_component); - frappe.model.set_value(cdt, cdn, 'depends_on_lwp',result.depends_on_lwp); - frappe.model.set_value(cdt, cdn, 'do_not_include_in_total',result.do_not_include_in_total); + frappe.model.set_value(cdt, cdn, 'statistical_component', result.statistical_component); + frappe.model.set_value(cdt, cdn, 'depends_on_lwp', result.depends_on_lwp); + frappe.model.set_value(cdt, cdn, 'do_not_include_in_total', result.do_not_include_in_total); + frappe.model.set_value(cdt, cdn, 'variable_based_on_taxable_salary', result.variable_based_on_taxable_salary); + frappe.model.set_value(cdt, cdn, 'is_tax_applicable', result.is_tax_applicable); + frappe.model.set_value(cdt, cdn, 'is_flexible_benefit', result.is_flexible_benefit); refresh_field("earnings"); refresh_field("deductions"); } diff --git a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json index bd0ec6be31..8e6c1d86ae 100644 --- a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json +++ b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json @@ -50,7 +50,7 @@ "collapsible": 0, "columns": 0, "fieldname": "to_amount", - "fieldtype": "Data", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -147,7 +147,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 20:09:36.675987", + "modified": "2018-05-16 18:18:23.802576", "modified_by": "Administrator", "module": "HR", "name": "Taxable Salary Slab", diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index df720c4cc6..20fe666d2b 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -234,3 +234,10 @@ def get_leave_period(from_date, to_date, company): if leave_period: return leave_period + +def get_payroll_period(from_date, to_date, company): + payroll_period = frappe.db.sql("""select pp.name, pd.start_date, pd.end_date from + `tabPayroll Period Date` pd join `tabPayroll Period` pp on + pd.parent=pp.name where pd.start_date<=%s and pd.end_date>= %s + and pp.company=%s""", (from_date, to_date, company), as_dict=1) + return payroll_period[0] if payroll_period else None