mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 19:59:12 +00:00
feat : Leave type with partial payment (#23173)
* feat: Partially paid Leaves * feat: some importatnt validation * fix: requested changes * fix: requested changes * fix: travis, sider, codacy * fix: changes requested * test: Partially Paid Leaves
This commit is contained in:
@@ -672,10 +672,10 @@
|
|||||||
"oldfieldtype": "Date"
|
"oldfieldtype": "Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.status == \"Left\"",
|
|
||||||
"fieldname": "relieving_date",
|
"fieldname": "relieving_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Relieving Date",
|
"label": "Relieving Date",
|
||||||
|
"mandatory_depends_on": "eval:doc.status == \"Left\"",
|
||||||
"oldfieldname": "relieving_date",
|
"oldfieldname": "relieving_date",
|
||||||
"oldfieldtype": "Date"
|
"oldfieldtype": "Date"
|
||||||
},
|
},
|
||||||
@@ -822,7 +822,7 @@
|
|||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-06 15:58:23.805489",
|
"modified": "2020-10-16 14:41:10.580897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ class LeaveApplication(Document):
|
|||||||
if self.status == "Approved":
|
if self.status == "Approved":
|
||||||
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
|
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
|
||||||
date = dt.strftime("%Y-%m-%d")
|
date = dt.strftime("%Y-%m-%d")
|
||||||
status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
|
status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
|
||||||
|
|
||||||
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
|
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
|
||||||
attendance_date = date, docstatus = ('!=', 2)))
|
attendance_date = date, docstatus = ('!=', 2)))
|
||||||
|
|
||||||
@@ -293,7 +292,8 @@ class LeaveApplication(Document):
|
|||||||
def set_half_day_date(self):
|
def set_half_day_date(self):
|
||||||
if self.from_date == self.to_date and self.half_day == 1:
|
if self.from_date == self.to_date and self.half_day == 1:
|
||||||
self.half_day_date = self.from_date
|
self.half_day_date = self.from_date
|
||||||
elif self.half_day == 0:
|
|
||||||
|
if self.half_day == 0:
|
||||||
self.half_day_date = None
|
self.half_day_date = None
|
||||||
|
|
||||||
def notify_employee(self):
|
def notify_employee(self):
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"column_break_3",
|
"column_break_3",
|
||||||
"is_carry_forward",
|
"is_carry_forward",
|
||||||
"is_lwp",
|
"is_lwp",
|
||||||
|
"is_ppl",
|
||||||
|
"fraction_of_daily_salary_per_leave",
|
||||||
"is_optional_leave",
|
"is_optional_leave",
|
||||||
"allow_negative",
|
"allow_negative",
|
||||||
"include_holiday",
|
"include_holiday",
|
||||||
@@ -77,6 +79,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.is_ppl == 0",
|
||||||
"fieldname": "is_lwp",
|
"fieldname": "is_lwp",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Leave Without Pay"
|
"label": "Is Leave Without Pay"
|
||||||
@@ -183,12 +186,26 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_22",
|
"fieldname": "column_break_22",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.is_lwp == 0",
|
||||||
|
"fieldname": "is_ppl",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Partially Paid Leave"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.is_ppl == 1",
|
||||||
|
"fieldname": "fraction_of_daily_salary_per_leave",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Fraction of Daily Salary per Leave",
|
||||||
|
"mandatory_depends_on": "eval:doc.is_ppl == 1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-flag",
|
"icon": "fa fa-flag",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-12 12:48:37.780254",
|
"modified": "2020-08-26 14:04:54.318687",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Leave Type",
|
"name": "Leave Type",
|
||||||
|
|||||||
@@ -21,3 +21,9 @@ class LeaveType(Document):
|
|||||||
leave_allocation = [l['name'] for l in leave_allocation]
|
leave_allocation = [l['name'] for l in leave_allocation]
|
||||||
if leave_allocation:
|
if leave_allocation:
|
||||||
frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
|
frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
|
||||||
|
|
||||||
|
if self.is_lwp and self.is_ppl:
|
||||||
|
frappe.throw(_("Leave Type can be either without pay or partial pay"))
|
||||||
|
|
||||||
|
if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1):
|
||||||
|
frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))
|
||||||
|
|||||||
@@ -18,9 +18,14 @@ def create_leave_type(**args):
|
|||||||
"allow_encashment": args.allow_encashment or 0,
|
"allow_encashment": args.allow_encashment or 0,
|
||||||
"is_earned_leave": args.is_earned_leave or 0,
|
"is_earned_leave": args.is_earned_leave or 0,
|
||||||
"is_lwp": args.is_lwp or 0,
|
"is_lwp": args.is_lwp or 0,
|
||||||
|
"is_ppl":args.is_ppl or 0,
|
||||||
"is_carry_forward": args.is_carry_forward or 0,
|
"is_carry_forward": args.is_carry_forward or 0,
|
||||||
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
|
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
|
||||||
"encashment_threshold_days": args.encashment_threshold_days or 5,
|
"encashment_threshold_days": args.encashment_threshold_days or 5,
|
||||||
"earning_component": "Leave Encashment"
|
"earning_component": "Leave Encashment"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if leave_type.is_ppl:
|
||||||
|
leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
|
||||||
|
|
||||||
return leave_type
|
return leave_type
|
||||||
@@ -13,12 +13,12 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
|
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
employee: frm.doc.employee
|
employee: frm.doc.employee
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
frm.set_query("salary_component", "earnings", function() {
|
frm.set_query("salary_component", "earnings", function() {
|
||||||
@@ -26,7 +26,7 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
filters: {
|
filters: {
|
||||||
type: "earning"
|
type: "earning"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("salary_component", "deductions", function() {
|
frm.set_query("salary_component", "deductions", function() {
|
||||||
@@ -34,18 +34,18 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
filters: {
|
filters: {
|
||||||
type: "deduction"
|
type: "deduction"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("employee", function() {
|
frm.set_query("employee", function() {
|
||||||
return{
|
return {
|
||||||
query: "erpnext.controllers.queries.employee_query"
|
query: "erpnext.controllers.queries.employee_query"
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
start_date: function(frm){
|
start_date: function(frm) {
|
||||||
if(frm.doc.start_date){
|
if (frm.doc.start_date) {
|
||||||
frm.trigger("set_end_date");
|
frm.trigger("set_end_date");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -54,7 +54,7 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
frm.events.get_emp_and_working_day_details(frm);
|
frm.events.get_emp_and_working_day_details(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
set_end_date: function(frm){
|
set_end_date: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
|
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
|
||||||
args: {
|
args: {
|
||||||
@@ -66,22 +66,22 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
frm.set_value('end_date', r.message.end_date);
|
frm.set_value('end_date', r.message.end_date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
var company = locals[':Company'][frm.doc.company];
|
var company = locals[':Company'][frm.doc.company];
|
||||||
if(!frm.doc.letter_head && company.default_letter_head) {
|
if (!frm.doc.letter_head && company.default_letter_head) {
|
||||||
frm.set_value('letter_head', company.default_letter_head);
|
frm.set_value('letter_head', company.default_letter_head);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.trigger("toggle_fields")
|
frm.trigger("toggle_fields");
|
||||||
|
|
||||||
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
||||||
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
|
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);
|
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
salary_slip_based_on_timesheet: function(frm) {
|
salary_slip_based_on_timesheet: function(frm) {
|
||||||
@@ -98,12 +98,12 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
frm.events.get_emp_and_working_day_details(frm);
|
frm.events.get_emp_and_working_day_details(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
leave_without_pay: function(frm){
|
leave_without_pay: function(frm) {
|
||||||
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
|
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: 'process_salary_based_on_working_days',
|
method: 'process_salary_based_on_working_days',
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function(r, rt) {
|
callback: function() {
|
||||||
frm.refresh();
|
frm.refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -121,10 +121,10 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: 'get_emp_and_working_day_details',
|
method: 'get_emp_and_working_day_details',
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function(r, rt) {
|
callback: function(r) {
|
||||||
frm.refresh();
|
frm.refresh();
|
||||||
if (r.message){
|
if (r.message[1] !== "Leave" && r.message[0]) {
|
||||||
frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as ")+ r.message[0] +__(". You can can change this in ") + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -141,7 +141,7 @@ frappe.ui.form.on('Salary Slip Timesheet', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// calculate total working hours, earnings based on hourly wages and totals
|
// calculate total working hours, earnings based on hourly wages and totals
|
||||||
var total_work_hours = function(frm, dt, dn) {
|
var total_work_hours = function(frm) {
|
||||||
var total_working_hours = 0.0;
|
var total_working_hours = 0.0;
|
||||||
$.each(frm.doc["timesheets"] || [], function(i, timesheet) {
|
$.each(frm.doc["timesheets"] || [], function(i, timesheet) {
|
||||||
total_working_hours += timesheet.working_hours;
|
total_working_hours += timesheet.working_hours;
|
||||||
@@ -165,4 +165,4 @@ var total_work_hours = function(frm, dt, dn) {
|
|||||||
frm.doc.rounded_total = Math.round(frm.doc.net_pay);
|
frm.doc.rounded_total = Math.round(frm.doc.net_pay);
|
||||||
refresh_many(['net_pay', 'rounded_total']);
|
refresh_many(['net_pay', 'rounded_total']);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -136,8 +136,8 @@ class SalarySlip(TransactionBase):
|
|||||||
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
||||||
self.set_time_sheet()
|
self.set_time_sheet()
|
||||||
self.pull_sal_struct()
|
self.pull_sal_struct()
|
||||||
consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
|
payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
|
||||||
return consider_unmarked_attendance_as
|
return [payroll_based_on, consider_unmarked_attendance_as]
|
||||||
|
|
||||||
def set_time_sheet(self):
|
def set_time_sheet(self):
|
||||||
if self.salary_slip_based_on_timesheet:
|
if self.salary_slip_based_on_timesheet:
|
||||||
@@ -210,10 +210,10 @@ class SalarySlip(TransactionBase):
|
|||||||
frappe.throw(_("Please set Payroll based on in Payroll settings"))
|
frappe.throw(_("Please set Payroll based on in Payroll settings"))
|
||||||
|
|
||||||
if payroll_based_on == "Attendance":
|
if payroll_based_on == "Attendance":
|
||||||
actual_lwp, absent = self.calculate_lwp_and_absent_days_based_on_attendance(holidays)
|
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
|
||||||
self.absent_days = absent
|
self.absent_days = absent
|
||||||
else:
|
else:
|
||||||
actual_lwp = self.calculate_lwp_based_on_leave_application(holidays, working_days)
|
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
|
||||||
|
|
||||||
if not lwp:
|
if not lwp:
|
||||||
lwp = actual_lwp
|
lwp = actual_lwp
|
||||||
@@ -300,7 +300,7 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
return holidays
|
return holidays
|
||||||
|
|
||||||
def calculate_lwp_based_on_leave_application(self, holidays, working_days):
|
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
|
||||||
lwp = 0
|
lwp = 0
|
||||||
holidays = "','".join(holidays)
|
holidays = "','".join(holidays)
|
||||||
daily_wages_fraction_for_half_day = \
|
daily_wages_fraction_for_half_day = \
|
||||||
@@ -311,10 +311,12 @@ class SalarySlip(TransactionBase):
|
|||||||
leave = frappe.db.sql("""
|
leave = frappe.db.sql("""
|
||||||
SELECT t1.name,
|
SELECT t1.name,
|
||||||
CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
|
CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
|
||||||
THEN t1.half_day else 0 END
|
THEN t1.half_day else 0 END,
|
||||||
|
t2.is_ppl,
|
||||||
|
t2.fraction_of_daily_salary_per_leave
|
||||||
FROM `tabLeave Application` t1, `tabLeave Type` t2
|
FROM `tabLeave Application` t1, `tabLeave Type` t2
|
||||||
WHERE t2.name = t1.leave_type
|
WHERE t2.name = t1.leave_type
|
||||||
AND t2.is_lwp = 1
|
AND (t2.is_lwp = 1 or t2.is_ppl = 1)
|
||||||
AND t1.docstatus = 1
|
AND t1.docstatus = 1
|
||||||
AND t1.employee = %(employee)s
|
AND t1.employee = %(employee)s
|
||||||
AND ifnull(t1.salary_slip, '') = ''
|
AND ifnull(t1.salary_slip, '') = ''
|
||||||
@@ -327,19 +329,35 @@ class SalarySlip(TransactionBase):
|
|||||||
""".format(holidays), {"employee": self.employee, "dt": dt})
|
""".format(holidays), {"employee": self.employee, "dt": dt})
|
||||||
|
|
||||||
if leave:
|
if leave:
|
||||||
|
equivalent_lwp_count = 0
|
||||||
is_half_day_leave = cint(leave[0][1])
|
is_half_day_leave = cint(leave[0][1])
|
||||||
lwp += (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
|
is_partially_paid_leave = cint(leave[0][2])
|
||||||
|
fraction_of_daily_salary_per_leave = flt(leave[0][3])
|
||||||
|
|
||||||
|
equivalent_lwp_count = (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
|
||||||
|
|
||||||
|
if is_partially_paid_leave:
|
||||||
|
equivalent_lwp_count *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||||
|
|
||||||
|
lwp += equivalent_lwp_count
|
||||||
|
|
||||||
return lwp
|
return lwp
|
||||||
|
|
||||||
def calculate_lwp_and_absent_days_based_on_attendance(self, holidays):
|
def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays):
|
||||||
lwp = 0
|
lwp = 0
|
||||||
absent = 0
|
absent = 0
|
||||||
|
|
||||||
daily_wages_fraction_for_half_day = \
|
daily_wages_fraction_for_half_day = \
|
||||||
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
|
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
|
||||||
|
|
||||||
lwp_leave_types = dict(frappe.get_all("Leave Type", {"is_lwp": 1}, ["name", "include_holiday"], as_list=1))
|
leave_types = frappe.get_all("Leave Type",
|
||||||
|
or_filters=[["is_ppl", "=", 1], ["is_lwp", "=", 1]],
|
||||||
|
fields =["name", "is_lwp", "is_ppl", "fraction_of_daily_salary_per_leave", "include_holiday"])
|
||||||
|
|
||||||
|
leave_type_map = {}
|
||||||
|
for leave_type in leave_types:
|
||||||
|
leave_type_map[leave_type.name] = leave_type
|
||||||
|
|
||||||
attendances = frappe.db.sql('''
|
attendances = frappe.db.sql('''
|
||||||
SELECT attendance_date, status, leave_type
|
SELECT attendance_date, status, leave_type
|
||||||
FROM `tabAttendance`
|
FROM `tabAttendance`
|
||||||
@@ -351,21 +369,30 @@ class SalarySlip(TransactionBase):
|
|||||||
''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
|
''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
|
||||||
|
|
||||||
for d in attendances:
|
for d in attendances:
|
||||||
if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in lwp_leave_types:
|
if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in leave_type_map.keys():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
|
if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
|
||||||
if d.status == "Absent" or \
|
if d.status == "Absent" or \
|
||||||
(d.leave_type and d.leave_type in lwp_leave_types and not lwp_leave_types[d.leave_type]):
|
(d.leave_type and d.leave_type in leave_type_map.keys() and not leave_type_map[d.leave_type]['include_holiday']):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if d.leave_type:
|
||||||
|
fraction_of_daily_salary_per_leave = leave_type_map[d.leave_type]["fraction_of_daily_salary_per_leave"]
|
||||||
|
|
||||||
if d.status == "Half Day":
|
if d.status == "Half Day":
|
||||||
lwp += (1 - daily_wages_fraction_for_half_day)
|
equivalent_lwp = (1 - daily_wages_fraction_for_half_day)
|
||||||
elif d.status == "On Leave" and d.leave_type in lwp_leave_types:
|
|
||||||
lwp += 1
|
if d.leave_type in leave_type_map.keys() and leave_type_map[d.leave_type]["is_ppl"]:
|
||||||
|
equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||||
|
lwp += equivalent_lwp
|
||||||
|
elif d.status == "On Leave" and d.leave_type and d.leave_type in leave_type_map.keys():
|
||||||
|
equivalent_lwp = 1
|
||||||
|
if leave_type_map[d.leave_type]["is_ppl"]:
|
||||||
|
equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||||
|
lwp += equivalent_lwp
|
||||||
elif d.status == "Absent":
|
elif d.status == "Absent":
|
||||||
absent += 1
|
absent += 1
|
||||||
|
|
||||||
return lwp, absent
|
return lwp, absent
|
||||||
|
|
||||||
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
|
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
|
||||||
@@ -949,9 +976,8 @@ class SalarySlip(TransactionBase):
|
|||||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||||
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
||||||
if payment.total_payment > total_amount:
|
if payment.total_payment > total_amount:
|
||||||
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
|
frappe.throw(_("Row {0}: Paid amount {1} is greater than pending accrued amount {2}against loan {3}").format(
|
||||||
against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
|
payment.idx, frappe.bold(payment.total_payment),frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||||
frappe.bold(total_amount), frappe.bold(payment.loan)))
|
|
||||||
|
|
||||||
self.total_interest_amount += payment.interest_amount
|
self.total_interest_amount += payment.interest_amount
|
||||||
self.total_principal_amount += payment.principal_amount
|
self.total_principal_amount += payment.principal_amount
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_
|
|||||||
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
|
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||||
|
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||||
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
|
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
|
||||||
import create_payroll_period, create_exemption_category
|
import create_payroll_period, create_exemption_category
|
||||||
|
|
||||||
@@ -93,14 +95,27 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
|
|
||||||
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
|
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
|
||||||
|
|
||||||
|
leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl = 1)
|
||||||
|
leave_type_ppl.save()
|
||||||
|
|
||||||
|
alloc = create_leave_allocation(
|
||||||
|
employee = emp_id, from_date = add_days(first_sunday, 4),
|
||||||
|
to_date = add_days(first_sunday, 10), new_leaves_allocated = 3,
|
||||||
|
leave_type = "Test Partially Paid Leave")
|
||||||
|
alloc.save()
|
||||||
|
alloc.submit()
|
||||||
|
|
||||||
|
#two day leave ppl with fraction_of_daily_salary_per_leave = 0.5 equivalent to single day lwp
|
||||||
|
make_leave_application(emp_id, add_days(first_sunday, 4), add_days(first_sunday, 5), "Test Partially Paid Leave")
|
||||||
|
|
||||||
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
|
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
|
||||||
|
|
||||||
self.assertEqual(ss.leave_without_pay, 3)
|
self.assertEqual(ss.leave_without_pay, 4)
|
||||||
|
|
||||||
days_in_month = no_of_days[0]
|
days_in_month = no_of_days[0]
|
||||||
no_of_holidays = no_of_days[1]
|
no_of_holidays = no_of_days[1]
|
||||||
|
|
||||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 3)
|
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
|
||||||
|
|
||||||
#Gross pay calculation based on attendances
|
#Gross pay calculation based on attendances
|
||||||
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
|
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
|
||||||
|
|||||||
Reference in New Issue
Block a user