fix: merge conflict
This commit is contained in:
commit
6e3668dc30
@ -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."
|
||||
|
@ -244,7 +244,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")
|
||||
elif self.party_type == "Donor":
|
||||
@ -612,7 +612,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):
|
||||
@ -950,6 +950,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
|
||||
@ -973,6 +975,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:
|
||||
@ -1178,7 +1182,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 == "Fees":
|
||||
party_type = "Student"
|
||||
@ -1197,6 +1201,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.payable_account
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
return party_account
|
||||
@ -1245,6 +1251,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
||||
elif dt == "Donation":
|
||||
grand_total = doc.amount
|
||||
outstanding_amount = doc.amount
|
||||
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)
|
||||
|
@ -813,7 +813,7 @@
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2021-01-01 16:54:33.477439",
|
||||
"modified": "2021-01-02 16:54:33.477439",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -48,6 +48,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",
|
||||
|
@ -757,4 +757,5 @@ erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
||||
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
||||
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
|
||||
|
@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import 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')
|
||||
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"}):
|
||||
from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
|
||||
create_gratuity_rule()
|
0
erpnext/payroll/doctype/gratuity/__init__.py
Normal file
0
erpnext/payroll/doctype/gratuity/__init__.py
Normal file
72
erpnext/payroll/doctype/gratuity/gratuity.js
Normal file
72
erpnext/payroll/doctype/gratuity/gratuity.js
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Gratuity', {
|
||||
setup: function (frm) {
|
||||
frm.set_query('salary_component', function () {
|
||||
return {
|
||||
filters: {
|
||||
type: "Earning"
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("expense_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
"root_type": "Expense",
|
||||
"is_group": 0,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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") {
|
||||
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);
|
||||
},
|
||||
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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
232
erpnext/payroll/doctype/gratuity/gratuity.json
Normal file
232
erpnext/payroll/doctype/gratuity/gratuity.json
Normal file
@ -0,0 +1,232 @@
|
||||
{
|
||||
"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",
|
||||
"payable_account",
|
||||
"expense_account",
|
||||
"mode_of_payment",
|
||||
"cost_center",
|
||||
"column_break_15",
|
||||
"current_work_experience",
|
||||
"amount",
|
||||
"paid_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": "Total 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"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.pay_via_salary_slip == 0",
|
||||
"fieldname": "paid_amount",
|
||||
"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-11-02 18:21:11.971488",
|
||||
"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": "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
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
249
erpnext/payroll/doctype/gratuity/gratuity.py
Normal file
249
erpnext/payroll/doctype/gratuity/gratuity.py
Normal file
@ -0,0 +1,249 @@
|
||||
# -*- 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.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(AccountsController):
|
||||
def validate(self):
|
||||
data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule)
|
||||
self.current_work_experience = data["current_work_experience"]
|
||||
self.amount = data["amount"]
|
||||
if self.docstatus == 1:
|
||||
self.status = "Unpaid"
|
||||
|
||||
def on_submit(self):
|
||||
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.payable_account,
|
||||
"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:
|
||||
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()
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
|
||||
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:
|
||||
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, employee)
|
||||
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"
|
||||
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
|
||||
|
||||
return employee_total_workings_days
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
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"])
|
||||
return record[0].total_lwp if len(record) else 0
|
||||
|
||||
def calculate_gratuity_amount(employee, gratuity_rule, experience):
|
||||
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":
|
||||
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:
|
||||
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 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 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)))
|
||||
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,
|
||||
'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
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
|
20
erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
Normal file
20
erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
Normal file
@ -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']
|
||||
}
|
||||
]
|
||||
}
|
192
erpnext/payroll/doctype/gratuity/test_gratuity.py
Normal file
192
erpnext/payroll/doctype/gratuity/test_gratuity.py
Normal file
@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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, \
|
||||
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", "Account"]
|
||||
class TestGratuity(unittest.TestCase):
|
||||
def setUp(self):
|
||||
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'")
|
||||
|
||||
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 = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
|
||||
|
||||
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'])
|
||||
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
|
||||
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
|
||||
gratuity.reload()
|
||||
|
||||
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": gratuity.name}))
|
||||
|
||||
def test_check_gratuity_amount_based_on_all_previous_slabs(self):
|
||||
employee, sal_slip = create_employee_and_get_last_salary_slip()
|
||||
rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
|
||||
set_mode_of_payment_account()
|
||||
|
||||
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'])
|
||||
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
|
||||
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
|
||||
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(flt(gratuity.paid_amount,2), 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 get_gratuity_rule(name):
|
||||
rule = frappe.db.exists("Gratuity Rule", name)
|
||||
if not rule:
|
||||
create_gratuity_rule()
|
||||
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 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()
|
||||
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
|
||||
|
||||
mode_of_payment.accounts = []
|
||||
mode_of_payment.append("accounts", {
|
||||
"company": "_Test Company",
|
||||
"default_account": "_Test Bank - _TC"
|
||||
})
|
||||
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())
|
||||
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)
|
||||
|
||||
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")
|
||||
|
||||
return employee, salary_slip
|
@ -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
|
||||
}
|
@ -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
|
0
erpnext/payroll/doctype/gratuity_rule/__init__.py
Normal file
0
erpnext/payroll/doctype/gratuity_rule/__init__.py
Normal file
40
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js
Normal file
40
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js
Normal file
@ -0,0 +1,40 @@
|
||||
// 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) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
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;
|
||||
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) {
|
||||
frappe.throw(__("To(Year) year can not be less than From(year) "));
|
||||
}
|
||||
}
|
||||
});
|
114
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
Normal file
114
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"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",
|
||||
"total_working_days_per_year",
|
||||
"column_break_3",
|
||||
"disable",
|
||||
"calculate_gratuity_amount_based_on",
|
||||
"minimum_year_for_gratuity",
|
||||
"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": "Current Slab\nSum of all previous slabs",
|
||||
"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> 0 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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 17:08:27.891535",
|
||||
"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": "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
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
33
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py
Normal file
33
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- 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
|
||||
from frappe import _
|
||||
|
||||
class GratuityRule(Document):
|
||||
|
||||
def validate(self):
|
||||
for current_slab in self.gratuity_rule_slabs:
|
||||
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."))
|
||||
|
||||
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
|
@ -0,0 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'gratuity_rule',
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Gratuity'),
|
||||
'items': ['Gratuity']
|
||||
}
|
||||
]
|
||||
}
|
10
erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py
Normal file
10
erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py
Normal 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
|
@ -0,0 +1,50 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-08-05 19:12:49.423500",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"from_year",
|
||||
"to_year",
|
||||
"fraction_of_applicable_earnings"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"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-17 14:09:56.781712",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Gratuity Rule Slab",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -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
|
@ -80,9 +80,26 @@ 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"], limit=1)
|
||||
|
||||
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()
|
||||
self.update_status()
|
||||
self.update_payment_status_for_gratuity()
|
||||
self.cancel_loan_repayment_entry()
|
||||
|
||||
def on_trash(self):
|
||||
@ -574,6 +591,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, {
|
||||
|
@ -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")
|
||||
|
@ -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_gratuity_rule()
|
||||
add_print_formats()
|
||||
|
||||
def add_hsn_sac_codes():
|
||||
@ -839,4 +840,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}])
|
||||
]
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
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()
|
@ -7,12 +7,15 @@ 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()
|
||||
add_print_formats()
|
||||
add_custom_roles_for_reports()
|
||||
add_permissions()
|
||||
create_gratuity_rule()
|
||||
|
||||
if company:
|
||||
create_sales_tax(company)
|
||||
|
||||
@ -155,3 +158,93 @@ 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_gratuity_rule():
|
||||
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 = 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 = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)", slabs)
|
||||
|
||||
# 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 = get_gratuity_rule("Rule Under Unlimited Contract on resignation (UAE)", slabs)
|
||||
|
||||
#for applicable salary component user need to set this by its own
|
||||
if rule_1:
|
||||
rule_1.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
|
||||
}]
|
||||
|
Loading…
x
Reference in New Issue
Block a user