Introduce the ability to specify in a Salary Structure that a component is statistical. This allows components to be used in calculations without being added/deducted from earnings deductions.
This commit is contained in:
parent
4782e8b751
commit
64f29f819a
@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
@ -21,7 +22,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Component",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -31,6 +34,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
@ -49,7 +53,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Abbr",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -59,6 +65,65 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ",
|
||||
"fieldname": "statistical_component",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Statistical Component",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -76,7 +141,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -84,6 +151,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -102,7 +170,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Condition",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -111,6 +181,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -130,7 +201,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount based on formula",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -140,6 +213,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -160,7 +234,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Formula",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -169,6 +245,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -187,7 +264,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -197,6 +276,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -215,7 +295,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Depends on Leave Without Pay",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -224,6 +306,7 @@
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -242,7 +325,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -252,6 +337,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -270,7 +356,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -278,6 +366,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -296,7 +385,9 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Condition and Formula Help",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@ -306,6 +397,7 @@
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
@ -313,18 +405,18 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-09-20 05:29:26.373992",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2017-04-12 22:47:33.980646",
|
||||
"modified_by": "chude.osiegbu@manqala.com",
|
||||
"module": "HR",
|
||||
"name": "Salary Detail",
|
||||
"name_case": "",
|
||||
@ -333,7 +425,9 @@
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -39,7 +39,7 @@ frappe.ui.form.on("Salary Slip", {
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
frm.trigger("toggle_reqd_fields")
|
||||
salary_detail_fields = ['formula', 'abbr']
|
||||
salary_detail_fields = ['formula', 'abbr', 'statistical_component']
|
||||
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
|
||||
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
|
||||
},
|
||||
@ -129,16 +129,15 @@ var calculate_earning_total = function(doc, dt, dn, reset_amount) {
|
||||
var tbl = doc.earnings || [];
|
||||
var total_earn = 0;
|
||||
for(var i = 0; i < tbl.length; i++){
|
||||
if(cint(tbl[i].depends_on_lwp) == 1) {
|
||||
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
|
||||
cint(doc.total_working_days)*100)/100;
|
||||
refresh_field('amount', tbl[i].name, 'earnings');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].amount = tbl[i].default_amount;
|
||||
refresh_field('amount', tbl[i].name, 'earnings');
|
||||
}
|
||||
total_earn += flt(tbl[i].amount);
|
||||
|
||||
if(cint(tbl[i].depends_on_lwp) == 1) {
|
||||
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
|
||||
cint(doc.total_working_days)*100)/100;
|
||||
refresh_field('amount', tbl[i].name, 'earnings');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].amount = tbl[i].default_amount;
|
||||
refresh_field('amount', tbl[i].name, 'earnings');
|
||||
}
|
||||
total_earn += flt(tbl[i].amount);
|
||||
}
|
||||
doc.gross_pay = total_earn;
|
||||
refresh_many(['amount','gross_pay']);
|
||||
@ -150,14 +149,14 @@ var calculate_ded_total = function(doc, dt, dn, reset_amount) {
|
||||
var tbl = doc.deductions || [];
|
||||
var total_ded = 0;
|
||||
for(var i = 0; i < tbl.length; i++){
|
||||
if(cint(tbl[i].depends_on_lwp) == 1) {
|
||||
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
|
||||
refresh_field('amount', tbl[i].name, 'deductions');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].amount = tbl[i].default_amount;
|
||||
refresh_field('amount', tbl[i].name, 'deductions');
|
||||
}
|
||||
total_ded += flt(tbl[i].amount);
|
||||
if(cint(tbl[i].depends_on_lwp) == 1) {
|
||||
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
|
||||
refresh_field('amount', tbl[i].name, 'deductions');
|
||||
} else if(reset_amount) {
|
||||
tbl[i].amount = tbl[i].default_amount;
|
||||
refresh_field('amount', tbl[i].name, 'deductions');
|
||||
}
|
||||
total_ded += flt(tbl[i].amount);
|
||||
}
|
||||
doc.total_deduction = total_ded;
|
||||
refresh_field('total_deduction');
|
||||
|
@ -13,409 +13,409 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
|
||||
class SalarySlip(TransactionBase):
|
||||
def autoname(self):
|
||||
self.name = make_autoname('Sal Slip/' +self.employee + '/.#####')
|
||||
|
||||
def validate(self):
|
||||
self.status = self.get_status()
|
||||
self.validate_dates()
|
||||
self.check_existing()
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
|
||||
if not (len(self.get("earnings")) or len(self.get("deductions"))):
|
||||
# get details from salary structure
|
||||
self.get_emp_and_leave_details()
|
||||
else:
|
||||
self.get_leave_details(lwp = self.leave_without_pay)
|
||||
|
||||
# if self.salary_slip_based_on_timesheet or not self.net_pay:
|
||||
self.calculate_net_pay()
|
||||
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
self.total_in_words = money_in_words(self.rounded_total, company_currency)
|
||||
|
||||
if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"):
|
||||
max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet")
|
||||
if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
|
||||
frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
|
||||
format(max_working_hours), alert=True)
|
||||
|
||||
def validate_dates(self):
|
||||
if date_diff(self.end_date, self.start_date) < 0:
|
||||
frappe.throw(_("To date cannot be before From date"))
|
||||
|
||||
def calculate_component_amounts(self):
|
||||
if not getattr(self, '_salary_structure_doc', None):
|
||||
self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
|
||||
|
||||
data = self.get_data_for_eval()
|
||||
|
||||
for key in ('earnings', 'deductions'):
|
||||
for struct_row in self._salary_structure_doc.get(key):
|
||||
amount = self.eval_condition_and_formula(struct_row, data)
|
||||
if amount:
|
||||
self.update_component_row(struct_row, amount, key)
|
||||
|
||||
def update_component_row(self, struct_row, amount, key):
|
||||
component_row = None
|
||||
for d in self.get(key):
|
||||
if d.salary_component == struct_row.salary_component:
|
||||
component_row = d
|
||||
|
||||
if not component_row:
|
||||
self.append(key, {
|
||||
'amount': amount,
|
||||
'default_amount': amount,
|
||||
'depends_on_lwp' : struct_row.depends_on_lwp,
|
||||
'salary_component' : struct_row.salary_component
|
||||
})
|
||||
else:
|
||||
component_row.amount = amount
|
||||
|
||||
def eval_condition_and_formula(self, d, data):
|
||||
try:
|
||||
if d.condition:
|
||||
if not frappe.safe_eval(d.condition, None, data):
|
||||
return None
|
||||
amount = d.amount
|
||||
if d.amount_based_on_formula:
|
||||
if d.formula:
|
||||
amount = frappe.safe_eval(d.formula, None, data)
|
||||
if amount:
|
||||
data[d.abbr] = amount
|
||||
|
||||
return amount
|
||||
|
||||
except NameError as err:
|
||||
frappe.throw(_("Name error: {0}".format(err)))
|
||||
except SyntaxError as err:
|
||||
frappe.throw(_("Syntax error in formula or condition: {0}".format(err)))
|
||||
except Exception, e:
|
||||
frappe.throw(_("Error in formula or condition: {0}".format(e)))
|
||||
raise
|
||||
|
||||
def get_data_for_eval(self):
|
||||
'''Returns data for evaluating formula'''
|
||||
data = frappe._dict()
|
||||
|
||||
data.update(frappe.get_doc("Salary Structure Employee", {"employee": self.employee}).as_dict())
|
||||
|
||||
data.update(frappe.get_doc("Employee", self.employee).as_dict())
|
||||
data.update(self.as_dict())
|
||||
|
||||
# set values for components
|
||||
salary_components = frappe.get_all("Salary Component", fields=["salary_component_abbr"])
|
||||
for sc in salary_components:
|
||||
data.setdefault(sc.salary_component_abbr, 0)
|
||||
|
||||
for key in ('earnings', 'deductions'):
|
||||
for d in self.get(key):
|
||||
data[d.abbr] = d.amount
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_emp_and_leave_details(self):
|
||||
'''First time, load all the components from salary structure'''
|
||||
if self.employee:
|
||||
self.set("earnings", [])
|
||||
self.set("deductions", [])
|
||||
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
self.validate_dates()
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
self.get_leave_details(joining_date, relieving_date)
|
||||
struct = self.check_sal_struct(joining_date, relieving_date)
|
||||
|
||||
if struct:
|
||||
self._salary_structure_doc = frappe.get_doc('Salary Structure', struct)
|
||||
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
||||
self.set_time_sheet()
|
||||
self.pull_sal_struct()
|
||||
|
||||
def set_time_sheet(self):
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.set("timesheets", [])
|
||||
timesheets = frappe.db.sql(""" select * from `tabTimesheet` where employee = %(employee)s and start_date BETWEEN %(start_date)s AND %(end_date)s and (status = 'Submitted' or
|
||||
status = 'Billed')""", {'employee': self.employee, 'start_date': self.start_date, 'end_date': self.end_date}, as_dict=1)
|
||||
|
||||
for data in timesheets:
|
||||
self.append('timesheets', {
|
||||
'time_sheet': data.name,
|
||||
'working_hours': data.total_hours
|
||||
})
|
||||
|
||||
def get_date_details(self):
|
||||
date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date)
|
||||
self.start_date = date_details.start_date
|
||||
self.end_date = date_details.end_date
|
||||
|
||||
def check_sal_struct(self, joining_date, relieving_date):
|
||||
cond = ''
|
||||
if self.payroll_frequency:
|
||||
cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
|
||||
where employee=%s and (from_date <= %s or from_date <= %s)
|
||||
and (to_date is null or to_date >= %s or to_date >= %s)
|
||||
and parent in (select name from `tabSalary Structure`
|
||||
where is_active = 'Yes'%s)
|
||||
"""% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date))
|
||||
|
||||
if st_name:
|
||||
if len(st_name) > 1:
|
||||
frappe.msgprint(_("Multiple active Salary Structures found for employee {0} for the given dates")
|
||||
.format(self.employee), title=_('Warning'))
|
||||
return st_name and st_name[0][0] or ''
|
||||
else:
|
||||
self.salary_structure = None
|
||||
frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates")
|
||||
.format(self.employee), title=_('Salary Structure Missing'))
|
||||
|
||||
def pull_sal_struct(self):
|
||||
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.salary_structure = self._salary_structure_doc.name
|
||||
self.hour_rate = self._salary_structure_doc.hour_rate
|
||||
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
|
||||
wages_amount = self.hour_rate * self.total_working_hours
|
||||
|
||||
self.add_earning_for_hourly_wages(self, self._salary_structure_doc.salary_component, wages_amount)
|
||||
|
||||
make_salary_slip(self._salary_structure_doc.name, self)
|
||||
|
||||
def process_salary_structure(self):
|
||||
'''Calculate salary after salary structure details have been updated'''
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
self.pull_emp_details()
|
||||
self.get_leave_details()
|
||||
self.calculate_net_pay()
|
||||
|
||||
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
|
||||
row_exists = False
|
||||
for row in doc.earnings:
|
||||
if row.salary_component == salary_component:
|
||||
row.amount = amount
|
||||
row_exists = True
|
||||
break
|
||||
|
||||
if not row_exists:
|
||||
wages_row = {
|
||||
"salary_component": salary_component,
|
||||
"abbr": frappe.db.get_value("Salary Component", salary_component, "salary_component_abbr"),
|
||||
"amount": self.hour_rate * self.total_working_hours
|
||||
}
|
||||
doc.append('earnings', wages_row)
|
||||
|
||||
def pull_emp_details(self):
|
||||
emp = frappe.db.get_value("Employee", self.employee, ["bank_name", "bank_ac_no"], as_dict=1)
|
||||
if emp:
|
||||
self.bank_name = emp.bank_name
|
||||
self.bank_account_no = emp.bank_ac_no
|
||||
|
||||
|
||||
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
|
||||
if not joining_date:
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
holidays = self.get_holidays_for_employee(self.start_date, self.end_date)
|
||||
working_days = date_diff(self.end_date, self.start_date) + 1
|
||||
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
|
||||
working_days -= len(holidays)
|
||||
if working_days < 0:
|
||||
frappe.throw(_("There are more holidays than working days this month."))
|
||||
|
||||
actual_lwp = self.calculate_lwp(holidays, working_days)
|
||||
if not lwp:
|
||||
lwp = actual_lwp
|
||||
elif lwp != actual_lwp:
|
||||
frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records"))
|
||||
|
||||
self.total_working_days = working_days
|
||||
self.leave_without_pay = lwp
|
||||
|
||||
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp)
|
||||
self.payment_days = payment_days > 0 and payment_days or 0
|
||||
|
||||
def get_payment_days(self, joining_date, relieving_date):
|
||||
start_date = getdate(self.start_date)
|
||||
if joining_date:
|
||||
if getdate(self.start_date) <= joining_date <= getdate(self.end_date):
|
||||
start_date = joining_date
|
||||
elif joining_date > getdate(self.end_date):
|
||||
return
|
||||
|
||||
end_date = getdate(self.end_date)
|
||||
if relieving_date:
|
||||
if getdate(self.start_date) <= relieving_date <= getdate(self.end_date):
|
||||
end_date = relieving_date
|
||||
elif relieving_date < getdate(self.start_date):
|
||||
frappe.throw(_("Employee relieved on {0} must be set as 'Left'")
|
||||
.format(relieving_date))
|
||||
|
||||
payment_days = date_diff(end_date, start_date) + 1
|
||||
|
||||
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
|
||||
holidays = self.get_holidays_for_employee(start_date, end_date)
|
||||
payment_days -= len(holidays)
|
||||
return payment_days
|
||||
|
||||
def get_holidays_for_employee(self, start_date, end_date):
|
||||
holiday_list = get_holiday_list_for_employee(self.employee)
|
||||
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday`
|
||||
where
|
||||
parent=%(holiday_list)s
|
||||
and holiday_date >= %(start_date)s
|
||||
and holiday_date <= %(end_date)s''', {
|
||||
"holiday_list": holiday_list,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date
|
||||
})
|
||||
|
||||
holidays = [cstr(i) for i in holidays]
|
||||
|
||||
return holidays
|
||||
|
||||
def calculate_lwp(self, holidays, working_days):
|
||||
lwp = 0
|
||||
holidays = "','".join(holidays)
|
||||
for d in range(working_days):
|
||||
dt = add_days(cstr(getdate(self.start_date)), d)
|
||||
leave = frappe.db.sql("""
|
||||
select t1.name, t1.half_day
|
||||
from `tabLeave Application` t1, `tabLeave Type` t2
|
||||
where t2.name = t1.leave_type
|
||||
and t2.is_lwp = 1
|
||||
and t1.docstatus = 1
|
||||
and t1.status = 'Approved'
|
||||
and t1.employee = %(employee)s
|
||||
and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
|
||||
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
|
||||
END
|
||||
""".format(holidays), {"employee": self.employee, "dt": dt})
|
||||
if leave:
|
||||
lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
|
||||
return lwp
|
||||
|
||||
def check_existing(self):
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where start_date = %s and end_date = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
(self.start_date, self.end_date, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee))
|
||||
else:
|
||||
for data in self.timesheets:
|
||||
if frappe.db.get_value('Timesheet', data.time_sheet, 'status') == 'Payrolled':
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
|
||||
|
||||
def sum_components(self, component_type, total_field):
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
if not relieving_date:
|
||||
relieving_date = getdate(self.end_date)
|
||||
|
||||
for d in self.get(component_type):
|
||||
if ((cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet) or\
|
||||
getdate(self.start_date) < joining_date or getdate(self.end_date) > relieving_date):
|
||||
|
||||
d.amount = rounded((flt(d.default_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_working_days)), self.precision("amount", component_type))
|
||||
elif not self.payment_days and not self.salary_slip_based_on_timesheet:
|
||||
d.amount = 0
|
||||
elif not d.amount:
|
||||
d.amount = d.default_amount
|
||||
self.set(total_field, self.get(total_field) + flt(d.amount))
|
||||
|
||||
def calculate_net_pay(self):
|
||||
if self.salary_structure:
|
||||
self.calculate_component_amounts()
|
||||
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
|
||||
|
||||
self.total_deduction = 0
|
||||
self.gross_pay = 0
|
||||
|
||||
self.sum_components('earnings', 'gross_pay')
|
||||
self.sum_components('deductions', 'total_deduction')
|
||||
|
||||
self.set_loan_repayment()
|
||||
|
||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||
self.rounded_total = rounded(self.net_pay,
|
||||
self.precision("net_pay") if disable_rounded_total else 0)
|
||||
|
||||
def set_loan_repayment(self):
|
||||
employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount,
|
||||
sum(total_payment) as total_loan_repayment from `tabRepayment Schedule`
|
||||
where payment_date between %s and %s and parent in (select name from `tabEmployee Loan`
|
||||
where employee = %s and repay_from_salary = 1 and docstatus = 1)""",
|
||||
(self.start_date, self.end_date, self.employee), as_dict=True)
|
||||
if employee_loan:
|
||||
self.principal_amount = employee_loan[0].principal_amount
|
||||
self.interest_amount = employee_loan[0].interest_amount
|
||||
self.total_loan_repayment = employee_loan[0].total_loan_repayment
|
||||
|
||||
def on_submit(self):
|
||||
if self.net_pay < 0:
|
||||
frappe.throw(_("Net Pay cannot be less than 0"))
|
||||
else:
|
||||
self.set_status()
|
||||
self.update_status(self.name)
|
||||
if(frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee")):
|
||||
self.email_salary_slip()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_status()
|
||||
self.update_status()
|
||||
|
||||
def email_salary_slip(self):
|
||||
receiver = frappe.db.get_value("Employee", self.employee, "prefered_email")
|
||||
|
||||
if receiver:
|
||||
subj = 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date)
|
||||
frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"),
|
||||
attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name)
|
||||
else:
|
||||
msgprint(_("{0}: Employee email not found, hence email not sent").format(self.employee_name))
|
||||
|
||||
def update_status(self, salary_slip=None):
|
||||
for data in self.timesheets:
|
||||
if data.time_sheet:
|
||||
timesheet = frappe.get_doc('Timesheet', data.time_sheet)
|
||||
timesheet.salary_slip = salary_slip
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.save()
|
||||
|
||||
def set_status(self, status=None):
|
||||
'''Get and update status'''
|
||||
if not status:
|
||||
status = self.get_status()
|
||||
self.db_set("status", status)
|
||||
|
||||
def get_status(self):
|
||||
if self.docstatus == 0:
|
||||
status = "Draft"
|
||||
elif self.docstatus == 1:
|
||||
status = "Submitted"
|
||||
elif self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
return status
|
||||
def autoname(self):
|
||||
self.name = make_autoname('Sal Slip/' +self.employee + '/.#####')
|
||||
|
||||
def validate(self):
|
||||
self.status = self.get_status()
|
||||
self.validate_dates()
|
||||
self.check_existing()
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
|
||||
if not (len(self.get("earnings")) or len(self.get("deductions"))):
|
||||
# get details from salary structure
|
||||
self.get_emp_and_leave_details()
|
||||
else:
|
||||
self.get_leave_details(lwp = self.leave_without_pay)
|
||||
|
||||
# if self.salary_slip_based_on_timesheet or not self.net_pay:
|
||||
self.calculate_net_pay()
|
||||
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
self.total_in_words = money_in_words(self.rounded_total, company_currency)
|
||||
|
||||
if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"):
|
||||
max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet")
|
||||
if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
|
||||
frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
|
||||
format(max_working_hours), alert=True)
|
||||
|
||||
def validate_dates(self):
|
||||
if date_diff(self.end_date, self.start_date) < 0:
|
||||
frappe.throw(_("To date cannot be before From date"))
|
||||
|
||||
def calculate_component_amounts(self):
|
||||
if not getattr(self, '_salary_structure_doc', None):
|
||||
self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
|
||||
|
||||
data = self.get_data_for_eval()
|
||||
|
||||
for key in ('earnings', 'deductions'):
|
||||
for struct_row in self._salary_structure_doc.get(key):
|
||||
amount = self.eval_condition_and_formula(struct_row, data)
|
||||
if amount and struct_row.statistical_component == 0:
|
||||
self.update_component_row(struct_row, amount, key)
|
||||
|
||||
def update_component_row(self, struct_row, amount, key):
|
||||
component_row = None
|
||||
for d in self.get(key):
|
||||
if d.salary_component == struct_row.salary_component:
|
||||
component_row = d
|
||||
|
||||
if not component_row:
|
||||
self.append(key, {
|
||||
'amount': amount,
|
||||
'default_amount': amount,
|
||||
'depends_on_lwp' : struct_row.depends_on_lwp,
|
||||
'salary_component' : struct_row.salary_component
|
||||
})
|
||||
else:
|
||||
component_row.amount = amount
|
||||
|
||||
def eval_condition_and_formula(self, d, data):
|
||||
try:
|
||||
if d.condition:
|
||||
if not frappe.safe_eval(d.condition, None, data):
|
||||
return None
|
||||
amount = d.amount
|
||||
if d.amount_based_on_formula:
|
||||
if d.formula:
|
||||
amount = frappe.safe_eval(d.formula, None, data)
|
||||
if amount:
|
||||
data[d.abbr] = amount
|
||||
|
||||
return amount
|
||||
|
||||
except NameError as err:
|
||||
frappe.throw(_("Name error: {0}".format(err)))
|
||||
except SyntaxError as err:
|
||||
frappe.throw(_("Syntax error in formula or condition: {0}".format(err)))
|
||||
except Exception, e:
|
||||
frappe.throw(_("Error in formula or condition: {0}".format(e)))
|
||||
raise
|
||||
|
||||
def get_data_for_eval(self):
|
||||
'''Returns data for evaluating formula'''
|
||||
data = frappe._dict()
|
||||
|
||||
data.update(frappe.get_doc("Salary Structure Employee", {"employee": self.employee}).as_dict())
|
||||
|
||||
data.update(frappe.get_doc("Employee", self.employee).as_dict())
|
||||
data.update(self.as_dict())
|
||||
|
||||
# set values for components
|
||||
salary_components = frappe.get_all("Salary Component", fields=["salary_component_abbr"])
|
||||
for sc in salary_components:
|
||||
data.setdefault(sc.salary_component_abbr, 0)
|
||||
|
||||
for key in ('earnings', 'deductions'):
|
||||
for d in self.get(key):
|
||||
data[d.abbr] = d.amount
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_emp_and_leave_details(self):
|
||||
'''First time, load all the components from salary structure'''
|
||||
if self.employee:
|
||||
self.set("earnings", [])
|
||||
self.set("deductions", [])
|
||||
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
self.validate_dates()
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
self.get_leave_details(joining_date, relieving_date)
|
||||
struct = self.check_sal_struct(joining_date, relieving_date)
|
||||
|
||||
if struct:
|
||||
self._salary_structure_doc = frappe.get_doc('Salary Structure', struct)
|
||||
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
||||
self.set_time_sheet()
|
||||
self.pull_sal_struct()
|
||||
|
||||
def set_time_sheet(self):
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.set("timesheets", [])
|
||||
timesheets = frappe.db.sql(""" select * from `tabTimesheet` where employee = %(employee)s and start_date BETWEEN %(start_date)s AND %(end_date)s and (status = 'Submitted' or
|
||||
status = 'Billed')""", {'employee': self.employee, 'start_date': self.start_date, 'end_date': self.end_date}, as_dict=1)
|
||||
|
||||
for data in timesheets:
|
||||
self.append('timesheets', {
|
||||
'time_sheet': data.name,
|
||||
'working_hours': data.total_hours
|
||||
})
|
||||
|
||||
def get_date_details(self):
|
||||
date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date)
|
||||
self.start_date = date_details.start_date
|
||||
self.end_date = date_details.end_date
|
||||
|
||||
def check_sal_struct(self, joining_date, relieving_date):
|
||||
cond = ''
|
||||
if self.payroll_frequency:
|
||||
cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
|
||||
where employee=%s and (from_date <= %s or from_date <= %s)
|
||||
and (to_date is null or to_date >= %s or to_date >= %s)
|
||||
and parent in (select name from `tabSalary Structure`
|
||||
where is_active = 'Yes'%s)
|
||||
"""% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date))
|
||||
|
||||
if st_name:
|
||||
if len(st_name) > 1:
|
||||
frappe.msgprint(_("Multiple active Salary Structures found for employee {0} for the given dates")
|
||||
.format(self.employee), title=_('Warning'))
|
||||
return st_name and st_name[0][0] or ''
|
||||
else:
|
||||
self.salary_structure = None
|
||||
frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates")
|
||||
.format(self.employee), title=_('Salary Structure Missing'))
|
||||
|
||||
def pull_sal_struct(self):
|
||||
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.salary_structure = self._salary_structure_doc.name
|
||||
self.hour_rate = self._salary_structure_doc.hour_rate
|
||||
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
|
||||
wages_amount = self.hour_rate * self.total_working_hours
|
||||
|
||||
self.add_earning_for_hourly_wages(self, self._salary_structure_doc.salary_component, wages_amount)
|
||||
|
||||
make_salary_slip(self._salary_structure_doc.name, self)
|
||||
|
||||
def process_salary_structure(self):
|
||||
'''Calculate salary after salary structure details have been updated'''
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
self.get_date_details()
|
||||
self.pull_emp_details()
|
||||
self.get_leave_details()
|
||||
self.calculate_net_pay()
|
||||
|
||||
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
|
||||
row_exists = False
|
||||
for row in doc.earnings:
|
||||
if row.salary_component == salary_component:
|
||||
row.amount = amount
|
||||
row_exists = True
|
||||
break
|
||||
|
||||
if not row_exists:
|
||||
wages_row = {
|
||||
"salary_component": salary_component,
|
||||
"abbr": frappe.db.get_value("Salary Component", salary_component, "salary_component_abbr"),
|
||||
"amount": self.hour_rate * self.total_working_hours
|
||||
}
|
||||
doc.append('earnings', wages_row)
|
||||
|
||||
def pull_emp_details(self):
|
||||
emp = frappe.db.get_value("Employee", self.employee, ["bank_name", "bank_ac_no"], as_dict=1)
|
||||
if emp:
|
||||
self.bank_name = emp.bank_name
|
||||
self.bank_account_no = emp.bank_ac_no
|
||||
|
||||
|
||||
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
|
||||
if not joining_date:
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
holidays = self.get_holidays_for_employee(self.start_date, self.end_date)
|
||||
working_days = date_diff(self.end_date, self.start_date) + 1
|
||||
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
|
||||
working_days -= len(holidays)
|
||||
if working_days < 0:
|
||||
frappe.throw(_("There are more holidays than working days this month."))
|
||||
|
||||
actual_lwp = self.calculate_lwp(holidays, working_days)
|
||||
if not lwp:
|
||||
lwp = actual_lwp
|
||||
elif lwp != actual_lwp:
|
||||
frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records"))
|
||||
|
||||
self.total_working_days = working_days
|
||||
self.leave_without_pay = lwp
|
||||
|
||||
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp)
|
||||
self.payment_days = payment_days > 0 and payment_days or 0
|
||||
|
||||
def get_payment_days(self, joining_date, relieving_date):
|
||||
start_date = getdate(self.start_date)
|
||||
if joining_date:
|
||||
if getdate(self.start_date) <= joining_date <= getdate(self.end_date):
|
||||
start_date = joining_date
|
||||
elif joining_date > getdate(self.end_date):
|
||||
return
|
||||
|
||||
end_date = getdate(self.end_date)
|
||||
if relieving_date:
|
||||
if getdate(self.start_date) <= relieving_date <= getdate(self.end_date):
|
||||
end_date = relieving_date
|
||||
elif relieving_date < getdate(self.start_date):
|
||||
frappe.throw(_("Employee relieved on {0} must be set as 'Left'")
|
||||
.format(relieving_date))
|
||||
|
||||
payment_days = date_diff(end_date, start_date) + 1
|
||||
|
||||
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")):
|
||||
holidays = self.get_holidays_for_employee(start_date, end_date)
|
||||
payment_days -= len(holidays)
|
||||
return payment_days
|
||||
|
||||
def get_holidays_for_employee(self, start_date, end_date):
|
||||
holiday_list = get_holiday_list_for_employee(self.employee)
|
||||
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday`
|
||||
where
|
||||
parent=%(holiday_list)s
|
||||
and holiday_date >= %(start_date)s
|
||||
and holiday_date <= %(end_date)s''', {
|
||||
"holiday_list": holiday_list,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date
|
||||
})
|
||||
|
||||
holidays = [cstr(i) for i in holidays]
|
||||
|
||||
return holidays
|
||||
|
||||
def calculate_lwp(self, holidays, working_days):
|
||||
lwp = 0
|
||||
holidays = "','".join(holidays)
|
||||
for d in range(working_days):
|
||||
dt = add_days(cstr(getdate(self.start_date)), d)
|
||||
leave = frappe.db.sql("""
|
||||
select t1.name, t1.half_day
|
||||
from `tabLeave Application` t1, `tabLeave Type` t2
|
||||
where t2.name = t1.leave_type
|
||||
and t2.is_lwp = 1
|
||||
and t1.docstatus = 1
|
||||
and t1.status = 'Approved'
|
||||
and t1.employee = %(employee)s
|
||||
and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
|
||||
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
|
||||
END
|
||||
""".format(holidays), {"employee": self.employee, "dt": dt})
|
||||
if leave:
|
||||
lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
|
||||
return lwp
|
||||
|
||||
def check_existing(self):
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where start_date = %s and end_date = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
(self.start_date, self.end_date, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee))
|
||||
else:
|
||||
for data in self.timesheets:
|
||||
if frappe.db.get_value('Timesheet', data.time_sheet, 'status') == 'Payrolled':
|
||||
frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
|
||||
|
||||
def sum_components(self, component_type, total_field):
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
if not relieving_date:
|
||||
relieving_date = getdate(self.end_date)
|
||||
|
||||
for d in self.get(component_type):
|
||||
if ((cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet) or\
|
||||
getdate(self.start_date) < joining_date or getdate(self.end_date) > relieving_date):
|
||||
|
||||
d.amount = rounded((flt(d.default_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_working_days)), self.precision("amount", component_type))
|
||||
elif not self.payment_days and not self.salary_slip_based_on_timesheet:
|
||||
d.amount = 0
|
||||
elif not d.amount:
|
||||
d.amount = d.default_amount
|
||||
self.set(total_field, self.get(total_field) + flt(d.amount))
|
||||
|
||||
def calculate_net_pay(self):
|
||||
if self.salary_structure:
|
||||
self.calculate_component_amounts()
|
||||
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
|
||||
|
||||
self.total_deduction = 0
|
||||
self.gross_pay = 0
|
||||
|
||||
self.sum_components('earnings', 'gross_pay')
|
||||
self.sum_components('deductions', 'total_deduction')
|
||||
|
||||
self.set_loan_repayment()
|
||||
|
||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||
self.rounded_total = rounded(self.net_pay,
|
||||
self.precision("net_pay") if disable_rounded_total else 0)
|
||||
|
||||
def set_loan_repayment(self):
|
||||
employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount,
|
||||
sum(total_payment) as total_loan_repayment from `tabRepayment Schedule`
|
||||
where payment_date between %s and %s and parent in (select name from `tabEmployee Loan`
|
||||
where employee = %s and repay_from_salary = 1 and docstatus = 1)""",
|
||||
(self.start_date, self.end_date, self.employee), as_dict=True)
|
||||
if employee_loan:
|
||||
self.principal_amount = employee_loan[0].principal_amount
|
||||
self.interest_amount = employee_loan[0].interest_amount
|
||||
self.total_loan_repayment = employee_loan[0].total_loan_repayment
|
||||
|
||||
def on_submit(self):
|
||||
if self.net_pay < 0:
|
||||
frappe.throw(_("Net Pay cannot be less than 0"))
|
||||
else:
|
||||
self.set_status()
|
||||
self.update_status(self.name)
|
||||
if(frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee")):
|
||||
self.email_salary_slip()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_status()
|
||||
self.update_status()
|
||||
|
||||
def email_salary_slip(self):
|
||||
receiver = frappe.db.get_value("Employee", self.employee, "prefered_email")
|
||||
|
||||
if receiver:
|
||||
subj = 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date)
|
||||
frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"),
|
||||
attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name)
|
||||
else:
|
||||
msgprint(_("{0}: Employee email not found, hence email not sent").format(self.employee_name))
|
||||
|
||||
def update_status(self, salary_slip=None):
|
||||
for data in self.timesheets:
|
||||
if data.time_sheet:
|
||||
timesheet = frappe.get_doc('Timesheet', data.time_sheet)
|
||||
timesheet.salary_slip = salary_slip
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.save()
|
||||
|
||||
def set_status(self, status=None):
|
||||
'''Get and update status'''
|
||||
if not status:
|
||||
status = self.get_status()
|
||||
self.db_set("status", status)
|
||||
|
||||
def get_status(self):
|
||||
if self.docstatus == 0:
|
||||
status = "Draft"
|
||||
elif self.docstatus == 1:
|
||||
status = "Submitted"
|
||||
elif self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
return status
|
||||
|
||||
def unlink_ref_doc_from_salary_slip(ref_no):
|
||||
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
|
||||
where journal_entry=%s and docstatus < 2""", (ref_no))
|
||||
if linked_ss:
|
||||
for ss in linked_ss:
|
||||
ss_doc = frappe.get_doc("Salary Slip", ss)
|
||||
frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "")
|
||||
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
|
||||
where journal_entry=%s and docstatus < 2""", (ref_no))
|
||||
if linked_ss:
|
||||
for ss in linked_ss:
|
||||
ss_doc = frappe.get_doc("Salary Slip", ss)
|
||||
frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "")
|
||||
|
Loading…
Reference in New Issue
Block a user