Skip to content

Commit 6eed7a3

Browse files
authored
Merge pull request #1427 from frappe/version-15-hotfix
chore: release v15
2 parents f1a0b09 + f25a245 commit 6eed7a3

File tree

6 files changed

+152
-78
lines changed

6 files changed

+152
-78
lines changed

hrms/hr/doctype/employee_onboarding/employee_onboarding.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
],
175175
"is_submittable": 1,
176176
"links": [],
177-
"modified": "2022-01-29 12:33:57.120384",
177+
"modified": "2024-02-12 19:33:57.120384",
178178
"modified_by": "Administrator",
179179
"module": "HR",
180180
"name": "Employee Onboarding",
@@ -195,7 +195,19 @@
195195
"share": 1,
196196
"submit": 1,
197197
"write": 1
198-
}
198+
},
199+
{
200+
"amend": 1,
201+
"cancel": 1,
202+
"create": 1,
203+
"export": 1,
204+
"print": 1,
205+
"read": 1,
206+
"role": "HR Manager",
207+
"share": 1,
208+
"submit": 1,
209+
"write": 1
210+
}
199211
],
200212
"sort_field": "modified",
201213
"sort_order": "DESC",

hrms/hr/doctype/expense_claim/expense_claim.py

+19-24
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,26 @@ def set_status(self, update=False):
5454

5555
precision = self.precision("grand_total")
5656

57-
if (
58-
# set as paid
59-
self.is_paid
60-
or (
61-
flt(self.total_sanctioned_amount) > 0
62-
and (
63-
# grand total is reimbursed
64-
(
65-
self.docstatus == 1
66-
and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision)
57+
if self.docstatus == 1:
58+
if self.approval_status == "Approved":
59+
if (
60+
# set as paid
61+
self.is_paid
62+
or (
63+
flt(self.total_sanctioned_amount) > 0
64+
and (
65+
# grand total is reimbursed
66+
(flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision))
67+
# grand total (to be paid) is 0 since linked advances already cover the claimed amount
68+
or (flt(self.grand_total, precision) == 0)
69+
)
6770
)
68-
# grand total (to be paid) is 0 since linked advances already cover the claimed amount
69-
or (flt(self.grand_total, precision) == 0)
70-
)
71-
)
72-
) and self.approval_status == "Approved":
73-
status = "Paid"
74-
elif (
75-
flt(self.total_sanctioned_amount) > 0
76-
and self.docstatus == 1
77-
and self.approval_status == "Approved"
78-
):
79-
status = "Unpaid"
80-
elif self.docstatus == 1 and self.approval_status == "Rejected":
81-
status = "Rejected"
71+
):
72+
status = "Paid"
73+
elif flt(self.total_sanctioned_amount) > 0:
74+
status = "Unpaid"
75+
elif self.approval_status == "Rejected":
76+
status = "Rejected"
8277

8378
if update:
8479
self.db_set("status", status)

hrms/hr/doctype/leave_application/leave_application_dashboard.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
</tbody>
2727
</table>
2828
{% else %}
29-
<p style="margin-top: 30px;"> No Leave has been allocated. </p>
29+
<p style="margin-top: 30px;"> {{ __("No leaves have been allocated.") }} </p>
3030
{% endif %}

hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,13 @@ def get_columns_for_days(filters: Filters) -> List[Dict]:
160160
days = []
161161

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

171172
return days
@@ -619,7 +620,7 @@ def get_chart_data(attendance_map: Dict, filters: Filters) -> Dict:
619620

620621
for employee, attendance_dict in attendance_map.items():
621622
for shift, attendance in attendance_dict.items():
622-
attendance_on_day = attendance.get(day["fieldname"])
623+
attendance_on_day = attendance.get(cint(day["fieldname"]))
623624

624625
if attendance_on_day == "On Leave":
625626
# leave should be counted only once for the entire day

hrms/payroll/doctype/salary_slip/salary_slip.py

+64-44
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from frappe import _, msgprint
1010
from frappe.model.naming import make_autoname
1111
from frappe.query_builder import Order
12-
from frappe.query_builder.functions import Sum
12+
from frappe.query_builder.functions import Count, Sum
1313
from frappe.utils import (
1414
add_days,
1515
ceil,
@@ -479,63 +479,83 @@ def get_working_days_details(self, lwp=None, for_preview=0):
479479
payroll_settings.payroll_based_on == "Attendance"
480480
and consider_unmarked_attendance_as == "Absent"
481481
):
482-
unmarked_days = self.get_unmarked_days(payroll_settings.include_holidays_in_total_working_days)
482+
unmarked_days = self.get_unmarked_days(
483+
payroll_settings.include_holidays_in_total_working_days, holidays
484+
)
483485
self.absent_days += unmarked_days # will be treated as absent
484486
self.payment_days -= unmarked_days
485487
else:
486488
self.payment_days = 0
487489

488-
def get_unmarked_days(self, include_holidays_in_total_working_days):
489-
unmarked_days = self.total_working_days
490+
def get_unmarked_days(
491+
self, include_holidays_in_total_working_days: bool, holidays: list | None = None
492+
) -> float:
493+
"""Calculates the number of unmarked days for an employee within a date range"""
494+
unmarked_days = (
495+
self.total_working_days
496+
- self._get_days_outside_period(include_holidays_in_total_working_days, holidays)
497+
- self._get_marked_attendance_days(holidays)
498+
)
499+
500+
if include_holidays_in_total_working_days and holidays:
501+
unmarked_days -= self._get_number_of_holidays(holidays)
502+
503+
return unmarked_days
504+
505+
def _get_days_outside_period(
506+
self, include_holidays_in_total_working_days: bool, holidays: list | None = None
507+
):
508+
"""Returns days before DOJ or after relieving date"""
509+
510+
def _get_days(start_date, end_date):
511+
no_of_days = date_diff(end_date, start_date) + 1
490512

513+
if include_holidays_in_total_working_days:
514+
return no_of_days
515+
else:
516+
days = 0
517+
end_date = getdate(end_date)
518+
for day in range(no_of_days):
519+
date = add_days(end_date, -day)
520+
if date not in holidays:
521+
days += 1
522+
return days
523+
524+
days = 0
491525
if self.actual_start_date != self.start_date:
492-
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
493-
unmarked_days,
494-
include_holidays_in_total_working_days,
495-
self.start_date,
496-
add_days(self.joining_date, -1),
497-
)
526+
days += _get_days(self.start_date, add_days(self.joining_date, -1))
498527

499528
if self.actual_end_date != self.end_date:
500-
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(
501-
unmarked_days,
502-
include_holidays_in_total_working_days,
503-
add_days(self.relieving_date, 1),
504-
self.end_date,
505-
)
529+
days += _get_days(add_days(self.relieving_date, 1), self.end_date)
506530

507-
# exclude days for which attendance has been marked
508-
marked_days = frappe.db.count(
509-
"Attendance",
510-
filters={
511-
"attendance_date": ["between", [self.actual_start_date, self.actual_end_date]],
512-
"employee": self.employee,
513-
"docstatus": 1,
514-
},
515-
)
516-
unmarked_days -= marked_days
531+
return days
517532

518-
return unmarked_days
533+
def _get_number_of_holidays(self, holidays: list | None = None) -> float:
534+
no_of_holidays = 0
535+
actual_end_date = getdate(self.actual_end_date)
519536

520-
def get_unmarked_days_based_on_doj_or_relieving(
521-
self, unmarked_days, include_holidays_in_total_working_days, start_date, end_date
522-
):
523-
"""
524-
Exclude days before DOJ or after
525-
Relieving Date from unmarked days
526-
"""
527-
from erpnext.setup.doctype.employee.employee import is_holiday
537+
for days in range(date_diff(self.actual_end_date, self.actual_start_date) + 1):
538+
date = add_days(actual_end_date, -days)
539+
if date in holidays:
540+
no_of_holidays += 1
528541

529-
if include_holidays_in_total_working_days:
530-
unmarked_days -= date_diff(end_date, start_date) + 1
531-
else:
532-
# exclude only if not holidays
533-
for days in range(date_diff(end_date, start_date) + 1):
534-
date = add_days(end_date, -days)
535-
if not is_holiday(self.employee, date):
536-
unmarked_days -= 1
542+
return no_of_holidays
537543

538-
return unmarked_days
544+
def _get_marked_attendance_days(self, holidays: list | None = None) -> float:
545+
Attendance = frappe.qb.DocType("Attendance")
546+
query = (
547+
frappe.qb.from_(Attendance)
548+
.select(Count("*"))
549+
.where(
550+
(Attendance.attendance_date.between(self.actual_start_date, self.actual_end_date))
551+
& (Attendance.employee == self.employee)
552+
& (Attendance.docstatus == 1)
553+
)
554+
)
555+
if holidays:
556+
query = query.where(Attendance.attendance_date.notin(holidays))
557+
558+
return query.run()[0][0]
539559

540560
def get_payment_days(self, include_holidays_in_total_working_days):
541561
if self.joining_date and self.joining_date > getdate(self.end_date):

hrms/payroll/doctype/salary_slip/test_salary_slip.py

+50-4
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,11 @@ def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):
232232

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

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

244241
frappe.db.set_value(
245242
"Employee",
@@ -254,7 +251,7 @@ def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):
254251
)
255252

256253
self.assertEqual(new_ss.total_working_days, no_of_days[0])
257-
self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
254+
self.assertEqual(new_ss.payment_days, no_of_days[0] - 8)
258255

259256
@change_settings(
260257
"Payroll Settings",
@@ -519,6 +516,55 @@ def test_consider_marked_attendance_on_holidays(self):
519516
ss.save()
520517
self.assertEqual(ss.total_working_days, no_of_days[0])
521518

519+
@change_settings(
520+
"Payroll Settings",
521+
{
522+
"payroll_based_on": "Attendance",
523+
"consider_unmarked_attendance_as": "Absent",
524+
"include_holidays_in_total_working_days": 1,
525+
"consider_marked_attendance_on_holidays": 1,
526+
},
527+
)
528+
def test_consider_marked_attendance_on_holidays_with_unmarked_attendance(self):
529+
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
530+
531+
no_of_days = get_no_of_days()
532+
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
533+
joining_date = add_days(month_start_date, 3)
534+
535+
emp_id = make_employee(
536+
537+
status="Active",
538+
joining_date=joining_date,
539+
relieving_date=None,
540+
)
541+
542+
for days in range(date_diff(month_end_date, add_days(joining_date, 1)) + 1):
543+
date = add_days(joining_date, days)
544+
if not is_holiday("Salary Slip Test Holiday List", date):
545+
mark_attendance(emp_id, date, "Present", ignore_validate=True)
546+
547+
# mark absent on holiday
548+
first_sunday = get_first_sunday(for_date=getdate())
549+
mark_attendance(emp_id, first_sunday, "Absent", ignore_validate=True)
550+
551+
ss = make_employee_salary_slip(
552+
emp_id,
553+
"Monthly",
554+
"Test Salary Slip With Holidays Included",
555+
)
556+
557+
self.assertEqual(ss.total_working_days, no_of_days[0])
558+
# no_of_days - absent on holiday - period before DOJ - 1 unmarked attendance
559+
self.assertEqual(ss.payment_days, no_of_days[0] - 1 - 3 - 1)
560+
561+
# disable consider marked attendance on holidays
562+
frappe.db.set_single_value("Payroll Settings", "consider_marked_attendance_on_holidays", 0)
563+
ss.save()
564+
self.assertEqual(ss.total_working_days, no_of_days[0])
565+
# no_of_days - period before DOJ
566+
self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 1)
567+
522568
@change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 1})
523569
def test_payment_days(self):
524570
from hrms.payroll.doctype.salary_structure.test_salary_structure import (

0 commit comments

Comments
 (0)