From fc2cb3a85e4fd24f767b4c1e3bbd7ea321d0f4c5 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 10 Aug 2020 11:52:34 +0530 Subject: [PATCH 01/27] Feat: gratuity --- erpnext/hr/doctype/employee/employee.json | 2 +- erpnext/payroll/doctype/gratuity/__init__.py | 0 erpnext/payroll/doctype/gratuity/gratuity.js | 46 +++++ .../payroll/doctype/gratuity/gratuity.json | 192 ++++++++++++++++++ erpnext/payroll/doctype/gratuity/gratuity.py | 113 +++++++++++ .../payroll/doctype/gratuity/test_gratuity.py | 10 + .../gratuity_applicable_component/__init__.py | 0 .../gratuity_applicable_component.json | 32 +++ .../gratuity_applicable_component.py | 10 + .../payroll/doctype/gratuity_rule/__init__.py | 0 .../doctype/gratuity_rule/gratuity_rule.js | 8 + .../doctype/gratuity_rule/gratuity_rule.json | 88 ++++++++ .../doctype/gratuity_rule/gratuity_rule.py | 10 + .../gratuity_rule/test_gratuity_rule.py | 10 + .../doctype/gratuity_rule_slab/__init__.py | 0 .../gratuity_rule_slab.json | 45 ++++ .../gratuity_rule_slab/gratuity_rule_slab.py | 10 + 17 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 erpnext/payroll/doctype/gratuity/__init__.py create mode 100644 erpnext/payroll/doctype/gratuity/gratuity.js create mode 100644 erpnext/payroll/doctype/gratuity/gratuity.json create mode 100644 erpnext/payroll/doctype/gratuity/gratuity.py create mode 100644 erpnext/payroll/doctype/gratuity/test_gratuity.py create mode 100644 erpnext/payroll/doctype/gratuity_applicable_component/__init__.py create mode 100644 erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json create mode 100644 erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py create mode 100644 erpnext/payroll/doctype/gratuity_rule/__init__.py create mode 100644 erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js create mode 100644 erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json create mode 100644 erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py create mode 100644 erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py create mode 100644 erpnext/payroll/doctype/gratuity_rule_slab/__init__.py create mode 100644 erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json create mode 100644 erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 4f1c04ff5d..b60e39282b 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2020-10-16 15:02:04.283657", + "modified": "2020-12-02 15:58:23.805489", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/payroll/doctype/gratuity/__init__.py b/erpnext/payroll/doctype/gratuity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js new file mode 100644 index 0000000000..cbf5119061 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -0,0 +1,46 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Gratuity', { + refresh: function(frm){ + if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + frm.add_custom_button(__("Make Payment Entry"), function() { + frm.trigger('make_payment_entry'); + }); + } + }, + onload: function(frm){ + frm.set_query('salary_component', function() { + return { + filters: { + type: "Earning" + } + }; + }); + }, + employee: function(frm) { + frm.events.calculate_work_experience_and_amount(frm); + }, + gratuity_rule: function(frm){ + frm.events.calculate_work_experience_and_amount(frm); + }, + calculate_work_experience_and_amount: function(frm) { + + if(frm.doc.employee && frm.doc.gratuity_rule){ + frappe.call({ + method:"erpnext.payroll.doctype.gratuity.gratuity.calculate_work_experience_and_amount", + args:{ + employee: frm.doc.employee, + gratuity_rule: frm.doc.gratuity_rule + } + }).then((r) => { + frm.set_value("current_work_experience", r.message['current_work_experience']); + frm.set_value("amount", r.message['amount']); + }); + } + }, + make_payment_entry: function(frm){ + console.log("Hello"); + } + +}); diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json new file mode 100644 index 0000000000..8e7bb8616b --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -0,0 +1,192 @@ +{ + "actions": [], + "autoname": "HR-GRA-PAY-.#####", + "creation": "2020-08-05 20:52:13.024683", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "designation", + "column_break_3", + "posting_date", + "status", + "company", + "gratuity_rule", + "section_break_5", + "pay_via_salary_slip", + "payroll_date", + "salary_component", + "expense_account", + "mode_of_payment", + "column_break_15", + "current_work_experience", + "amount", + "amended_from" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "1", + "fieldname": "pay_via_salary_slip", + "fieldtype": "Check", + "label": "Pay via Salary Slip" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting date", + "reqd": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 1", + "fieldname": "salary_component", + "fieldtype": "Link", + "label": "Salary Component", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1", + "options": "Salary Component" + }, + { + "default": "0", + "fieldname": "current_work_experience", + "fieldtype": "Int", + "label": "Current Work Experience", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Draft\nUnpaid\nPaid", + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Account" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Mode of Payment" + }, + { + "fieldname": "gratuity_rule", + "fieldtype": "Link", + "label": "Gratuity Rule", + "options": "Gratuity Rule", + "reqd": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Payment Configuration" + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Data", + "label": "Designation", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Gratuity", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 1", + "fieldname": "payroll_date", + "fieldtype": "Date", + "label": "Payroll Date", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-08-06 15:51:16.047698", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py new file mode 100644 index 0000000000..fe31f4d7a6 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, bold +from frappe.model.document import Document + +from dateutil.relativedelta import relativedelta + +class Gratuity(Document): + def validate(self): + calculate_work_experience_and_amount(self.employee, self.gratuity_rule) + + def on_submit(self): + if self.pay_via_salary_slip: + additional_salary = frappe.new_doc('Additional Salary') + additional_salary.employee = self.employee + additional_salary.salary_component = self.salary_component + additional_salary.overwrite_salary_structure_amount = 0 + additional_salary.amount = self.amount + additional_salary.payroll_date = self.payroll_date + additional_salary.company = self.company + additional_salary.ref_doctype = self.doctype + additional_salary.ref_docname = self.name + additional_salary.submit() + self.status = "Paid" + else: + self.status = "Unpaid" + +@frappe.whitelist() +def calculate_work_experience_and_amount(employee, gratuity_rule): + current_work_experience = calculate_work_experience(employee, gratuity_rule) or 0 + gratuity_amount = calculate_gratuity_amount(employee, gratuity_rule, current_work_experience) or 0 + + return {'current_work_experience': current_work_experience, "amount": gratuity_amount} + +def calculate_work_experience(employee, gratuity_rule): + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + if not relieving_date: + frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(employee))) + + time_difference = relativedelta(relieving_date, date_of_joining) + method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") + + current_work_experience = time_difference.years + + if method == "Round off Work Experience": + if time_difference.months >= 6 and time_difference.days > 0: + current_work_experience += 1 + + return current_work_experience + +def calculate_gratuity_amount(employee, gratuity_rule, experience): + applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) + applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] + + slabs = get_gratuity_rule_slabs(gratuity_rule) + + total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) + + + fraction_to_be_paid = 0 + + for slab in slabs: + if experience > slab.get("from", 0) and (slab.to == 0 or experience < slab.to): + fraction_to_be_paid = slab.fraction_of_applicable_earnings + if fraction_to_be_paid: + break + + gratuity_amount = total_applicable_components_amount * experience * fraction_to_be_paid + + return gratuity_amount + +def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): + calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") + if calculate_gratuity_amount_based_on == "Last Month Salary": + sal_slip = get_last_salary_slip(employee) + + if not sal_slip: + frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) + + component_and_amounts = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': ('in', applicable_earnings_component) + }, + fields=["amount"]) + total_applicable_components_amount = 0 + if not len(component_and_amounts): + frappe.throw("No Applicable Component is present in last month salary slip") + for data in component_and_amounts: + total_applicable_components_amount += data.amount + elif calculate_gratuity_amount_based_on == "Actual Salary": + pass + + return total_applicable_components_amount + +def get_gratuity_rule_slabs(gratuity_rule): + return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"]) + +def get_salary_structure(employee): + return frappe.get_list("Salary Structure Assignment", filters = {"employee": employee, 'docstatus': 1}, fields=["from_date", "salary_structure"], order_by = "from_date desc")[0].salary_structure + +def get_last_salary_slip(employee): + return frappe.get_list("Salary Slip", filters = {"employee": employee, 'docstatus': 1}, order_by = "start_date desc")[0].name + + + + diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py new file mode 100644 index 0000000000..92c1248b73 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestGratuity(unittest.TestCase): + pass diff --git a/erpnext/payroll/doctype/gratuity_applicable_component/__init__.py b/erpnext/payroll/doctype/gratuity_applicable_component/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json new file mode 100644 index 0000000000..eea0e852b1 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-08-05 19:00:28.097265", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "salary_component" + ], + "fields": [ + { + "fieldname": "salary_component", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Salary Component ", + "options": "Salary Component", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-08-05 20:17:13.855035", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Applicable Component", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py new file mode 100644 index 0000000000..23e4340b04 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class GratuityApplicableComponent(Document): + pass diff --git a/erpnext/payroll/doctype/gratuity_rule/__init__.py b/erpnext/payroll/doctype/gratuity_rule/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js new file mode 100644 index 0000000000..929370fb17 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Gratuity Rule', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json new file mode 100644 index 0000000000..b5de28173a --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -0,0 +1,88 @@ +{ + "actions": [], + "autoname": "Prompt", + "creation": "2020-08-05 19:00:36.103500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "applicable_earnings_component", + "work_experience_calculation_function", + "column_break_3", + "disable", + "calculate_gratuity_amount_based_on", + "gratuity_rules_section", + "gratuity_rule_slabs" + ], + "fields": [ + { + "default": "0", + "fieldname": "disable", + "fieldtype": "Check", + "label": "Disable" + }, + { + "fieldname": "calculate_gratuity_amount_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Calculate Gratuity Amount Based on", + "options": "Last Month Salary\nActual Salary", + "reqd": 1 + }, + { + "description": "Salary components should be part of the Salary Structure.", + "fieldname": "applicable_earnings_component", + "fieldtype": "Table MultiSelect", + "label": "Applicable Earnings Component", + "options": "Gratuity Applicable Component", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "gratuity_rules_section", + "fieldtype": "Section Break", + "label": "Gratuity Rules" + }, + { + "description": "Leave From and To blank for no upper and lower limit.", + "fieldname": "gratuity_rule_slabs", + "fieldtype": "Table", + "label": "Current Work Experience", + "options": "Gratuity Rule Slab", + "reqd": 1 + }, + { + "default": "Round off Work Experience", + "fieldname": "work_experience_calculation_function", + "fieldtype": "Select", + "label": "Work Experience Calculation method", + "options": "Round off Work Experience\nTake Exact Completed Years" + } + ], + "links": [], + "modified": "2020-08-06 12:28:13.757792", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Rule", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py new file mode 100644 index 0000000000..10b2a87b97 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class GratuityRule(Document): + pass diff --git a/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py new file mode 100644 index 0000000000..1f5dc4e571 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestGratuityRule(unittest.TestCase): + pass diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/__init__.py b/erpnext/payroll/doctype/gratuity_rule_slab/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json new file mode 100644 index 0000000000..615829f45a --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "creation": "2020-08-05 19:12:49.423500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "from", + "to", + "fraction_of_applicable_earnings" + ], + "fields": [ + { + "fieldname": "from", + "fieldtype": "Int", + "in_list_view": 1, + "label": "From(Year)" + }, + { + "fieldname": "to", + "fieldtype": "Int", + "in_list_view": 1, + "label": "To(Year)" + }, + { + "fieldname": "fraction_of_applicable_earnings", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Fraction of Applicable Earnings ", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-08-05 20:03:25.955448", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Rule Slab", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py new file mode 100644 index 0000000000..fa468e77be --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class GratuityRuleSlab(Document): + pass From 49b326fc08dbf44b87e8c97fe6b9582ab6e34449 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 14 Aug 2020 12:53:06 +0530 Subject: [PATCH 02/27] feat(HR): Gratuity Payment --- .../doctype/payment_entry/payment_entry.py | 19 ++- erpnext/payroll/doctype/gratuity/gratuity.js | 24 +++- .../payroll/doctype/gratuity/gratuity.json | 13 +- erpnext/payroll/doctype/gratuity/gratuity.py | 116 +++++++++++++----- .../doctype/gratuity_rule/gratuity_rule.json | 10 +- 5 files changed, 135 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 31a4c8a387..df49667ed2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -242,7 +242,7 @@ class PaymentEntry(AccountsController): elif self.party_type == "Supplier": valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") elif self.party_type == "Employee": - valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") + valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity") elif self.party_type == "Shareholder": valid_reference_doctypes = ("Journal Entry") @@ -604,7 +604,7 @@ class PaymentEntry(AccountsController): if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): if d.allocated_amount \ - and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance"): + and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance", "Gratuity"): frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid() def update_expense_claim(self): @@ -932,6 +932,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre exchange_rate = ref_doc.get("exchange_rate") if party_account_currency != ref_doc.currency: total_amount = flt(total_amount) * flt(exchange_rate) + elif ref_doc.doctype == "Gratuity": + total_amount = ref_doc.amount if not total_amount: if party_account_currency == company_currency: total_amount = ref_doc.base_grand_total @@ -955,6 +957,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre outstanding_amount = flt(outstanding_amount) * flt(exchange_rate) if party_account_currency == company_currency: exchange_rate = 1 + elif reference_doctype == "Gratuity": + outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount) else: outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid) else: @@ -996,7 +1000,7 @@ def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_curre total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges) elif ref_doc.doctype == "Employee Advance": total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc) - + if not total_amount: total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency( party_account_currency, company_currency, ref_doc) @@ -1032,7 +1036,7 @@ def get_total_amount_exchange_rate_base_on_currency(party_account_currency, comp def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency): outstanding_amount, bill_no = None - if reference_doctype in ("Sales Invoice", "Purchase Invoice"): +if reference_doctype in ("Sales Invoice", "Purchase Invoice"): outstanding_amount = ref_doc.get("outstanding_amount") bill_no = ref_doc.get("bill_no") elif reference_doctype == "Expense Claim": @@ -1160,7 +1164,7 @@ def set_party_type(dt): party_type = "Customer" elif dt in ("Purchase Invoice", "Purchase Order"): party_type = "Supplier" - elif dt in ("Expense Claim", "Employee Advance"): + elif dt in ("Expense Claim", "Employee Advance", "Gratuity"): party_type = "Employee" elif dt in ("Fees"): party_type = "Student" @@ -1177,6 +1181,8 @@ def set_party_account(dt, dn, doc, party_type): party_account = doc.advance_account elif dt == "Expense Claim": party_account = doc.payable_account + elif dt == "Gratuity": + party_account = doc.expense_account else: party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) return party_account @@ -1222,6 +1228,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre elif dt == "Dunning": grand_total = doc.grand_total outstanding_amount = doc.grand_total + elif dt == "Gratuity": + grand_total = doc.amount + outstanding_amount = flt(doc.amount) - flt(doc.paid_amount) else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index cbf5119061..d6e93af524 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -5,7 +5,17 @@ frappe.ui.form.on('Gratuity', { refresh: function(frm){ if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { frm.add_custom_button(__("Make Payment Entry"), function() { - frm.trigger('make_payment_entry'); + return frappe.call({ + method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', + args: { + "dt": cur_frm.doc.doctype, + "dn": cur_frm.doc.name + }, + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); }); } }, @@ -17,6 +27,15 @@ frappe.ui.form.on('Gratuity', { } }; }); + frm.set_query("expense_account", function() { + return { + filters: { + "root_type": "Asset", + "is_group": 0, + "company": frm.doc.company + } + }; + }); }, employee: function(frm) { frm.events.calculate_work_experience_and_amount(frm); @@ -38,9 +57,6 @@ frappe.ui.form.on('Gratuity', { frm.set_value("amount", r.message['amount']); }); } - }, - make_payment_entry: function(frm){ - console.log("Hello"); } }); diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index 8e7bb8616b..b8122dfb89 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -24,6 +24,7 @@ "column_break_15", "current_work_experience", "amount", + "paid_amount", "amended_from" ], "fields": [ @@ -77,7 +78,7 @@ "default": "0", "fieldname": "amount", "fieldtype": "Currency", - "label": "Amount", + "label": "Total Amount", "read_only": 1, "reqd": 1 }, @@ -164,11 +165,19 @@ "fieldtype": "Date", "label": "Payroll Date", "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1" + }, + { + "default": "0", + "depends_on": "eval:doc.pay_via_salary_slip == 0", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-08-06 15:51:16.047698", + "modified": "2020-08-14 11:59:15.499548", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index fe31f4d7a6..23cc16b994 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -6,13 +6,19 @@ from __future__ import unicode_literals import frappe from frappe import _, bold from frappe.model.document import Document +from frappe.utils import flt +from math import floor -from dateutil.relativedelta import relativedelta - +from frappe.utils import get_datetime class Gratuity(Document): def validate(self): calculate_work_experience_and_amount(self.employee, self.gratuity_rule) + def before_submit(self): + self.status = "Unpaid" + if self.pay_via_salary_slip: + self.status = "Paid" + def on_submit(self): if self.pay_via_salary_slip: additional_salary = frappe.new_doc('Additional Salary') @@ -25,9 +31,27 @@ class Gratuity(Document): additional_salary.ref_doctype = self.doctype additional_salary.ref_docname = self.name additional_salary.submit() - self.status = "Paid" - else: - self.status = "Unpaid" + + + def set_total_advance_paid(self): + paid_amount = frappe.db.sql(""" + select ifnull(sum(debit_in_account_currency), 0) as paid_amount + from `tabGL Entry` + where against_voucher_type = 'Gratuity' + and against_voucher = %s + and party_type = 'Employee' + and party = %s + """, (self.name, self.employee), as_dict=1)[0].paid_amount + + if flt(paid_amount) > self.amount: + frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount"), + EmployeeAdvanceOverPayment) + + + self.db_set("paid_amount", paid_amount) + if self.amount == self.paid_amount: + self.db_set("status", "Paid") + @frappe.whitelist() def calculate_work_experience_and_amount(employee, gratuity_rule): @@ -37,18 +61,29 @@ def calculate_work_experience_and_amount(employee, gratuity_rule): return {'current_work_experience': current_work_experience, "amount": gratuity_amount} def calculate_work_experience(employee, gratuity_rule): + + total_working_days_per_year = frappe.db.get_value("Gratuity Rule", gratuity_rule, "total_working_days_per_year") + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) if not relieving_date: frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(employee))) - time_difference = relativedelta(relieving_date, date_of_joining) + # time_difference = relativedelta(relieving_date, date_of_joining) method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") - current_work_experience = time_difference.years + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + # current_work_experience = time_difference.years + + current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 + + print("--->", current_work_experience) if method == "Round off Work Experience": - if time_difference.months >= 6 and time_difference.days > 0: - current_work_experience += 1 + current_work_experience = round(current_work_experience) + else: + current_work_experience = floor(current_work_experience) + return current_work_experience @@ -61,41 +96,54 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) - fraction_to_be_paid = 0 + calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") + + gratuity_amount = 0 + fraction_to_be_paid = 0 + year_left = experience for slab in slabs: - if experience > slab.get("from", 0) and (slab.to == 0 or experience < slab.to): - fraction_to_be_paid = slab.fraction_of_applicable_earnings - if fraction_to_be_paid: + if calculate_gratuity_amount_based_on == "Single Slab": + if experience >= slab.get("from", 0) and (slab.to == 0 or experience <= slab.to): + gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings + if slab.fraction_of_applicable_earnings: + break + elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": + if slab.get("to") == 0 and slab.get("from") == 0: + gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings break - gratuity_amount = total_applicable_components_amount * experience * fraction_to_be_paid + if experience > slab.get("to") and experience > slab.get("from"): + gratuity_amount += (slab.get("to") - slab.get("from")) * total_applicable_components_amount * slab.fraction_of_applicable_earnings + year_left -= (slab.get("to") - slab.get("from")) + print(experience, year_left) + elif slab.get("from") < experience < slab.get("to"): + print(year_left) + gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + + return gratuity_amount def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): - calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") - if calculate_gratuity_amount_based_on == "Last Month Salary": - sal_slip = get_last_salary_slip(employee) + sal_slip = get_last_salary_slip(employee) - if not sal_slip: - frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) + if not sal_slip: + frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_list("Salary Detail", - filters={ - "docstatus": 1, - 'parent': sal_slip, - "parentfield": "earnings", - 'salary_component': ('in', applicable_earnings_component) - }, - fields=["amount"]) - total_applicable_components_amount = 0 - if not len(component_and_amounts): - frappe.throw("No Applicable Component is present in last month salary slip") - for data in component_and_amounts: - total_applicable_components_amount += data.amount - elif calculate_gratuity_amount_based_on == "Actual Salary": - pass + component_and_amounts = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': ('in', applicable_earnings_component) + }, + fields=["amount"]) + total_applicable_components_amount = 0 + if not len(component_and_amounts): + frappe.throw("No Applicable Component is present in last month salary slip") + for data in component_and_amounts: + total_applicable_components_amount += data.amount return total_applicable_components_amount diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index b5de28173a..40906fa6e4 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -8,6 +8,7 @@ "field_order": [ "applicable_earnings_component", "work_experience_calculation_function", + "total_working_days_per_year", "column_break_3", "disable", "calculate_gratuity_amount_based_on", @@ -26,7 +27,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Calculate Gratuity Amount Based on", - "options": "Last Month Salary\nActual Salary", + "options": "Single Slab\nSum of all previous slabs", "reqd": 1 }, { @@ -60,10 +61,15 @@ "fieldtype": "Select", "label": "Work Experience Calculation method", "options": "Round off Work Experience\nTake Exact Completed Years" + }, + { + "fieldname": "total_working_days_per_year", + "fieldtype": "Int", + "label": "Total Working Days per year" } ], "links": [], - "modified": "2020-08-06 12:28:13.757792", + "modified": "2020-08-13 16:21:10.466739", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", From dedc0015c0094653bb6335fad41f420cb5ebc7e9 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 14 Aug 2020 14:30:31 +0530 Subject: [PATCH 03/27] feat(HR): Working day calulation based on attendance or Leave --- erpnext/payroll/doctype/gratuity/gratuity.py | 38 ++++++++++++++----- .../doctype/gratuity_rule/gratuity_rule.json | 6 +-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 23cc16b994..dc2e773c0f 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _, bold from frappe.model.document import Document -from frappe.utils import flt +from frappe.utils import flt, get_datetime from math import floor from frappe.utils import get_datetime @@ -68,17 +68,22 @@ def calculate_work_experience(employee, gratuity_rule): if not relieving_date: frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(employee))) - # time_difference = relativedelta(relieving_date, date_of_joining) method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + payroll_based_on = frappe.db.get_value("Payroll Settings", None, "payroll_based_on") or "Leave" + if payroll_based_on == "Leave": + total_lwp = get_non_working_days(employee, relieving_date, "On Leave") + employee_total_workings_days -= total_lwp + elif payroll_based_on == "Attendance": + total_absents = get_non_working_days(employee, relieving_date, "Absent") + employee_total_workings_days -= total_absents + # current_work_experience = time_difference.years current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 - print("--->", current_work_experience) - if method == "Round off Work Experience": current_work_experience = round(current_work_experience) else: @@ -87,6 +92,24 @@ def calculate_work_experience(employee, gratuity_rule): return current_work_experience +def get_non_working_days(employee, relieving_date, status): + + filters={ + "docstatus": 1, + "status": status, + "employee": employee, + "attendance_date": ("<=", get_datetime(relieving_date)) + } + + if status == "On Leave": + lwp_leave_types = frappe.get_list("Leave Type", filters = {"is_lwp":1}) + lwp_leave_types = [leave_type.name for leave_type in lwp_leave_types] + filters["leave_type"] = ("IN", lwp_leave_types) + + + record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"], debug = 1) + return record[0].total_lwp if len(record) else 0 + def calculate_gratuity_amount(employee, gratuity_rule, experience): applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] @@ -95,15 +118,13 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) - - calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") gratuity_amount = 0 fraction_to_be_paid = 0 year_left = experience for slab in slabs: - if calculate_gratuity_amount_based_on == "Single Slab": + if calculate_gratuity_amount_based_on == "Current Slab": if experience >= slab.get("from", 0) and (slab.to == 0 or experience <= slab.to): gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings if slab.fraction_of_applicable_earnings: @@ -116,13 +137,10 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): if experience > slab.get("to") and experience > slab.get("from"): gratuity_amount += (slab.get("to") - slab.get("from")) * total_applicable_components_amount * slab.fraction_of_applicable_earnings year_left -= (slab.get("to") - slab.get("from")) - print(experience, year_left) elif slab.get("from") < experience < slab.get("to"): - print(year_left) gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings - return gratuity_amount def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index 40906fa6e4..0df274d157 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -27,7 +27,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Calculate Gratuity Amount Based on", - "options": "Single Slab\nSum of all previous slabs", + "options": "Current slab\nSum of all previous slabs", "reqd": 1 }, { @@ -65,11 +65,11 @@ { "fieldname": "total_working_days_per_year", "fieldtype": "Int", - "label": "Total Working Days per year" + "label": "Total Working Days Per Year" } ], "links": [], - "modified": "2020-08-13 16:21:10.466739", + "modified": "2020-08-14 14:17:36.599008", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", From 30299c6f499ff810a99160f8ddb7e29f67b1e473 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 14 Aug 2020 16:44:22 +0530 Subject: [PATCH 04/27] feat: validating and ordeing the rule slabr --- .../doctype/gratuity_rule/gratuity_rule.js | 33 +++++++++++++++++ .../doctype/gratuity_rule/gratuity_rule.py | 14 ++++++-- .../gratuity_rule_slab.json | 35 +++++++++++-------- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index 929370fb17..feaf6a8e18 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -6,3 +6,36 @@ frappe.ui.form.on('Gratuity Rule', { // } }); + +frappe.ui.form.on('Gratuity Rule Slab', { + + /* + Slabs should be in order like + + from | to | fraction + 0 | 4 | 0.5 + 4 | 6 | 0.7 + + So, on row addition setting current_row.from = previous row.to. + On to_year insert we have to check that it is not less than from_year + + */ + + + gratuity_rule_slabs_add(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let array_idx = row.idx - 1 + if(array_idx > 0){ + row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx-1].to_year; + frm.refresh(); + } + }, + + to_year(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.to_year <= row.from_year){ + frappe.throw(__("To(Year) year can not be less than From(year) ")); + } + } +}); + diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py index 10b2a87b97..71adbe5b31 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -3,8 +3,18 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ class GratuityRule(Document): - pass + + def validate(self): + for current_slab in self.gratuity_rule_slabs: + if current_slab.from_year > current_slab.to_year: + frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(slab.idx)) + + if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: + frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) + + diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json index 615829f45a..dd642f4cd0 100644 --- a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json @@ -5,34 +5,39 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "from", - "to", + "from_year", + "to_year", "fraction_of_applicable_earnings" ], "fields": [ - { - "fieldname": "from", - "fieldtype": "Int", - "in_list_view": 1, - "label": "From(Year)" - }, - { - "fieldname": "to", - "fieldtype": "Int", - "in_list_view": 1, - "label": "To(Year)" - }, { "fieldname": "fraction_of_applicable_earnings", "fieldtype": "Float", "in_list_view": 1, "label": "Fraction of Applicable Earnings ", "reqd": 1 + }, + { + "default": "0", + "fieldname": "from_year", + "fieldtype": "Int", + "in_list_view": 1, + "label": "From(Year)", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "to_year", + "fieldtype": "Int", + "in_list_view": 1, + "label": "To(Year)", + "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-08-05 20:03:25.955448", + "modified": "2020-08-14 15:23:12.041375", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule Slab", From 6f1538026aa36550692b9427bbec785c57f7c41a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 14 Aug 2020 16:45:34 +0530 Subject: [PATCH 05/27] feat: added minimum year for gratuity and condition utilisation --- erpnext/payroll/doctype/gratuity/gratuity.py | 17 +++++++++-------- .../doctype/gratuity_rule/gratuity_rule.json | 11 +++++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index dc2e773c0f..4439d81b8c 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -32,7 +32,6 @@ class Gratuity(Document): additional_salary.ref_docname = self.name additional_salary.submit() - def set_total_advance_paid(self): paid_amount = frappe.db.sql(""" select ifnull(sum(debit_in_account_currency), 0) as paid_amount @@ -62,7 +61,7 @@ def calculate_work_experience_and_amount(employee, gratuity_rule): def calculate_work_experience(employee, gratuity_rule): - total_working_days_per_year = frappe.db.get_value("Gratuity Rule", gratuity_rule, "total_working_days_per_year") + total_working_days_per_year, minimum_year_for_gratuity = frappe.db.get_value("Gratuity Rule", gratuity_rule, ["total_working_days_per_year", "minimum_year_for_gratuity"]) date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) if not relieving_date: @@ -89,6 +88,8 @@ def calculate_work_experience(employee, gratuity_rule): else: current_work_experience = floor(current_work_experience) + if current_work_experience < minimum_year_for_gratuity: + frappe.throw(_("Employee: {0} have to complete minimum {1} years for gratuity").format(bold(employee), minimum_year_for_gratuity)) return current_work_experience @@ -125,19 +126,19 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): year_left = experience for slab in slabs: if calculate_gratuity_amount_based_on == "Current Slab": - if experience >= slab.get("from", 0) and (slab.to == 0 or experience <= slab.to): + if experience >= slab.from_year and (slab.to_year == 0 or experience < slab.to_year): gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings if slab.fraction_of_applicable_earnings: break elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": - if slab.get("to") == 0 and slab.get("from") == 0: + if slab.to_year == 0 and slab.from_year == 0: gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings break - if experience > slab.get("to") and experience > slab.get("from"): - gratuity_amount += (slab.get("to") - slab.get("from")) * total_applicable_components_amount * slab.fraction_of_applicable_earnings - year_left -= (slab.get("to") - slab.get("from")) - elif slab.get("from") < experience < slab.get("to"): + if experience > slab.to_year and experience > slab.from_year: + gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings + year_left -= (slab.to_year - slab.from_year) + elif slab.from_year <= experience < slab.to_year: gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index 0df274d157..41d5a97641 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -12,6 +12,7 @@ "column_break_3", "disable", "calculate_gratuity_amount_based_on", + "minimum_year_for_gratuity", "gratuity_rules_section", "gratuity_rule_slabs" ], @@ -48,7 +49,7 @@ "label": "Gratuity Rules" }, { - "description": "Leave From and To blank for no upper and lower limit.", + "description": "Leave From and To 0 for no upper and lower limit.", "fieldname": "gratuity_rule_slabs", "fieldtype": "Table", "label": "Current Work Experience", @@ -63,13 +64,19 @@ "options": "Round off Work Experience\nTake Exact Completed Years" }, { + "default": "365", "fieldname": "total_working_days_per_year", "fieldtype": "Int", "label": "Total Working Days Per Year" + }, + { + "fieldname": "minimum_year_for_gratuity", + "fieldtype": "Int", + "label": "Minimum Year for Gratuity" } ], "links": [], - "modified": "2020-08-14 14:17:36.599008", + "modified": "2020-08-14 16:23:05.287545", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", From 47f3a3a5bd5e0378dc98aef4acc8c2a4ebf2c079 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 14 Aug 2020 17:02:41 +0530 Subject: [PATCH 06/27] feat: validation for if no slab found --- erpnext/payroll/doctype/gratuity/gratuity.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 4439d81b8c..6b624140e2 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -122,24 +122,31 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") gratuity_amount = 0 - fraction_to_be_paid = 0 + slab_found = False year_left = experience for slab in slabs: if calculate_gratuity_amount_based_on == "Current Slab": if experience >= slab.from_year and (slab.to_year == 0 or experience < slab.to_year): gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings if slab.fraction_of_applicable_earnings: + slab_found = True break elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": if slab.to_year == 0 and slab.from_year == 0: gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + slab_found = True break if experience > slab.to_year and experience > slab.from_year: gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings year_left -= (slab.to_year - slab.from_year) + slab_found = True elif slab.from_year <= experience < slab.to_year: gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + slab_found = True + + if not slab_found: + frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) return gratuity_amount From 0761301c2eee23cca45ccdc00ad477ea9948d299 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 17 Aug 2020 14:50:35 +0530 Subject: [PATCH 07/27] feat: Added all 3 standard slab for UAE --- .../regional/united_arab_emirates/setup.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 013ae5cf73..690c6feedd 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -13,6 +13,8 @@ def setup(company=None, patch=True): add_print_formats() add_custom_roles_for_reports() add_permissions() + create_standard_documents() + if company: create_sales_tax(company) @@ -153,3 +155,104 @@ def add_permissions(): add_permission(doctype, role, 0) update_permission_property(doctype, role, 0, 'write', 1) update_permission_property(doctype, role, 0, 'create', 1) +def create_standard_documents(): + + # Standard Gratuity Rules for UAE + + # Rule Under Limited Contract + rule_1 = frappe.new_doc("Gratuity Rule") + rule_1.name = "Rule Under Limited Contract" + rule_1.calculate_gratuity_amount_based_on = "Sum of all previous slabs" + rule_1.work_experience_calculation_method = "Take Exact Completed Years" + rule_1.minimum_year_for_gratuity = 1 + + rule_1.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) + + rule_1.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }) + + rule_1.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }) + + # Rule Under Unlimited Contract on termination + rule_2 = frappe.new_doc("Gratuity Rule") + rule_2.name = "Rule Under Unlimited Contract on termination" + rule_2.calculate_gratuity_amount_based_on = "Current Slab" + rule_2.work_experience_calculation_method = "Take Exact Completed Years" + rule_2.minimum_year_for_gratuity = 1 + + rule_2.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) + + rule_2.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }) + + rule_2.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }) + + # Rule Under Unlimited Contract + rule_3 = frappe.new_doc("Gratuity Rule") + rule_3.name = "Rule Under Unlimited Contract on resignation" + rule_3.calculate_gratuity_amount_based_on = "Current Slab" + rule_3.work_experience_calculation_method = "Take Exact Completed Years" + rule_3.minimum_year_for_gratuity = 1 + + rule_3.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) + + fraction_of_applicable_earnings = 1/3 * 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":3, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) + + fraction_of_applicable_earnings = 2/3 * 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 3, + "to_year":5, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) + + fraction_of_applicable_earnings = 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) + + + #for applicable salary component user need to set this by its own + rule_1.flags.ignore_mandatory = True + rule_2.flags.ignore_mandatory = True + rule_3.flags.ignore_mandatory = True + + rule_1.save() + rule_2.save() + rule_3.save() + + return rule_1, rule_2, rule_3 + + From 34a7250a2d9f7233e14589ae3d097629110418c5 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 17 Aug 2020 14:51:25 +0530 Subject: [PATCH 08/27] fix: Some enhancements and better validation --- erpnext/payroll/doctype/gratuity/gratuity.py | 21 ++++++++++--------- .../doctype/gratuity_rule/gratuity_rule.js | 3 ++- .../doctype/gratuity_rule/gratuity_rule.json | 4 ++-- .../doctype/gratuity_rule/gratuity_rule.py | 4 ++-- .../gratuity_rule_slab.json | 2 +- .../regional/united_arab_emirates/setup.py | 6 +++--- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 6b624140e2..9f59280393 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _, bold from frappe.model.document import Document -from frappe.utils import flt, get_datetime +from frappe.utils import flt, get_datetime, get_link_to_form from math import floor from frappe.utils import get_datetime @@ -43,8 +43,7 @@ class Gratuity(Document): """, (self.name, self.employee), as_dict=1)[0].paid_amount if flt(paid_amount) > self.amount: - frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount"), - EmployeeAdvanceOverPayment) + frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount")) self.db_set("paid_amount", paid_amount) @@ -65,7 +64,7 @@ def calculate_work_experience(employee, gratuity_rule): date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) if not relieving_date: - frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(employee))) + frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(get_link_to_form("Employee", employee)))) method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") @@ -108,11 +107,13 @@ def get_non_working_days(employee, relieving_date, status): filters["leave_type"] = ("IN", lwp_leave_types) - record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"], debug = 1) + record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"]) return record[0].total_lwp if len(record) else 0 def calculate_gratuity_amount(employee, gratuity_rule, experience): applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) + if len(applicable_earnings_component) == 0: + frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule)))) applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] slabs = get_gratuity_rule_slabs(gratuity_rule) @@ -137,16 +138,16 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): slab_found = True break - if experience > slab.to_year and experience > slab.from_year: + if experience > slab.to_year and experience > slab.from_year and slab.to_year !=0: gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings year_left -= (slab.to_year - slab.from_year) slab_found = True - elif slab.from_year <= experience < slab.to_year: + elif slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0): gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings slab_found = True - if not slab_found: - frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) + if not slab_found: + frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) return gratuity_amount @@ -174,7 +175,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen return total_applicable_components_amount def get_gratuity_rule_slabs(gratuity_rule): - return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"]) + return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx") def get_salary_structure(employee): return frappe.get_list("Salary Structure Assignment", filters = {"employee": employee, 'docstatus': 1}, fields=["from_date", "salary_structure"], order_by = "from_date desc")[0].salary_structure diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index feaf6a8e18..69099bb39d 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -19,6 +19,7 @@ frappe.ui.form.on('Gratuity Rule Slab', { So, on row addition setting current_row.from = previous row.to. On to_year insert we have to check that it is not less than from_year + Wrong order may lead to Wrong Calculation */ @@ -33,7 +34,7 @@ frappe.ui.form.on('Gratuity Rule Slab', { to_year(frm, cdt, cdn) { let row = locals[cdt][cdn]; - if (row.to_year <= row.from_year){ + if (row.to_year <= row.from_year && row.to_year === 0){ frappe.throw(__("To(Year) year can not be less than From(year) ")); } } diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index 41d5a97641..18053ba88e 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -28,7 +28,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Calculate Gratuity Amount Based on", - "options": "Current slab\nSum of all previous slabs", + "options": "Current Slab\nSum of all previous slabs", "reqd": 1 }, { @@ -76,7 +76,7 @@ } ], "links": [], - "modified": "2020-08-14 16:23:05.287545", + "modified": "2020-08-17 14:17:02.594665", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py index 71adbe5b31..00b5752eb8 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -11,8 +11,8 @@ class GratuityRule(Document): def validate(self): for current_slab in self.gratuity_rule_slabs: - if current_slab.from_year > current_slab.to_year: - frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(slab.idx)) + if (current_slab.from_year > current_slab.to_year) and current_slab.to_year != 0: + frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx)) if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json index dd642f4cd0..bc37b0f51e 100644 --- a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json @@ -37,7 +37,7 @@ ], "istable": 1, "links": [], - "modified": "2020-08-14 15:23:12.041375", + "modified": "2020-08-17 14:09:56.781712", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule Slab", diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 690c6feedd..f23698e19b 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -161,7 +161,7 @@ def create_standard_documents(): # Rule Under Limited Contract rule_1 = frappe.new_doc("Gratuity Rule") - rule_1.name = "Rule Under Limited Contract" + rule_1.name = "Rule Under Limited Contract (UAE)" rule_1.calculate_gratuity_amount_based_on = "Sum of all previous slabs" rule_1.work_experience_calculation_method = "Take Exact Completed Years" rule_1.minimum_year_for_gratuity = 1 @@ -186,7 +186,7 @@ def create_standard_documents(): # Rule Under Unlimited Contract on termination rule_2 = frappe.new_doc("Gratuity Rule") - rule_2.name = "Rule Under Unlimited Contract on termination" + rule_2.name = "Rule Under Unlimited Contract on termination (UAE)" rule_2.calculate_gratuity_amount_based_on = "Current Slab" rule_2.work_experience_calculation_method = "Take Exact Completed Years" rule_2.minimum_year_for_gratuity = 1 @@ -211,7 +211,7 @@ def create_standard_documents(): # Rule Under Unlimited Contract rule_3 = frappe.new_doc("Gratuity Rule") - rule_3.name = "Rule Under Unlimited Contract on resignation" + rule_3.name = "Rule Under Unlimited Contract on resignation (UAE)" rule_3.calculate_gratuity_amount_based_on = "Current Slab" rule_3.work_experience_calculation_method = "Take Exact Completed Years" rule_3.minimum_year_for_gratuity = 1 From e1464a7bf0a986b48e39ebdafbad174a678a6258 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 17 Aug 2020 15:03:32 +0530 Subject: [PATCH 09/27] feat: Indian Standard Gratuity Rule --- erpnext/regional/india/setup.py | 23 ++++++++++++++++++- .../regional/united_arab_emirates/setup.py | 2 -- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index cbcd6e3203..a8ff3f8484 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -21,6 +21,7 @@ def setup_company_independent_fixtures(): add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) + create_standard_documents() add_print_formats() def add_hsn_sac_codes(): @@ -793,4 +794,24 @@ def get_tds_details(accounts, fiscal_year): doctype="Tax Withholding Category", accounts=accounts, rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) - ] \ No newline at end of file + ] + +def create_standard_documents(): + + # Standard Indain Gratuity Rule + + rule = frappe.new_doc("Gratuity Rule") + rule.name = "Indian Standard Gratuity Rule" + rule.calculate_gratuity_amount_based_on = "Current Slab" + rule.work_experience_calculation_method = "Round Off Work Experience" + rule.minimum_year_for_gratuity = 5 + + fraction = 15/26 + rule.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":0, + "fraction_of_applicable_earnings": fraction + }) + + rule.flags.ignore_mandatory = True + rule.save() \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index f23698e19b..1a899272f4 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -253,6 +253,4 @@ def create_standard_documents(): rule_2.save() rule_3.save() - return rule_1, rule_2, rule_3 - From f63df91186ec45802a2f2c06a99675ec655b046c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 17 Aug 2020 15:13:25 +0530 Subject: [PATCH 10/27] patch: to create standard Gratuity Rule for india and UAE --- erpnext/patches.txt | 1 + .../setup_gratuity_rule_for_india_and_uae.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9e33014c38..fd9c78ab4d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -740,3 +740,4 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py new file mode 100644 index 0000000000..5de355f747 --- /dev/null +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -0,0 +1,15 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import erpnext + +def execute(): + region = erpnext.get_region() + if region == "India": + from erpnext.regional.india.setup import create_standard_documents + create_standard_documents() + elif region == "United Arab Emirates": + from erpnext.regional.united_arab_emirates.setup import create_standard_documents + create_standard_documents() \ No newline at end of file From 25c356894d58b46692f4de0a34affa8008b8d707 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 18 Aug 2020 14:13:54 +0530 Subject: [PATCH 11/27] test: gratuity --- .../mode_of_payment/mode_of_payment.py | 6 +- .../setup_gratuity_rule_for_india_and_uae.py | 13 +- erpnext/payroll/doctype/gratuity/gratuity.py | 4 +- .../payroll/doctype/gratuity/test_gratuity.py | 174 +++++++++++++++++- erpnext/regional/india/setup.py | 4 +- .../regional/united_arab_emirates/setup.py | 7 +- 6 files changed, 194 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py index d54a47e3c9..32473694c8 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py @@ -12,7 +12,7 @@ class ModeofPayment(Document): self.validate_accounts() self.validate_repeating_companies() self.validate_pos_mode_of_payment() - + def validate_repeating_companies(self): """Error when Same Company is entered multiple times in accounts""" accounts_list = [] @@ -31,10 +31,10 @@ class ModeofPayment(Document): def validate_pos_mode_of_payment(self): if not self.enabled: - pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip + pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name)) pos_profiles = list(map(lambda x: x[0], pos_profiles)) - + if pos_profiles: message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \ Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode." diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py index 5de355f747..93cadf576c 100644 --- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -3,13 +3,16 @@ from __future__ import unicode_literals -import erpnext +import erpnext, frappe def execute(): + frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') + frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab') + frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component') region = erpnext.get_region() if region == "India": - from erpnext.regional.india.setup import create_standard_documents - create_standard_documents() + from erpnext.regional.india.setup import create_gratuity_rule + create_gratuity_rule() elif region == "United Arab Emirates": - from erpnext.regional.united_arab_emirates.setup import create_standard_documents - create_standard_documents() \ No newline at end of file + from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule + create_gratuity_rule() \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 9f59280393..815e24d6e4 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -12,7 +12,9 @@ from math import floor from frappe.utils import get_datetime class Gratuity(Document): def validate(self): - calculate_work_experience_and_amount(self.employee, self.gratuity_rule) + data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule) + self.current_work_experience = data["current_work_experience"] + self.amount = data["amount"] def before_submit(self): self.status = "Unpaid" diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 92c1248b73..fb2488cf85 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -3,8 +3,178 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip +from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip +from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule +from frappe.utils import getdate, add_days, get_datetime, flt + class TestGratuity(unittest.TestCase): - pass + + def setUp(self): + frappe.db.sql("DELETE FROM `tabgratuity`") + frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") + + + def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): + employee, sal_slip = create_employee_and_get_last_salary_slip() + rule = frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)") + if not rule: + create_gratuity_rule() + else: + rule = frappe.get_doc("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)") + rule.applicable_earnings_component = [] + rule.append("applicable_earnings_component", { + "salary_component": "Basic Salary" + }) + rule.save() + rule.reload() + + gra = frappe.new_doc("Gratuity") + gra.employee = employee + gra.posting_date = getdate() + gra.gratuity_rule = rule.name + gra.pay_via_salary_slip = 1 + gra.salary_component = "Performance Bonus" + gra.payroll_date = getdate() + gra.save() + gra.submit() + + #work experience calculation + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + experience = employee_total_workings_days/rule.total_working_days_per_year + + gra.reload() + + from math import floor + + self.assertEqual(floor(experience), gra.current_work_experience) + + #amount Calculation 6 + component_amount = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': "Basic Salary" + }, + fields=["amount"]) + + ''' 5 - 0 fraction is 1 ''' + + gratuity_amount = component_amount[0].amount * experience + gra.reload() + + self.assertEqual(flt(gratuity_amount, 2), flt(gra.amount, 2)) + + #additional salary creation (Pay via salary slip) + self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gra.name})) + self.assertEqual(gra.status, "Paid") + + + + def test_check_gratuity_amount_based_on_all_previous_slabs(self): + employee, sal_slip = create_employee_and_get_last_salary_slip() + rule = frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)") + if not rule: + create_gratuity_rule() + else: + rule = frappe.get_doc("Gratuity Rule", rule) + rule.applicable_earnings_component = [] + rule = frappe.get_doc("Gratuity Rule", "Rule Under Limited Contract (UAE)") + rule.append("applicable_earnings_component", { + "salary_component": "Basic Salary" + }) + rule.save() + rule.reload() + + mof = frappe.get_doc("Mode of Payment", "Cheque") + mof.accounts = [] + mof.append("accounts", { + "company": "_Test Company", + "default_account": "_Test Bank - _TC" + }) + + mof.save() + + gra = frappe.new_doc("Gratuity") + gra.employee = employee + gra.posting_date = getdate() + gra.gratuity_rule = rule.name + gra.pay_via_salary_slip = 0 + gra.payroll_date = getdate() + gra.expense_account = "Payment Account - _TC" + gra.mode_of_payment = "Cheque" + + gra.save() + gra.submit() + + #work experience calculation + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + experience = employee_total_workings_days/rule.total_working_days_per_year + + gra.reload() + + from math import floor + + self.assertEqual(floor(experience), gra.current_work_experience) + + #amount Calculation 6 + component_amount = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': "Basic Salary" + }, + fields=["amount"]) + + + ''' range | Fraction + 0-1 | 0 + 1-5 | 0.7 + 5-0 | 1 + ''' + + + gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount + gra.reload() + + self.assertEqual(flt(gratuity_amount, 2), flt(gra.amount, 2)) + self.assertEqual(gra.status, "Unpaid") + + + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + pay_entry = get_payment_entry("Gratuity", gra.name) + pay_entry.reference_no = "123467" + pay_entry.reference_date = getdate() + + pay_entry.save() + pay_entry.submit() + + gra.reload() + + self.assertEqual(gra.status, "Paid") + self.assertEqual(gra.paid_amount, flt(gra.amount, 2)) + +def create_employee_and_get_last_salary_slip(): + employee = make_employee("test_employee@salary.com") + frappe.db.set_value("Employee", employee, "relieving_date", getdate()) + frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), - (6*365))) + if not frappe.db.exists("Salary Slip", {"employee":employee}): + salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly") + salary_slip.submit() + salary_slip = salary_slip.name + else: + salary_slip = get_last_salary_slip(employee) + + return employee, salary_slip + diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index a8ff3f8484..9be5239d5e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -21,7 +21,7 @@ def setup_company_independent_fixtures(): add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) - create_standard_documents() + create_gratuity_rule() add_print_formats() def add_hsn_sac_codes(): @@ -796,7 +796,7 @@ def get_tds_details(accounts, fiscal_year): "single_threshold": 2500, "cumulative_threshold": 0}]) ] -def create_standard_documents(): +def create_gratuity_rule(): # Standard Indain Gratuity Rule diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 1a899272f4..72d7c13204 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -11,9 +11,13 @@ from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax def setup(company=None, patch=True): make_custom_fields() add_print_formats() +<<<<<<< HEAD add_custom_roles_for_reports() add_permissions() create_standard_documents() +======= + create_gratuity_rule() +>>>>>>> test: gratuity if company: create_sales_tax(company) @@ -155,7 +159,8 @@ def add_permissions(): add_permission(doctype, role, 0) update_permission_property(doctype, role, 0, 'write', 1) update_permission_property(doctype, role, 0, 'create', 1) -def create_standard_documents(): + +def create_gratuity_rule(): # Standard Gratuity Rules for UAE From 493eea19e84b6596c3410fa921ab84633ae4b7db Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 18 Aug 2020 15:02:32 +0530 Subject: [PATCH 12/27] feat: added link from desk page and some minor fixes --- .../payroll/desk_page/payroll/payroll.json | 4 +- erpnext/regional/india/setup.py | 28 ++-- .../regional/united_arab_emirates/setup.py | 153 +++++++++--------- 3 files changed, 94 insertions(+), 91 deletions(-) diff --git a/erpnext/payroll/desk_page/payroll/payroll.json b/erpnext/payroll/desk_page/payroll/payroll.json index 285e3b3a13..1caf9c7b4e 100644 --- a/erpnext/payroll/desk_page/payroll/payroll.json +++ b/erpnext/payroll/desk_page/payroll/payroll.json @@ -13,7 +13,7 @@ { "hidden": 0, "label": "Compensations", - "links": "[\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Gratuity Rule\",\n \"name\": \"Gratuity Rule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Gratuity\",\n \"name\": \"Gratuity\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -38,7 +38,7 @@ "idx": 0, "is_standard": 1, "label": "Payroll", - "modified": "2020-08-10 19:38:45.976209", + "modified": "2020-08-18 15:00:55.671767", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9be5239d5e..d46c372fdd 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -799,19 +799,19 @@ def get_tds_details(accounts, fiscal_year): def create_gratuity_rule(): # Standard Indain Gratuity Rule + if not frappe.db.exists("Gratuity Rule", "Indian Standard Gratuity Rule"): + rule = frappe.new_doc("Gratuity Rule") + rule.name = "Indian Standard Gratuity Rule" + rule.calculate_gratuity_amount_based_on = "Current Slab" + rule.work_experience_calculation_method = "Round Off Work Experience" + rule.minimum_year_for_gratuity = 5 - rule = frappe.new_doc("Gratuity Rule") - rule.name = "Indian Standard Gratuity Rule" - rule.calculate_gratuity_amount_based_on = "Current Slab" - rule.work_experience_calculation_method = "Round Off Work Experience" - rule.minimum_year_for_gratuity = 5 + fraction = 15/26 + rule.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":0, + "fraction_of_applicable_earnings": fraction + }) - fraction = 15/26 - rule.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":0, - "fraction_of_applicable_earnings": fraction - }) - - rule.flags.ignore_mandatory = True - rule.save() \ No newline at end of file + rule.flags.ignore_mandatory = True + rule.save() \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 72d7c13204..b91318c9af 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -165,97 +165,100 @@ def create_gratuity_rule(): # Standard Gratuity Rules for UAE # Rule Under Limited Contract - rule_1 = frappe.new_doc("Gratuity Rule") - rule_1.name = "Rule Under Limited Contract (UAE)" - rule_1.calculate_gratuity_amount_based_on = "Sum of all previous slabs" - rule_1.work_experience_calculation_method = "Take Exact Completed Years" - rule_1.minimum_year_for_gratuity = 1 + if not frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)"): + rule_1 = frappe.new_doc("Gratuity Rule") + rule_1.name = "Rule Under Limited Contract (UAE)" + rule_1.calculate_gratuity_amount_based_on = "Sum of all previous slabs" + rule_1.work_experience_calculation_method = "Take Exact Completed Years" + rule_1.minimum_year_for_gratuity = 1 - rule_1.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) + rule_1.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) - rule_1.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":5, - "fraction_of_applicable_earnings": 21/30 - }) + rule_1.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }) - rule_1.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": 1 - }) + rule_1.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }) # Rule Under Unlimited Contract on termination - rule_2 = frappe.new_doc("Gratuity Rule") - rule_2.name = "Rule Under Unlimited Contract on termination (UAE)" - rule_2.calculate_gratuity_amount_based_on = "Current Slab" - rule_2.work_experience_calculation_method = "Take Exact Completed Years" - rule_2.minimum_year_for_gratuity = 1 + if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)"): + rule_2 = frappe.new_doc("Gratuity Rule") + rule_2.name = "Rule Under Unlimited Contract on termination (UAE)" + rule_2.calculate_gratuity_amount_based_on = "Current Slab" + rule_2.work_experience_calculation_method = "Take Exact Completed Years" + rule_2.minimum_year_for_gratuity = 1 - rule_2.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) + rule_2.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) - rule_2.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":5, - "fraction_of_applicable_earnings": 21/30 - }) + rule_2.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }) - rule_2.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": 1 - }) + rule_2.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }) # Rule Under Unlimited Contract - rule_3 = frappe.new_doc("Gratuity Rule") - rule_3.name = "Rule Under Unlimited Contract on resignation (UAE)" - rule_3.calculate_gratuity_amount_based_on = "Current Slab" - rule_3.work_experience_calculation_method = "Take Exact Completed Years" - rule_3.minimum_year_for_gratuity = 1 + if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on resignation (UAE)"): + rule_3 = frappe.new_doc("Gratuity Rule") + rule_3.name = "Rule Under Unlimited Contract on resignation (UAE)" + rule_3.calculate_gratuity_amount_based_on = "Current Slab" + rule_3.work_experience_calculation_method = "Take Exact Completed Years" + rule_3.minimum_year_for_gratuity = 1 - rule_3.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) + rule_3.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }) - fraction_of_applicable_earnings = 1/3 * 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":3, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) + fraction_of_applicable_earnings = 1/3 * 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 1, + "to_year":3, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) - fraction_of_applicable_earnings = 2/3 * 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 3, - "to_year":5, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) + fraction_of_applicable_earnings = 2/3 * 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 3, + "to_year":5, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) - fraction_of_applicable_earnings = 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) + fraction_of_applicable_earnings = 21/30 + rule_3.append("gratuity_rule_slabs", { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": fraction_of_applicable_earnings + }) - #for applicable salary component user need to set this by its own - rule_1.flags.ignore_mandatory = True - rule_2.flags.ignore_mandatory = True - rule_3.flags.ignore_mandatory = True + #for applicable salary component user need to set this by its own + rule_1.flags.ignore_mandatory = True + rule_2.flags.ignore_mandatory = True + rule_3.flags.ignore_mandatory = True - rule_1.save() - rule_2.save() - rule_3.save() + rule_1.save() + rule_2.save() + rule_3.save() From 4d6c3c9449541fc2983b19b9c88426b43c240a33 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 27 Oct 2020 14:42:55 +0530 Subject: [PATCH 13/27] Fix: Changes Requested, Sider, codacy, Transalation --- .../setup_gratuity_rule_for_india_and_uae.py | 5 +- erpnext/payroll/doctype/gratuity/gratuity.js | 38 +++++----- .../payroll/doctype/gratuity/gratuity.json | 17 ++++- erpnext/payroll/doctype/gratuity/gratuity.py | 17 ++++- .../payroll/doctype/gratuity/test_gratuity.py | 76 +++++++++---------- .../doctype/gratuity_rule/gratuity_rule.js | 2 +- .../doctype/gratuity_rule/gratuity_rule.json | 17 ++++- 7 files changed, 103 insertions(+), 69 deletions(-) diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py index 93cadf576c..2dd064ebca 100644 --- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -9,10 +9,9 @@ def execute(): frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab') frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component') - region = erpnext.get_region() - if region == "India": + if frappe.db.exists("company", {"country": "India"}): from erpnext.regional.india.setup import create_gratuity_rule create_gratuity_rule() - elif region == "United Arab Emirates": + if frappe.db.exists("company", {"country": "United Arab Emirates"}): from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule create_gratuity_rule() \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index d6e93af524..dfdf08bdea 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -2,24 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Gratuity', { - refresh: function(frm){ - if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { - frm.add_custom_button(__("Make Payment Entry"), function() { - return frappe.call({ - method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', - args: { - "dt": cur_frm.doc.doctype, - "dn": cur_frm.doc.name - }, - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }); - }); - } - }, - onload: function(frm){ + setup: function(frm){ frm.set_query('salary_component', function() { return { filters: { @@ -30,13 +13,30 @@ frappe.ui.form.on('Gratuity', { frm.set_query("expense_account", function() { return { filters: { - "root_type": "Asset", + "root_type": "Expense", "is_group": 0, "company": frm.doc.company } }; }); }, + refresh: function(frm){ + if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + frm.add_custom_button(__("Create Payment Entry"), function() { + return frappe.call({ + method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', + args: { + "dt": frm.doc.doctype, + "dn": frm.doc.name + }, + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }); + } + }, employee: function(frm) { frm.events.calculate_work_experience_and_amount(frm); }, diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index b8122dfb89..b81ae588ea 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -175,9 +175,10 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-08-14 11:59:15.499548", + "modified": "2020-10-27 14:04:41.886934", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", @@ -191,7 +192,19 @@ "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", "share": 1, "write": 1 } diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 815e24d6e4..e6c519a482 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -9,7 +9,6 @@ from frappe.model.document import Document from frappe.utils import flt, get_datetime, get_link_to_form from math import floor -from frappe.utils import get_datetime class Gratuity(Document): def validate(self): data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule) @@ -22,6 +21,9 @@ class Gratuity(Document): self.status = "Paid" def on_submit(self): + create_additional_salary() + + def create_additional_salary(self): if self.pay_via_salary_slip: additional_salary = frappe.new_doc('Additional Salary') additional_salary.employee = self.employee @@ -170,7 +172,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen fields=["amount"]) total_applicable_components_amount = 0 if not len(component_and_amounts): - frappe.throw("No Applicable Component is present in last month salary slip") + frappe.throw(_("No Applicable Component is present in last month salary slip")) for data in component_and_amounts: total_applicable_components_amount += data.amount @@ -180,10 +182,17 @@ def get_gratuity_rule_slabs(gratuity_rule): return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx") def get_salary_structure(employee): - return frappe.get_list("Salary Structure Assignment", filters = {"employee": employee, 'docstatus': 1}, fields=["from_date", "salary_structure"], order_by = "from_date desc")[0].salary_structure + return frappe.get_list("Salary Structure Assignment", filters = { + "employee": employee, 'docstatus': 1 + }, + fields=["from_date", "salary_structure"], + order_by = "from_date desc")[0].salary_structure def get_last_salary_slip(employee): - return frappe.get_list("Salary Slip", filters = {"employee": employee, 'docstatus': 1}, order_by = "start_date desc")[0].name + return frappe.get_list("Salary Slip", filters = { + "employee": employee, 'docstatus': 1 + }, + order_by = "start_date desc")[0].name diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index fb2488cf85..680ecbcfc1 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -13,12 +13,10 @@ from frappe.utils import getdate, add_days, get_datetime, flt class TestGratuity(unittest.TestCase): - def setUp(self): frappe.db.sql("DELETE FROM `tabgratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") - def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)") @@ -33,15 +31,15 @@ class TestGratuity(unittest.TestCase): rule.save() rule.reload() - gra = frappe.new_doc("Gratuity") - gra.employee = employee - gra.posting_date = getdate() - gra.gratuity_rule = rule.name - gra.pay_via_salary_slip = 1 - gra.salary_component = "Performance Bonus" - gra.payroll_date = getdate() - gra.save() - gra.submit() + gratuity = frappe.new_doc("Gratuity") + gratuity.employee = employee + gratuity.posting_date = getdate() + gratuity.gratuity_rule = rule.name + gratuity.pay_via_salary_slip = 1 + gratuity.salary_component = "Performance Bonus" + gratuity.payroll_date = getdate() + gratuity.save() + gratuity.submit() #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -49,11 +47,11 @@ class TestGratuity(unittest.TestCase): experience = employee_total_workings_days/rule.total_working_days_per_year - gra.reload() + gratuity.reload() from math import floor - self.assertEqual(floor(experience), gra.current_work_experience) + self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation 6 component_amount = frappe.get_list("Salary Detail", @@ -68,15 +66,13 @@ class TestGratuity(unittest.TestCase): ''' 5 - 0 fraction is 1 ''' gratuity_amount = component_amount[0].amount * experience - gra.reload() + gratuity.reload() - self.assertEqual(flt(gratuity_amount, 2), flt(gra.amount, 2)) + self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) #additional salary creation (Pay via salary slip) - self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gra.name})) - self.assertEqual(gra.status, "Paid") - - + self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) + self.assertEqual(gratuity.status, "Paid") def test_check_gratuity_amount_based_on_all_previous_slabs(self): employee, sal_slip = create_employee_and_get_last_salary_slip() @@ -102,17 +98,17 @@ class TestGratuity(unittest.TestCase): mof.save() - gra = frappe.new_doc("Gratuity") - gra.employee = employee - gra.posting_date = getdate() - gra.gratuity_rule = rule.name - gra.pay_via_salary_slip = 0 - gra.payroll_date = getdate() - gra.expense_account = "Payment Account - _TC" - gra.mode_of_payment = "Cheque" + gratuity = frappe.new_doc("Gratuity") + gratuity.employee = employee + gratuity.posting_date = getdate() + gratuity.gratuity_rule = rule.name + gratuity.pay_via_salary_slip = 0 + gratuity.payroll_date = getdate() + gratuity.expense_account = "Payment Account - _TC" + gratuity.mode_of_payment = "Cheque" - gra.save() - gra.submit() + gratuity.save() + gratuity.submit() #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -120,11 +116,11 @@ class TestGratuity(unittest.TestCase): experience = employee_total_workings_days/rule.total_working_days_per_year - gra.reload() + gratuity.reload() from math import floor - self.assertEqual(floor(experience), gra.current_work_experience) + self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation 6 component_amount = frappe.get_list("Salary Detail", @@ -145,25 +141,29 @@ class TestGratuity(unittest.TestCase): gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount - gra.reload() + gratuity.reload() - self.assertEqual(flt(gratuity_amount, 2), flt(gra.amount, 2)) - self.assertEqual(gra.status, "Unpaid") + self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) + self.assertEqual(gratuity.status, "Unpaid") from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry - pay_entry = get_payment_entry("Gratuity", gra.name) + pay_entry = get_payment_entry("Gratuity", gratuity.name) pay_entry.reference_no = "123467" pay_entry.reference_date = getdate() pay_entry.save() pay_entry.submit() - gra.reload() + gratuity.reload() - self.assertEqual(gra.status, "Paid") - self.assertEqual(gra.paid_amount, flt(gra.amount, 2)) + self.assertEqual(gratuity.status, "Paid") + self.assertEqual(gratuity.paid_amount, flt(gratuity.amount, 2)) + + def tearDown(self): + frappe.db.sql("DELETE FROM `tabgratuity`") + frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") def create_employee_and_get_last_salary_slip(): employee = make_employee("test_employee@salary.com") diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index 69099bb39d..9e9f3e204b 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -25,7 +25,7 @@ frappe.ui.form.on('Gratuity Rule Slab', { gratuity_rule_slabs_add(frm, cdt, cdn) { let row = locals[cdt][cdn]; - let array_idx = row.idx - 1 + let array_idx = row.idx - 1; if(array_idx > 0){ row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx-1].to_year; frm.refresh(); diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index 18053ba88e..7d24e41f48 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -75,8 +75,9 @@ "label": "Minimum Year for Gratuity" } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-17 14:17:02.594665", + "modified": "2020-10-27 14:04:31.617621", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", @@ -90,7 +91,19 @@ "print": 1, "read": 1, "report": 1, - "role": "All", + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", "share": 1, "write": 1 } From 46e1c09b4558079f36bebf4a96cce3b077391f22 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 27 Oct 2020 15:44:43 +0530 Subject: [PATCH 14/27] style: Broken into smaller function --- .../setup_gratuity_rule_for_india_and_uae.py | 3 +- erpnext/payroll/doctype/gratuity/gratuity.py | 5 +- .../doctype/gratuity_rule/gratuity_rule.js | 1 - .../doctype/gratuity_rule/gratuity_rule.py | 13 ++ .../regional/united_arab_emirates/setup.py | 160 ++++++++---------- 5 files changed, 89 insertions(+), 93 deletions(-) diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py index 2dd064ebca..a71f33c233 100644 --- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -2,8 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - -import erpnext, frappe +import frappe def execute(): frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index e6c519a482..0693583c88 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -21,7 +21,7 @@ class Gratuity(Document): self.status = "Paid" def on_submit(self): - create_additional_salary() + self.create_additional_salary() def create_additional_salary(self): if self.pay_via_salary_slip: @@ -194,6 +194,3 @@ def get_last_salary_slip(employee): }, order_by = "start_date desc")[0].name - - - diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index 9e9f3e204b..1a5347e792 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -22,7 +22,6 @@ frappe.ui.form.on('Gratuity Rule Slab', { Wrong order may lead to Wrong Calculation */ - gratuity_rule_slabs_add(frm, cdt, cdn) { let row = locals[cdt][cdn]; let array_idx = row.idx - 1; diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py index 00b5752eb8..29a6ebe1a6 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -17,4 +17,17 @@ class GratuityRule(Document): if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) +def get_gratuity_rule(name, slabs, **args): + args = frappe._dict(args) + rule = frappe.new_doc("Gratuity Rule") + rule.name = name + rule.calculate_gratuity_amount_based_on = args.calculate_gratuity_amount_based_on or "Current Slab" + rule.work_experience_calculation_method = args.work_experience_calculation_method or "Take Exact Completed Years" + rule.minimum_year_for_gratuity = 1 + + + for slab in slabs: + slab = frappe._dict(slab) + rule.append("gratuity_rule_slabs", slab) + return rule diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index b91318c9af..2a45c22590 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -7,6 +7,7 @@ import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax +from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule def setup(company=None, patch=True): make_custom_fields() @@ -161,104 +162,91 @@ def add_permissions(): update_permission_property(doctype, role, 0, 'create', 1) def create_gratuity_rule(): - - # Standard Gratuity Rules for UAE + rule_1 = rule_2 = rule_3 = None # Rule Under Limited Contract + slabs = get_slab_for_limited_contract() if not frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)"): - rule_1 = frappe.new_doc("Gratuity Rule") - rule_1.name = "Rule Under Limited Contract (UAE)" - rule_1.calculate_gratuity_amount_based_on = "Sum of all previous slabs" - rule_1.work_experience_calculation_method = "Take Exact Completed Years" - rule_1.minimum_year_for_gratuity = 1 - - rule_1.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) - - rule_1.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":5, - "fraction_of_applicable_earnings": 21/30 - }) - - rule_1.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": 1 - }) + rule_1 = get_gratuity_rule("Rule Under Limited Contract (UAE)", slabs, calculate_gratuity_amount_based_on="Sum of all previous slabs") # Rule Under Unlimited Contract on termination + slabs = get_slab_for_unlimited_contract_on_termination() if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)"): - rule_2 = frappe.new_doc("Gratuity Rule") - rule_2.name = "Rule Under Unlimited Contract on termination (UAE)" - rule_2.calculate_gratuity_amount_based_on = "Current Slab" - rule_2.work_experience_calculation_method = "Take Exact Completed Years" - rule_2.minimum_year_for_gratuity = 1 + rule_2 = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)", slabs) - rule_2.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) - - rule_2.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":5, - "fraction_of_applicable_earnings": 21/30 - }) - - rule_2.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": 1 - }) - - # Rule Under Unlimited Contract + # Rule Under Unlimited Contract on resignation + slabs = get_slab_for_unlimited_contract_on_resignation() if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on resignation (UAE)"): - rule_3 = frappe.new_doc("Gratuity Rule") - rule_3.name = "Rule Under Unlimited Contract on resignation (UAE)" - rule_3.calculate_gratuity_amount_based_on = "Current Slab" - rule_3.work_experience_calculation_method = "Take Exact Completed Years" - rule_3.minimum_year_for_gratuity = 1 + rule_3 = get_gratuity_rule("Rule Under Unlimited Contract on resignation (UAE)", slabs) - rule_3.append("gratuity_rule_slabs", { - "from_year": 0, - "to_year":1, - "fraction_of_applicable_earnings": 0 - }) - - fraction_of_applicable_earnings = 1/3 * 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 1, - "to_year":3, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) - - fraction_of_applicable_earnings = 2/3 * 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 3, - "to_year":5, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) - - fraction_of_applicable_earnings = 21/30 - rule_3.append("gratuity_rule_slabs", { - "from_year": 5, - "to_year":0, - "fraction_of_applicable_earnings": fraction_of_applicable_earnings - }) - - - #for applicable salary component user need to set this by its own + #for applicable salary component user need to set this by its own + if rule_1: rule_1.flags.ignore_mandatory = True - rule_2.flags.ignore_mandatory = True - rule_3.flags.ignore_mandatory = True - rule_1.save() + if rule_2: + rule_2.flags.ignore_mandatory = True rule_2.save() + if rule_3: + rule_3.flags.ignore_mandatory = True rule_3.save() +def get_slab_for_limited_contract(): + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }] + +def get_slab_for_unlimited_contract_on_termination(): + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }] + +def get_slab_for_unlimited_contract_on_resignation(): + fraction_1 = 1/3 * 21/30 + fraction_2 = 2/3 * 21/30 + fraction_3 = 21/30 + + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":3, + "fraction_of_applicable_earnings": fraction_1 + }, + { + "from_year": 3, + "to_year":5, + "fraction_of_applicable_earnings": fraction_2 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": fraction_3 + }] From 78fdd5d9b43061cd9be8da84973e8aaa30487f8e Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 28 Oct 2020 15:46:51 +0530 Subject: [PATCH 15/27] fix: update status on salary Slip submission --- erpnext/payroll/doctype/gratuity/gratuity.py | 7 ++----- .../doctype/salary_slip/salary_slip.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 0693583c88..db353e9d71 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -14,11 +14,8 @@ class Gratuity(Document): data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule) self.current_work_experience = data["current_work_experience"] self.amount = data["amount"] - - def before_submit(self): - self.status = "Unpaid" - if self.pay_via_salary_slip: - self.status = "Paid" + if self.docstatus == 1: + self.status = "Unpaid" def on_submit(self): self.create_additional_salary() diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 20365b191d..68147269b3 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -74,9 +74,27 @@ class SalarySlip(TransactionBase): if (frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry: self.email_salary_slip() + self.update_payment_status_for_gratuity() + + def update_payment_status_for_gratuity(self): + add_salary = frappe.db.get_all("Additional Salary", + filters = { + "payroll_date": ("BETWEEN", [self.start_date, self.end_date]), + "employee": self.employee, + "ref_doctype": "Gratuity", + "docstatus": 1, + }, fields = ["ref_docname", "name"])[0] + + status = "Paid" if self.docstatus == 1 else "Unpaid" + + + if add_salary and add_salary.name in [data.additional_salary for data in self.earnings]: + frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status) + def on_cancel(self): self.set_status() self.update_status() + self.update_payment_status_for_gratuity() self.cancel_loan_repayment_entry() def on_trash(self): @@ -566,6 +584,7 @@ class SalarySlip(TransactionBase): for d in self.get(key): if d.salary_component == struct_row.salary_component: component_row = d + if not component_row or (struct_row.get("is_additional_component") and not overwrite): if amount: self.append(key, { From b88af3a3f79e4cfd3d8db321b34c48ae276f5087 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 2 Nov 2020 18:35:03 +0530 Subject: [PATCH 16/27] feat: Accrural Entry for Gratuity beafore Payment --- .../doctype/payment_entry/payment_entry.py | 2 +- erpnext/payroll/doctype/gratuity/gratuity.js | 10 ++++ .../payroll/doctype/gratuity/gratuity.json | 20 +++++++- erpnext/payroll/doctype/gratuity/gratuity.py | 50 ++++++++++++++++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index df49667ed2..123db7ee9c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1182,7 +1182,7 @@ def set_party_account(dt, dn, doc, party_type): elif dt == "Expense Claim": party_account = doc.payable_account elif dt == "Gratuity": - party_account = doc.expense_account + party_account = doc.payable_account else: party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) return party_account diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index dfdf08bdea..9118ccc99d 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -19,6 +19,16 @@ frappe.ui.form.on('Gratuity', { } }; }); + + frm.set_query("payable_account", function() { + return { + filters: { + "root_type": "Liability", + "is_group": 0, + "company": frm.doc.company + } + }; + }); }, refresh: function(frm){ if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index b81ae588ea..5cffd7eebf 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -19,8 +19,10 @@ "pay_via_salary_slip", "payroll_date", "salary_component", + "payable_account", "expense_account", "mode_of_payment", + "cost_center", "column_break_15", "current_work_experience", "amount", @@ -173,12 +175,28 @@ "fieldtype": "Currency", "label": "Paid Amount", "read_only": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "payable_account", + "fieldtype": "Link", + "label": "Payable Account", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Account" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Cost Center" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-27 14:04:41.886934", + "modified": "2020-11-02 18:21:11.971488", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index db353e9d71..b09419116d 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -7,9 +7,11 @@ import frappe from frappe import _, bold from frappe.model.document import Document from frappe.utils import flt, get_datetime, get_link_to_form +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController from math import floor -class Gratuity(Document): +class Gratuity(AccountsController): def validate(self): data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule) self.current_work_experience = data["current_work_experience"] @@ -18,7 +20,51 @@ class Gratuity(Document): self.status = "Unpaid" def on_submit(self): - self.create_additional_salary() + if self.pay_via_salary_slip: + self.create_additional_salary() + else: + self.create_gl_entries() + + def on_cancel(self): + self.ignore_linked_doctypes = ['GL Entry'] + self.create_gl_entries(cancel=True) + + def create_gl_entries(self, cancel=False): + gl_entries = self.get_gl_entries() + make_gl_entries(gl_entries, cancel) + + def get_gl_entries(self): + gl_entry = [] + # payable entry + if self.amount: + gl_entry.append( + self.get_gl_dict({ + "account": self.payable_account, + "credit": self.amount, + "credit_in_account_currency": self.amount, + "against": self.expense_account, + "party_type": "Employee", + "party": self.employee, + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "cost_center": self.cost_center + }, item=self) + ) + + # expense entries + gl_entry.append( + self.get_gl_dict({ + "account": self.expense_account, + "debit": self.amount, + "debit_in_account_currency": self.amount, + "against": self.employee, + "cost_center": self.cost_center + }, item=self) + ) + else: + frappe.throw(_("Total Amount can not be zero")) + + return gl_entry def create_additional_salary(self): if self.pay_via_salary_slip: From 550e60a69d135d8804353abe159d22c912d0e3e1 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 2 Nov 2020 19:14:20 +0530 Subject: [PATCH 17/27] feat: fix test for Gratuity --- erpnext/payroll/doctype/gratuity/gratuity.py | 1 + .../payroll/doctype/gratuity/test_gratuity.py | 74 +++++++++---------- .../doctype/salary_slip/salary_slip.py | 11 ++- 3 files changed, 39 insertions(+), 47 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index b09419116d..e9e577c125 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -34,6 +34,7 @@ class Gratuity(AccountsController): make_gl_entries(gl_entries, cancel) def get_gl_entries(self): + print(self.payable_account, self.expense_account) gl_entry = [] # payable entry if self.amount: diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 680ecbcfc1..5053f886cb 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -9,27 +9,19 @@ from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule +from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account from frappe.utils import getdate, add_days, get_datetime, flt class TestGratuity(unittest.TestCase): def setUp(self): - frappe.db.sql("DELETE FROM `tabgratuity`") + frappe.db.sql("DELETE FROM `tabGratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): employee, sal_slip = create_employee_and_get_last_salary_slip() - rule = frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)") - if not rule: - create_gratuity_rule() - else: - rule = frappe.get_doc("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)") - rule.applicable_earnings_component = [] - rule.append("applicable_earnings_component", { - "salary_component": "Basic Salary" - }) - rule.save() - rule.reload() + + rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)") gratuity = frappe.new_doc("Gratuity") gratuity.employee = employee @@ -72,31 +64,12 @@ class TestGratuity(unittest.TestCase): #additional salary creation (Pay via salary slip) self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) - self.assertEqual(gratuity.status, "Paid") def test_check_gratuity_amount_based_on_all_previous_slabs(self): employee, sal_slip = create_employee_and_get_last_salary_slip() - rule = frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)") - if not rule: - create_gratuity_rule() - else: - rule = frappe.get_doc("Gratuity Rule", rule) - rule.applicable_earnings_component = [] - rule = frappe.get_doc("Gratuity Rule", "Rule Under Limited Contract (UAE)") - rule.append("applicable_earnings_component", { - "salary_component": "Basic Salary" - }) - rule.save() - rule.reload() - - mof = frappe.get_doc("Mode of Payment", "Cheque") - mof.accounts = [] - mof.append("accounts", { - "company": "_Test Company", - "default_account": "_Test Bank - _TC" - }) - - mof.save() + rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") + set_mode_of_payment_account() + payable_account = get_payable_account("_Test Company") gratuity = frappe.new_doc("Gratuity") gratuity.employee = employee @@ -105,6 +78,7 @@ class TestGratuity(unittest.TestCase): gratuity.pay_via_salary_slip = 0 gratuity.payroll_date = getdate() gratuity.expense_account = "Payment Account - _TC" + gratuity.payable_account = payable_account gratuity.mode_of_payment = "Cheque" gratuity.save() @@ -132,39 +106,57 @@ class TestGratuity(unittest.TestCase): }, fields=["amount"]) - ''' range | Fraction 0-1 | 0 1-5 | 0.7 5-0 | 1 ''' - gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount gratuity.reload() self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) self.assertEqual(gratuity.status, "Unpaid") - from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry - pay_entry = get_payment_entry("Gratuity", gratuity.name) pay_entry.reference_no = "123467" pay_entry.reference_date = getdate() - pay_entry.save() pay_entry.submit() - gratuity.reload() self.assertEqual(gratuity.status, "Paid") self.assertEqual(gratuity.paid_amount, flt(gratuity.amount, 2)) def tearDown(self): - frappe.db.sql("DELETE FROM `tabgratuity`") + frappe.db.sql("DELETE FROM `tabGratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") +def get_gratuity_rule(name): + rule = frappe.db.exists("Gratuity Rule", name) + if not rule: + create_gratuity_rule() + else: + rule = frappe.get_doc("Gratuity Rule", name) + rule.applicable_earnings_component = [] + rule.append("applicable_earnings_component", { + "salary_component": "Basic Salary" + }) + rule.save() + rule.reload() + + return rule + +def set_mode_of_payment_account(): + mode_of_payment = frappe.get_doc("Mode of Payment", "Cheque") + mode_of_payment.accounts = [] + mode_of_payment.append("accounts", { + "company": "_Test Company", + "default_account": "_Test Bank - _TC" + }) + mode_of_payment.save() + def create_employee_and_get_last_salary_slip(): employee = make_employee("test_employee@salary.com") frappe.db.set_value("Employee", employee, "relieving_date", getdate()) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 68147269b3..c7b83b9aeb 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -83,13 +83,12 @@ class SalarySlip(TransactionBase): "employee": self.employee, "ref_doctype": "Gratuity", "docstatus": 1, - }, fields = ["ref_docname", "name"])[0] + }, fields = ["ref_docname", "name"], limit=1) - status = "Paid" if self.docstatus == 1 else "Unpaid" - - - if add_salary and add_salary.name in [data.additional_salary for data in self.earnings]: - frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status) + if len(add_salary): + status = "Paid" if self.docstatus == 1 else "Unpaid" + if add_salary[0].name in [data.additional_salary for data in self.earnings]: + frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status) def on_cancel(self): self.set_status() From 66b697cd054b71604a560e8670642496d9e819b2 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 3 Nov 2020 12:33:00 +0530 Subject: [PATCH 18/27] feat: fix test for Gratuity --- erpnext/hr/doctype/employee/test_employee.py | 1 + erpnext/payroll/doctype/gratuity/gratuity.py | 1 - erpnext/payroll/doctype/gratuity/test_gratuity.py | 14 ++++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index f4b214adc3..ed8222aca1 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -46,6 +46,7 @@ class TestEmployee(unittest.TestCase): self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) def make_employee(user, company=None, **kwargs): + "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index e9e577c125..d2fc2f726f 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe import _, bold -from frappe.model.document import Document from frappe.utils import flt, get_datetime, get_link_to_form from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import AccountsController diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 5053f886cb..569c89b270 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -158,7 +158,7 @@ def set_mode_of_payment_account(): mode_of_payment.save() def create_employee_and_get_last_salary_slip(): - employee = make_employee("test_employee@salary.com") + employee = make_employee("test_employee@salary.com", company='_Test Company') frappe.db.set_value("Employee", employee, "relieving_date", getdate()) frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), - (6*365))) if not frappe.db.exists("Salary Slip", {"employee":employee}): @@ -168,5 +168,15 @@ def create_employee_and_get_last_salary_slip(): else: salary_slip = get_last_salary_slip(employee) - return employee, salary_slip + #just to see what going on travis will remove this + print(frappe.db.get_value("Employee", "test_employee@salary.com", "company")) + print(frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list")) + if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"): + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + make_holiday_list() + frappe.db.set_value("Company", '_Test Company', "default_holiday_list", "Salary Slip Test Holiday List") + + print(frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list")) + + return employee, salary_slip From af9f172be8a077f704a69a641f0ba44a32cfd6e2 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 24 Nov 2020 18:36:26 +0530 Subject: [PATCH 19/27] fix: requested changes and sider --- erpnext/payroll/doctype/gratuity/gratuity.js | 30 +++++++++---------- .../payroll/doctype/gratuity/test_gratuity.py | 11 ++----- .../doctype/gratuity_rule/gratuity_rule.js | 9 +++--- .../doctype/salary_slip/test_salary_slip.py | 1 + .../regional/united_arab_emirates/setup.py | 4 --- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index 9118ccc99d..565d2c49f9 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -2,15 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('Gratuity', { - setup: function(frm){ - frm.set_query('salary_component', function() { + setup: function (frm) { + frm.set_query('salary_component', function () { return { filters: { type: "Earning" } }; }); - frm.set_query("expense_account", function() { + frm.set_query("expense_account", function () { return { filters: { "root_type": "Expense", @@ -20,7 +20,7 @@ frappe.ui.form.on('Gratuity', { }; }); - frm.set_query("payable_account", function() { + frm.set_query("payable_account", function () { return { filters: { "root_type": "Liability", @@ -30,16 +30,16 @@ frappe.ui.form.on('Gratuity', { }; }); }, - refresh: function(frm){ - if(frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { - frm.add_custom_button(__("Create Payment Entry"), function() { + refresh: function (frm) { + if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + frm.add_custom_button(__("Create Payment Entry"), function () { return frappe.call({ method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', args: { "dt": frm.doc.doctype, "dn": frm.doc.name }, - callback: function(r) { + callback: function (r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); } @@ -47,18 +47,18 @@ frappe.ui.form.on('Gratuity', { }); } }, - employee: function(frm) { + employee: function (frm) { frm.events.calculate_work_experience_and_amount(frm); }, - gratuity_rule: function(frm){ + gratuity_rule: function (frm) { frm.events.calculate_work_experience_and_amount(frm); }, - calculate_work_experience_and_amount: function(frm) { + calculate_work_experience_and_amount: function (frm) { - if(frm.doc.employee && frm.doc.gratuity_rule){ + if (frm.doc.employee && frm.doc.gratuity_rule) { frappe.call({ - method:"erpnext.payroll.doctype.gratuity.gratuity.calculate_work_experience_and_amount", - args:{ + method: "erpnext.payroll.doctype.gratuity.gratuity.calculate_work_experience_and_amount", + args: { employee: frm.doc.employee, gratuity_rule: frm.doc.gratuity_rule } @@ -69,4 +69,4 @@ frappe.ui.form.on('Gratuity', { } } -}); +}); \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 569c89b270..0e485cc830 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -6,15 +6,16 @@ from __future__ import unicode_literals import frappe import unittest from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account from frappe.utils import getdate, add_days, get_datetime, flt - +test_dependencies = ["Salary Component", "Salary Slip"] class TestGratuity(unittest.TestCase): def setUp(self): + make_earning_salary_component() frappe.db.sql("DELETE FROM `tabGratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") @@ -168,15 +169,9 @@ def create_employee_and_get_last_salary_slip(): else: salary_slip = get_last_salary_slip(employee) - #just to see what going on travis will remove this - print(frappe.db.get_value("Employee", "test_employee@salary.com", "company")) - print(frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list")) - if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"): from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list make_holiday_list() frappe.db.set_value("Company", '_Test Company', "default_holiday_list", "Salary Slip Test Holiday List") - print(frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list")) - return employee, salary_slip diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index 1a5347e792..ee6c5df737 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -25,17 +25,16 @@ frappe.ui.form.on('Gratuity Rule Slab', { gratuity_rule_slabs_add(frm, cdt, cdn) { let row = locals[cdt][cdn]; let array_idx = row.idx - 1; - if(array_idx > 0){ - row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx-1].to_year; + if (array_idx > 0) { + row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx - 1].to_year; frm.refresh(); } }, to_year(frm, cdt, cdn) { let row = locals[cdt][cdn]; - if (row.to_year <= row.from_year && row.to_year === 0){ + if (row.to_year <= row.from_year && row.to_year === 0) { frappe.throw(__("To(Year) year can not be less than From(year) ")); } } -}); - +}); \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 5daf1d439d..634500fc47 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -21,6 +21,7 @@ from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_ta class TestSalarySlip(unittest.TestCase): def setUp(self): setup_test() + def tearDown(self): frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) frappe.set_user("Administrator") diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 2a45c22590..c26633675f 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -12,13 +12,9 @@ from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rul def setup(company=None, patch=True): make_custom_fields() add_print_formats() -<<<<<<< HEAD add_custom_roles_for_reports() add_permissions() - create_standard_documents() -======= create_gratuity_rule() ->>>>>>> test: gratuity if company: create_sales_tax(company) From 708065cb851423311c3eebaa3e9a65a93e5275d0 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 1 Dec 2020 18:54:31 +0530 Subject: [PATCH 20/27] fix: test cases --- .../payroll/doctype/gratuity/test_gratuity.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 0e485cc830..571eef4a6c 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -6,16 +6,18 @@ from __future__ import unicode_literals import frappe import unittest from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component, \ + make_deduction_salary_component from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account from frappe.utils import getdate, add_days, get_datetime, flt -test_dependencies = ["Salary Component", "Salary Slip"] +test_dependencies = ["Salary Component", "Salary Slip", "Account"] class TestGratuity(unittest.TestCase): def setUp(self): - make_earning_salary_component() + make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) + make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) frappe.db.sql("DELETE FROM `tabGratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") @@ -39,14 +41,11 @@ class TestGratuity(unittest.TestCase): employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days experience = employee_total_workings_days/rule.total_working_days_per_year - gratuity.reload() - from math import floor - self.assertEqual(floor(experience), gratuity.current_work_experience) - #amount Calculation 6 + #amount Calculation component_amount = frappe.get_list("Salary Detail", filters={ "docstatus": 1, @@ -80,7 +79,7 @@ class TestGratuity(unittest.TestCase): gratuity.payroll_date = getdate() gratuity.expense_account = "Payment Account - _TC" gratuity.payable_account = payable_account - gratuity.mode_of_payment = "Cheque" + gratuity.mode_of_payment = "Cash" gratuity.save() gratuity.submit() @@ -97,7 +96,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) - #amount Calculation 6 + #amount Calculation component_amount = frappe.get_list("Salary Detail", filters={ "docstatus": 1, @@ -150,7 +149,11 @@ def get_gratuity_rule(name): return rule def set_mode_of_payment_account(): - mode_of_payment = frappe.get_doc("Mode of Payment", "Cheque") + if not frappe.db.exists("Account", "Payment Account - _TC"): + mode_of_payment = create_account() + else: + mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") + mode_of_payment.accounts = [] mode_of_payment.append("accounts", { "company": "_Test Company", @@ -158,6 +161,18 @@ def set_mode_of_payment_account(): }) mode_of_payment.save() +def create_account(): + return frappe.get_doc({ + "doctype": "Account", + "company": "_Test Company", + "account_name": "Payment Account", + "root_type": "Asset", + "report_type": "Balance Sheet", + "currency": "INR", + "parent_account": "Bank Accounts - _TC", + "account_type": "Bank", + }).insert(ignore_permissions=True) + def create_employee_and_get_last_salary_slip(): employee = make_employee("test_employee@salary.com", company='_Test Company') frappe.db.set_value("Employee", employee, "relieving_date", getdate()) From ba6ff6e2270302c83416d74ec13a3513ef3cc892 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 2 Dec 2020 13:32:02 +0530 Subject: [PATCH 21/27] fix: conflict --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- erpnext/payroll/doctype/gratuity/test_gratuity.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 123db7ee9c..8d2907e0fb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1036,7 +1036,7 @@ def get_total_amount_exchange_rate_base_on_currency(party_account_currency, comp def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency): outstanding_amount, bill_no = None -if reference_doctype in ("Sales Invoice", "Purchase Invoice"): + if reference_doctype in ("Sales Invoice", "Purchase Invoice"): outstanding_amount = ref_doc.get("outstanding_amount") bill_no = ref_doc.get("bill_no") elif reference_doctype == "Expense Claim": diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 571eef4a6c..f32e0eb74e 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -137,8 +137,7 @@ def get_gratuity_rule(name): rule = frappe.db.exists("Gratuity Rule", name) if not rule: create_gratuity_rule() - else: - rule = frappe.get_doc("Gratuity Rule", name) + rule = frappe.get_doc("Gratuity Rule", name) rule.applicable_earnings_component = [] rule.append("applicable_earnings_component", { "salary_component": "Basic Salary" From 4cc333996c1e2b4516f2c03a6a52c829bd09d297 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 3 Dec 2020 11:47:23 +0530 Subject: [PATCH 22/27] fix: test --- .../payroll/doctype/gratuity/test_gratuity.py | 51 ++++++++++--------- .../doctype/gratuity_rule/gratuity_rule.json | 6 +-- .../doctype/payroll_entry/payroll_entry.py | 2 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index f32e0eb74e..e89e3dd077 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -26,15 +26,7 @@ class TestGratuity(unittest.TestCase): rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)") - gratuity = frappe.new_doc("Gratuity") - gratuity.employee = employee - gratuity.posting_date = getdate() - gratuity.gratuity_rule = rule.name - gratuity.pay_via_salary_slip = 1 - gratuity.salary_component = "Performance Bonus" - gratuity.payroll_date = getdate() - gratuity.save() - gratuity.submit() + gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name) #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -69,20 +61,8 @@ class TestGratuity(unittest.TestCase): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") set_mode_of_payment_account() - payable_account = get_payable_account("_Test Company") - gratuity = frappe.new_doc("Gratuity") - gratuity.employee = employee - gratuity.posting_date = getdate() - gratuity.gratuity_rule = rule.name - gratuity.pay_via_salary_slip = 0 - gratuity.payroll_date = getdate() - gratuity.expense_account = "Payment Account - _TC" - gratuity.payable_account = payable_account - gratuity.mode_of_payment = "Cash" - - gratuity.save() - gratuity.submit() + gratuity = create_gratuity(expense_account = 'Payment Account - _TC', mode_of_payment='Cash', employee=employee) #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -127,7 +107,7 @@ class TestGratuity(unittest.TestCase): gratuity.reload() self.assertEqual(gratuity.status, "Paid") - self.assertEqual(gratuity.paid_amount, flt(gratuity.amount, 2)) + self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2)) def tearDown(self): frappe.db.sql("DELETE FROM `tabGratuity`") @@ -147,11 +127,32 @@ def get_gratuity_rule(name): return rule +def create_gratuity(**args): + if args: + args = frappe._dict(args) + gratuity = frappe.new_doc("Gratuity") + gratuity.employee = args.employee + gratuity.posting_date = getdate() + gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)" + gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0 + if gratuity.pay_via_salary_slip: + gratuity.payroll_date = getdate() + gratuity.salary_component = "Performance Bonus" + else: + gratuity.expense_account = args.expense_account or 'Payment Account - _TC' + gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") + gratuity.mode_of_payment = args.mode_of_payment or 'Cash' + + gratuity.save() + gratuity.submit() + + return gratuity + def set_mode_of_payment_account(): if not frappe.db.exists("Account", "Payment Account - _TC"): mode_of_payment = create_account() - else: - mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") + + mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") mode_of_payment.accounts = [] mode_of_payment.append("accounts", { diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json index 7d24e41f48..84cdcf5038 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -27,7 +27,7 @@ "fieldname": "calculate_gratuity_amount_based_on", "fieldtype": "Select", "in_list_view": 1, - "label": "Calculate Gratuity Amount Based on", + "label": "Calculate Gratuity Amount Based On", "options": "Current Slab\nSum of all previous slabs", "reqd": 1 }, @@ -67,7 +67,7 @@ "default": "365", "fieldname": "total_working_days_per_year", "fieldtype": "Int", - "label": "Total Working Days Per Year" + "label": "Total working Days Per Year" }, { "fieldname": "minimum_year_for_gratuity", @@ -77,7 +77,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-27 14:04:31.617621", + "modified": "2020-12-03 17:08:27.891535", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity Rule", diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 8c2d9740ec..4cf4542a4f 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -542,7 +542,7 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): title = _("Creating Salary Slips...")) else: salary_slip_name = frappe.db.sql( - '''SELECT + '''SELECT name FROM `tabSalary Slip` WHERE company=%s From b71611dd3da99c18563cd32d317566cc37e0d820 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Dec 2020 15:05:57 +0530 Subject: [PATCH 23/27] feat: Added Form dashboard --- .../doctype/gratuity/gratuity_dashboard.py | 20 +++++++++++++++++++ .../gratuity_rule/gratuity_rule_dashboard.py | 13 ++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 erpnext/payroll/doctype/gratuity/gratuity_dashboard.py create mode 100644 erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py new file mode 100644 index 0000000000..5b2489f22c --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'reference_name', + 'non_standard_fieldnames': { + 'Additional Salary': 'ref_docname', + }, + 'transactions': [ + { + 'label': _('Payment'), + 'items': ['Payment Entry'] + }, + { + 'label': _('Additional Salary'), + 'items': ['Additional Salary'] + } + ] + } \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py new file mode 100644 index 0000000000..0d70163495 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'gratuity_rule', + 'transactions': [ + { + 'label': _('Gratuity'), + 'items': ['Gratuity'] + } + ] + } \ No newline at end of file From 4da7a15ac048f7592351891e150be628727ae8fd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 11 Dec 2020 09:45:58 +0530 Subject: [PATCH 24/27] fix: Fixed typo --- .../patches/v13_0/setup_gratuity_rule_for_india_and_uae.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py index a71f33c233..01fd6a158e 100644 --- a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -8,9 +8,9 @@ def execute(): frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab') frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component') - if frappe.db.exists("company", {"country": "India"}): + if frappe.db.exists("Company", {"country": "India"}): from erpnext.regional.india.setup import create_gratuity_rule create_gratuity_rule() - if frappe.db.exists("company", {"country": "United Arab Emirates"}): + if frappe.db.exists("Company", {"country": "United Arab Emirates"}): from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule - create_gratuity_rule() \ No newline at end of file + create_gratuity_rule() From eb065d6d40d3af79d8802264db9af1658d8a0553 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 11 Dec 2020 09:50:50 +0530 Subject: [PATCH 25/27] fix: fixed gl entries --- erpnext/payroll/doctype/gratuity/gratuity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index d2fc2f726f..5a08f6a24c 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -33,7 +33,6 @@ class Gratuity(AccountsController): make_gl_entries(gl_entries, cancel) def get_gl_entries(self): - print(self.payable_account, self.expense_account) gl_entry = [] # payable entry if self.amount: @@ -57,7 +56,7 @@ class Gratuity(AccountsController): "account": self.expense_account, "debit": self.amount, "debit_in_account_currency": self.amount, - "against": self.employee, + "against": self.payable_account, "cost_center": self.cost_center }, item=self) ) From 1446749b63be791d309de2ecb29208135e2db2f7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 17 Dec 2020 14:24:05 +0530 Subject: [PATCH 26/27] fix: changes requested --- erpnext/payroll/doctype/gratuity/gratuity.py | 53 ++++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 5a08f6a24c..7160bf0ef6 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -113,7 +113,13 @@ def calculate_work_experience(employee, gratuity_rule): frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(get_link_to_form("Employee", employee)))) method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") + employee_total_workings_days = calculate_employee_total_workings_days(employee, date_of_joining, relieving_date) + current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 + current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity) + return current_work_experience + +def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date ): employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days payroll_based_on = frappe.db.get_value("Payroll Settings", None, "payroll_based_on") or "Leave" @@ -124,10 +130,9 @@ def calculate_work_experience(employee, gratuity_rule): total_absents = get_non_working_days(employee, relieving_date, "Absent") employee_total_workings_days -= total_absents - # current_work_experience = time_difference.years - - current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 + return employee_total_workings_days +def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity): if method == "Round off Work Experience": current_work_experience = round(current_work_experience) else: @@ -135,7 +140,6 @@ def calculate_work_experience(employee, gratuity_rule): if current_work_experience < minimum_year_for_gratuity: frappe.throw(_("Employee: {0} have to complete minimum {1} years for gratuity").format(bold(employee), minimum_year_for_gratuity)) - return current_work_experience def get_non_working_days(employee, relieving_date, status): @@ -157,27 +161,22 @@ def get_non_working_days(employee, relieving_date, status): return record[0].total_lwp if len(record) else 0 def calculate_gratuity_amount(employee, gratuity_rule, experience): - applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) - if len(applicable_earnings_component) == 0: - frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule)))) - applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] - - slabs = get_gratuity_rule_slabs(gratuity_rule) - + applicable_earnings_component = get_applicable_components(gratuity_rule) total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") - gratuity_amount = 0 + slabs = get_gratuity_rule_slabs(gratuity_rule) slab_found = False year_left = experience + for slab in slabs: if calculate_gratuity_amount_based_on == "Current Slab": - if experience >= slab.from_year and (slab.to_year == 0 or experience < slab.to_year): - gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings - if slab.fraction_of_applicable_earnings: - slab_found = True + slab_found, gratuity_amount = calculate_amount_based_on_current_slab(slab.from_year, slab.to_year, + experience, total_applicable_components_amount, slab.fraction_of_applicable_earnings) + if slab_found == True: break + elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": if slab.to_year == 0 and slab.from_year == 0: gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings @@ -194,16 +193,20 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): if not slab_found: frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) - - return gratuity_amount +def get_applicable_components(gratuity_rule): + applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) + if len(applicable_earnings_component) == 0: + frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule)))) + applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] + + return applicable_earnings_component + def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): sal_slip = get_last_salary_slip(employee) - if not sal_slip: frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_list("Salary Detail", filters={ "docstatus": 1, @@ -217,9 +220,17 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen frappe.throw(_("No Applicable Component is present in last month salary slip")) for data in component_and_amounts: total_applicable_components_amount += data.amount - return total_applicable_components_amount +def calculate_amount_based_on_current_slab(from_year, to_year, experience, total_applicable_components_amount, fraction_of_applicable_earnings): + slab_found = False; gratuity_amount = 0 + if experience >= from_year and (to_year == 0 or experience < to_year): + gratuity_amount = total_applicable_components_amount * experience * fraction_of_applicable_earnings + if fraction_of_applicable_earnings: + slab_found = True + + return slab_found, gratuity_amount + def get_gratuity_rule_slabs(gratuity_rule): return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx") From 3278d02cd2c3a910cbe2ca0b032047ada198cdfa Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 23 Dec 2020 18:06:46 +0530 Subject: [PATCH 27/27] fix: sider --- erpnext/payroll/doctype/gratuity/gratuity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 7160bf0ef6..1acd6e342f 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -116,7 +116,7 @@ def calculate_work_experience(employee, gratuity_rule): employee_total_workings_days = calculate_employee_total_workings_days(employee, date_of_joining, relieving_date) current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 - current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity) + current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee) return current_work_experience def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date ): @@ -132,7 +132,7 @@ def calculate_employee_total_workings_days(employee, date_of_joining, relieving_ return employee_total_workings_days -def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity): +def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee): if method == "Round off Work Experience": current_work_experience = round(current_work_experience) else: @@ -174,7 +174,7 @@ def calculate_gratuity_amount(employee, gratuity_rule, experience): if calculate_gratuity_amount_based_on == "Current Slab": slab_found, gratuity_amount = calculate_amount_based_on_current_slab(slab.from_year, slab.to_year, experience, total_applicable_components_amount, slab.fraction_of_applicable_earnings) - if slab_found == True: + if slab_found: break elif calculate_gratuity_amount_based_on == "Sum of all previous slabs":