feat(HR): Gratuity Payment
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 31a4c8a..df49667 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -242,7 +242,7 @@
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 @@
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 @@
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 @@
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 @@
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_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 @@
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 @@
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 @@
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
+ elif dt == "Gratuity":
+ grand_total = doc.amount
+ outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js
index cbf5119..d6e93af 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.js
+++ b/erpnext/payroll/doctype/gratuity/gratuity.js
@@ -5,7 +5,17 @@
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 @@
}
};
});
+ 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 @@
frm.set_value("amount", r.message['amount']);
});
}
- },
- make_payment_entry: function(frm){
- console.log("Hello");
}
});
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json
index 8e7bb86..b8122df 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -24,6 +24,7 @@
"column_break_15",
"current_work_experience",
"amount",
+ "paid_amount",
"amended_from"
],
"fields": [
@@ -77,7 +78,7 @@
"default": "0",
"fieldname": "amount",
"fieldtype": "Currency",
- "label": "Amount",
+ "label": "Total Amount",
"read_only": 1,
"reqd": 1
},
@@ -164,11 +165,19 @@
"fieldtype": "Date",
"label": "Payroll Date",
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.pay_via_salary_slip == 0",
+ "fieldname": "paid_amount",
+ "fieldtype": "Currency",
+ "label": "Paid Amount",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-08-06 15:51:16.047698",
+ "modified": "2020-08-14 11:59:15.499548",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Gratuity",
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py
index fe31f4d..23cc16b 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity.py
@@ -6,13 +6,19 @@
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 @@
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 @@
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 @@
total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule)
- fraction_to_be_paid = 0
+ calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on")
+
+ gratuity_amount = 0
+ fraction_to_be_paid = 0
+ year_left = experience
for slab in slabs:
- if experience > slab.get("from", 0) and (slab.to == 0 or experience < slab.to):
- fraction_to_be_paid = slab.fraction_of_applicable_earnings
- if fraction_to_be_paid:
+ if calculate_gratuity_amount_based_on == "Single Slab":
+ if experience >= slab.get("from", 0) and (slab.to == 0 or experience <= slab.to):
+ gratuity_amount = total_applicable_components_amount * experience * slab.fraction_of_applicable_earnings
+ if slab.fraction_of_applicable_earnings:
+ break
+ elif calculate_gratuity_amount_based_on == "Sum of all previous slabs":
+ if slab.get("to") == 0 and slab.get("from") == 0:
+ gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
break
- gratuity_amount = total_applicable_components_amount * experience * fraction_to_be_paid
+ if experience > slab.get("to") and experience > slab.get("from"):
+ gratuity_amount += (slab.get("to") - slab.get("from")) * total_applicable_components_amount * slab.fraction_of_applicable_earnings
+ year_left -= (slab.get("to") - slab.get("from"))
+ print(experience, year_left)
+ elif slab.get("from") < experience < slab.get("to"):
+ print(year_left)
+ gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
+
+
return gratuity_amount
def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule):
- calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on")
- if calculate_gratuity_amount_based_on == "Last Month Salary":
- sal_slip = get_last_salary_slip(employee)
+ sal_slip = get_last_salary_slip(employee)
- if not sal_slip:
- frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee)))
+ if not sal_slip:
+ frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee)))
- component_and_amounts = frappe.get_list("Salary Detail",
- filters={
- "docstatus": 1,
- 'parent': sal_slip,
- "parentfield": "earnings",
- 'salary_component': ('in', applicable_earnings_component)
- },
- fields=["amount"])
- total_applicable_components_amount = 0
- if not len(component_and_amounts):
- frappe.throw("No Applicable Component is present in last month salary slip")
- for data in component_and_amounts:
- total_applicable_components_amount += data.amount
- elif calculate_gratuity_amount_based_on == "Actual Salary":
- pass
+ component_and_amounts = frappe.get_list("Salary Detail",
+ filters={
+ "docstatus": 1,
+ 'parent': sal_slip,
+ "parentfield": "earnings",
+ 'salary_component': ('in', applicable_earnings_component)
+ },
+ fields=["amount"])
+ total_applicable_components_amount = 0
+ if not len(component_and_amounts):
+ frappe.throw("No Applicable Component is present in last month salary slip")
+ for data in component_and_amounts:
+ total_applicable_components_amount += data.amount
return total_applicable_components_amount
diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
index b5de281..40906fa 100644
--- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
+++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
@@ -8,6 +8,7 @@
"field_order": [
"applicable_earnings_component",
"work_experience_calculation_function",
+ "total_working_days_per_year",
"column_break_3",
"disable",
"calculate_gratuity_amount_based_on",
@@ -26,7 +27,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Calculate Gratuity Amount Based on",
- "options": "Last Month Salary\nActual Salary",
+ "options": "Single Slab\nSum of all previous slabs",
"reqd": 1
},
{
@@ -60,10 +61,15 @@
"fieldtype": "Select",
"label": "Work Experience Calculation method",
"options": "Round off Work Experience\nTake Exact Completed Years"
+ },
+ {
+ "fieldname": "total_working_days_per_year",
+ "fieldtype": "Int",
+ "label": "Total Working Days per year"
}
],
"links": [],
- "modified": "2020-08-06 12:28:13.757792",
+ "modified": "2020-08-13 16:21:10.466739",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Gratuity Rule",