feat(HR): Gratuity Payment

This commit is contained in:
Anurag Mishra 2020-08-14 12:53:06 +05:30
parent fc2cb3a85e
commit 49b326fc08
5 changed files with 135 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

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