Feat: gratuity

This commit is contained in:
Anurag Mishra 2020-08-10 11:52:34 +05:30
parent aa7ab211be
commit fc2cb3a85e
17 changed files with 575 additions and 1 deletions

View File

@ -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",

View File

@ -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");
}
});

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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) {
// }
});

View File

@ -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 <b>From</b> and <b>To</b> 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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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