Skip to content

Commit

Permalink
Merge pull request #1427 from frappe/version-15-hotfix
Browse files Browse the repository at this point in the history
chore: release v15
  • Loading branch information
ruchamahabal committed Feb 13, 2024
2 parents f1a0b09 + f25a245 commit 6eed7a3
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 78 deletions.
16 changes: 14 additions & 2 deletions hrms/hr/doctype/employee_onboarding/employee_onboarding.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2022-01-29 12:33:57.120384",
"modified": "2024-02-12 19:33:57.120384",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Onboarding",
Expand All @@ -195,7 +195,19 @@
"share": 1,
"submit": 1,
"write": 1
}
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"export": 1,
"print": 1,
"read": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
Expand Down
43 changes: 19 additions & 24 deletions hrms/hr/doctype/expense_claim/expense_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,26 @@ def set_status(self, update=False):

precision = self.precision("grand_total")

if (
# set as paid
self.is_paid
or (
flt(self.total_sanctioned_amount) > 0
and (
# grand total is reimbursed
(
self.docstatus == 1
and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision)
if self.docstatus == 1:
if self.approval_status == "Approved":
if (
# set as paid
self.is_paid
or (
flt(self.total_sanctioned_amount) > 0
and (
# grand total is reimbursed
(flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision))
# grand total (to be paid) is 0 since linked advances already cover the claimed amount
or (flt(self.grand_total, precision) == 0)
)
)
# grand total (to be paid) is 0 since linked advances already cover the claimed amount
or (flt(self.grand_total, precision) == 0)
)
)
) and self.approval_status == "Approved":
status = "Paid"
elif (
flt(self.total_sanctioned_amount) > 0
and self.docstatus == 1
and self.approval_status == "Approved"
):
status = "Unpaid"
elif self.docstatus == 1 and self.approval_status == "Rejected":
status = "Rejected"
):
status = "Paid"
elif flt(self.total_sanctioned_amount) > 0:
status = "Unpaid"
elif self.approval_status == "Rejected":
status = "Rejected"

if update:
self.db_set("status", status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@
</tbody>
</table>
{% else %}
<p style="margin-top: 30px;"> No Leave has been allocated. </p>
<p style="margin-top: 30px;"> {{ __("No leaves have been allocated.") }} </p>
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,13 @@ def get_columns_for_days(filters: Filters) -> List[Dict]:
days = []

for day in range(1, total_days + 1):
day = cstr(day)
# forms the dates from selected year and month from filters
date = "{}-{}-{}".format(cstr(filters.year), cstr(filters.month), cstr(day))
date = "{}-{}-{}".format(cstr(filters.year), cstr(filters.month), day)
# gets abbr from weekday number
weekday = day_abbr[getdate(date).weekday()]
# sets days as 1 Mon, 2 Tue, 3 Wed
label = "{} {}".format(cstr(day), weekday)
label = "{} {}".format(day, weekday)
days.append({"label": label, "fieldtype": "Data", "fieldname": day, "width": 65})

return days
Expand Down Expand Up @@ -619,7 +620,7 @@ def get_chart_data(attendance_map: Dict, filters: Filters) -> Dict:

for employee, attendance_dict in attendance_map.items():
for shift, attendance in attendance_dict.items():
attendance_on_day = attendance.get(day["fieldname"])
attendance_on_day = attendance.get(cint(day["fieldname"]))

if attendance_on_day == "On Leave":
# leave should be counted only once for the entire day
Expand Down
108 changes: 64 additions & 44 deletions hrms/payroll/doctype/salary_slip/salary_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from frappe import _, msgprint
from frappe.model.naming import make_autoname
from frappe.query_builder import Order
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import Count, Sum
from frappe.utils import (
add_days,
ceil,
Expand Down Expand Up @@ -479,63 +479,83 @@ def get_working_days_details(self, lwp=None, for_preview=0):
payroll_settings.payroll_based_on == "Attendance"
and consider_unmarked_attendance_as == "Absent"
):
unmarked_days = self.get_unmarked_days(payroll_settings.include_holidays_in_total_working_days)
unmarked_days = self.get_unmarked_days(
payroll_settings.include_holidays_in_total_working_days, holidays
)
self.absent_days += unmarked_days # will be treated as absent
self.payment_days -= unmarked_days
else:
self.payment_days = 0

def get_unmarked_days(self, include_holidays_in_total_working_days):
unmarked_days = self.total_working_days
def get_unmarked_days(
self, include_holidays_in_total_working_days: bool, holidays: list | None = None
) -> float:
"""Calculates the number of unmarked days for an employee within a date range"""
unmarked_days = (
self.total_working_days
- self._get_days_outside_period(include_holidays_in_total_working_days, holidays)
- self._get_marked_attendance_days(holidays)
)

if include_holidays_in_total_working_days and holidays:
unmarked_days -= self._get_number_of_holidays(holidays)

return unmarked_days

def _get_days_outside_period(
self, include_holidays_in_total_working_days: bool, holidays: list | None = None
):
"""Returns days before DOJ or after relieving date"""

def _get_days(start_date, end_date):
no_of_days = date_diff(end_date, start_date) + 1

if include_holidays_in_total_working_days:
return no_of_days
else:
days = 0
end_date = getdate(end_date)
for day in range(no_of_days):
date = add_days(end_date, -day)
if date not in holidays:
days += 1
return days

days = 0
if self.actual_start_date != self.start_date:
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
unmarked_days,
include_holidays_in_total_working_days,
self.start_date,
add_days(self.joining_date, -1),
)
days += _get_days(self.start_date, add_days(self.joining_date, -1))

if self.actual_end_date != self.end_date:
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
unmarked_days,
include_holidays_in_total_working_days,
add_days(self.relieving_date, 1),
self.end_date,
)
days += _get_days(add_days(self.relieving_date, 1), self.end_date)

# exclude days for which attendance has been marked
marked_days = frappe.db.count(
"Attendance",
filters={
"attendance_date": ["between", [self.actual_start_date, self.actual_end_date]],
"employee": self.employee,
"docstatus": 1,
},
)
unmarked_days -= marked_days
return days

return unmarked_days
def _get_number_of_holidays(self, holidays: list | None = None) -> float:
no_of_holidays = 0
actual_end_date = getdate(self.actual_end_date)

def get_unmarked_days_based_on_doj_or_relieving(
self, unmarked_days, include_holidays_in_total_working_days, start_date, end_date
):
"""
Exclude days before DOJ or after
Relieving Date from unmarked days
"""
from erpnext.setup.doctype.employee.employee import is_holiday
for days in range(date_diff(self.actual_end_date, self.actual_start_date) + 1):
date = add_days(actual_end_date, -days)
if date in holidays:
no_of_holidays += 1

if include_holidays_in_total_working_days:
unmarked_days -= date_diff(end_date, start_date) + 1
else:
# exclude only if not holidays
for days in range(date_diff(end_date, start_date) + 1):
date = add_days(end_date, -days)
if not is_holiday(self.employee, date):
unmarked_days -= 1
return no_of_holidays

return unmarked_days
def _get_marked_attendance_days(self, holidays: list | None = None) -> float:
Attendance = frappe.qb.DocType("Attendance")
query = (
frappe.qb.from_(Attendance)
.select(Count("*"))
.where(
(Attendance.attendance_date.between(self.actual_start_date, self.actual_end_date))
& (Attendance.employee == self.employee)
& (Attendance.docstatus == 1)
)
)
if holidays:
query = query.where(Attendance.attendance_date.notin(holidays))

return query.run()[0][0]

def get_payment_days(self, include_holidays_in_total_working_days):
if self.joining_date and self.joining_date > getdate(self.end_date):
Expand Down
54 changes: 50 additions & 4 deletions hrms/payroll/doctype/salary_slip/test_salary_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,11 @@ def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):

new_emp_id = make_employee("[email protected]")
joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
holidays = 0

for days in range(date_diff(relieving_date, joining_date) + 1):
date = add_days(joining_date, days)
if not is_holiday("Salary Slip Test Holiday List", date):
mark_attendance(new_emp_id, date, "Present", ignore_validate=True)
else:
holidays += 1

frappe.db.set_value(
"Employee",
Expand All @@ -254,7 +251,7 @@ def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):
)

self.assertEqual(new_ss.total_working_days, no_of_days[0])
self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
self.assertEqual(new_ss.payment_days, no_of_days[0] - 8)

@change_settings(
"Payroll Settings",
Expand Down Expand Up @@ -519,6 +516,55 @@ def test_consider_marked_attendance_on_holidays(self):
ss.save()
self.assertEqual(ss.total_working_days, no_of_days[0])

@change_settings(
"Payroll Settings",
{
"payroll_based_on": "Attendance",
"consider_unmarked_attendance_as": "Absent",
"include_holidays_in_total_working_days": 1,
"consider_marked_attendance_on_holidays": 1,
},
)
def test_consider_marked_attendance_on_holidays_with_unmarked_attendance(self):
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday

no_of_days = get_no_of_days()
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
joining_date = add_days(month_start_date, 3)

emp_id = make_employee(
"[email protected]",
status="Active",
joining_date=joining_date,
relieving_date=None,
)

for days in range(date_diff(month_end_date, add_days(joining_date, 1)) + 1):
date = add_days(joining_date, days)
if not is_holiday("Salary Slip Test Holiday List", date):
mark_attendance(emp_id, date, "Present", ignore_validate=True)

# mark absent on holiday
first_sunday = get_first_sunday(for_date=getdate())
mark_attendance(emp_id, first_sunday, "Absent", ignore_validate=True)

ss = make_employee_salary_slip(
emp_id,
"Monthly",
"Test Salary Slip With Holidays Included",
)

self.assertEqual(ss.total_working_days, no_of_days[0])
# no_of_days - absent on holiday - period before DOJ - 1 unmarked attendance
self.assertEqual(ss.payment_days, no_of_days[0] - 1 - 3 - 1)

# disable consider marked attendance on holidays
frappe.db.set_single_value("Payroll Settings", "consider_marked_attendance_on_holidays", 0)
ss.save()
self.assertEqual(ss.total_working_days, no_of_days[0])
# no_of_days - period before DOJ
self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 1)

@change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 1})
def test_payment_days(self):
from hrms.payroll.doctype.salary_structure.test_salary_structure import (
Expand Down

0 comments on commit 6eed7a3

Please sign in to comment.