feat(HR): Gratuity Payment
This commit is contained in:
parent
fc2cb3a85e
commit
49b326fc08
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user