Skip to content

Commit 71a4157

Browse files
committed
fix(attendance_request): enhance leave record checks for half-day attendance requests
1 parent 6db4a95 commit 71a4157

2 files changed

Lines changed: 130 additions & 4 deletions

File tree

hrms/hr/doctype/attendance_request/attendance_request.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,25 @@ def create_or_update_attendance(self, date: str):
186186
),
187187
title=_("Attendance Updated"),
188188
)
189+
elif status == "Half Day" and doc.half_day_status == "Absent" and self.half_day:
190+
old_half_day_status = doc.half_day_status
191+
doc.db_set({"half_day_status": "Present", "attendance_request": self.name})
192+
text = _(
193+
"Changed the Status for Other Half from {0} to {1} via Attendance Request as the status is Half Day"
194+
).format(frappe.bold(old_half_day_status), frappe.bold("Present"))
195+
doc.add_comment(comment_type="Info", text=text)
196+
197+
frappe.msgprint(
198+
_(
199+
"Updated Status for Other Half from {0} to {1} for date {2} in the attendance record {3}"
200+
).format(
201+
frappe.bold(old_half_day_status),
202+
frappe.bold("Present"),
203+
frappe.bold(format_date(date)),
204+
get_link_to_form("Attendance", doc.name),
205+
),
206+
title=_("Attendance Updated"),
207+
)
189208
else:
190209
# submit a new attendance record
191210
doc = frappe.new_doc("Attendance")
@@ -221,6 +240,19 @@ def should_mark_attendance(self, attendance_date: str) -> bool:
221240
return True
222241

223242
def has_leave_record(self, attendance_date: str) -> str | None:
243+
filters = {
244+
"employee": self.employee,
245+
"docstatus": 1,
246+
"from_date": ("<=", attendance_date),
247+
"to_date": (">=", attendance_date),
248+
"status": "Approved",
249+
}
250+
if self.half_day_date == attendance_date:
251+
filters["half_day"] = 0
252+
253+
return frappe.db.exists("Leave Application", filters)
254+
255+
def has_half_day_leave_record(self, attendance_date: str) -> str | None:
224256
return frappe.db.exists(
225257
"Leave Application",
226258
{
@@ -229,6 +261,8 @@ def has_leave_record(self, attendance_date: str) -> str | None:
229261
"from_date": ("<=", attendance_date),
230262
"to_date": (">=", attendance_date),
231263
"status": "Approved",
264+
"half_day": 1,
265+
"half_day_date": attendance_date,
232266
},
233267
)
234268

@@ -256,6 +290,8 @@ def status_unchanged(self, attendance_date):
256290
new_status = self.get_attendance_status(attendance_date)
257291
attendance_doc = self.get_attendance_doc(attendance_date)
258292
if attendance_doc and attendance_doc.status == new_status:
293+
if new_status == "Half Day" and self.half_day and attendance_doc.half_day_status == "Absent":
294+
return False
259295
return True
260296
return False
261297

@@ -277,7 +313,6 @@ def get_attendance_warnings(self) -> list:
277313

278314
for day in range(request_days):
279315
attendance_date = add_days(self.from_date, day)
280-
281316
if not self.include_holidays and is_holiday(self.employee, attendance_date):
282317
attendance_warnings.append({"date": attendance_date, "reason": "Holiday", "action": "Skip"})
283318
elif self.has_leave_record(attendance_date):

hrms/hr/doctype/attendance_request/test_attendance_request.py

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,7 @@ def test_skip_attendance_on_leave(self):
117117
self.to_date = get_year_ending(getdate())
118118

119119
frappe.delete_doc_if_exists("Leave Type", "Test Skip Attendance", force=1)
120-
leave_type = frappe.get_doc(
121-
dict(leave_type_name="Test Skip Attendance", doctype="Leave Type")
122-
).insert()
120+
leave_type = frappe.get_doc(leave_type_name="Test Skip Attendance", doctype="Leave Type").insert()
123121

124122
make_allocation_record(leave_type=leave_type.name, from_date=self.from_date, to_date=self.to_date)
125123
today = getdate()
@@ -243,6 +241,99 @@ def test_half_day_status_change_when_existing_attendance_is_updated(self):
243241
)
244242
self.assertEqual(half_day_status, "Absent")
245243

244+
def test_half_day_absent_half_to_present(self):
245+
"""Test attendance request updates half_day_status from Absent to Present when existing Half Day attendance has the other half marked absent"""
246+
today = getdate()
247+
248+
mark_attendance(self.employee.name, today, "Half Day", half_day_status="Absent")
249+
250+
attendance_request = frappe.get_doc(
251+
{
252+
"doctype": "Attendance Request",
253+
"employee": self.employee.name,
254+
"from_date": today,
255+
"to_date": today,
256+
"reason": "On Duty",
257+
"half_day": 1,
258+
"half_day_date": today,
259+
"company": "_Test Company",
260+
}
261+
).save()
262+
attendance_request.submit()
263+
264+
updated = frappe.db.get_value(
265+
"Attendance",
266+
{"employee": self.employee.name, "attendance_date": today, "docstatus": 1},
267+
["status", "half_day_status"],
268+
as_dict=True,
269+
)
270+
self.assertEqual(updated.status, "Half Day")
271+
self.assertEqual(updated.half_day_status, "Present")
272+
273+
def test_half_day_with_shift_auto_absent(self):
274+
"""Test half-day attendance request when shift_type auto-flags the other half as absent due to missing checkins"""
275+
from_date = get_year_start(add_months(getdate(), -1))
276+
to_date = get_year_ending(getdate())
277+
today = getdate()
278+
279+
frappe.delete_doc_if_exists("Leave Type", "Test Half Day Leave", force=1)
280+
leave_type = frappe.get_doc(leave_type_name="Test Half Day Leave", doctype="Leave Type").insert()
281+
make_allocation_record(leave_type=leave_type.name, from_date=from_date, to_date=to_date)
282+
frappe.db.delete("Holiday", {"parent": self.holiday_list})
283+
284+
# 1) Submit half-day leave
285+
leave_application = frappe.get_doc(
286+
{
287+
"doctype": "Leave Application",
288+
"employee": self.employee.name,
289+
"leave_type": leave_type.name,
290+
"from_date": today,
291+
"to_date": today,
292+
"half_day": 1,
293+
"half_day_date": today,
294+
"status": "Approved",
295+
}
296+
).insert()
297+
leave_application.submit()
298+
299+
# 2) Create shift type + assignment
300+
shift_type = create_shift("Test Half Day Shift", "09:00:00", "17:00:00")
301+
shift_type.process_attendance_after = add_days(today, -1)
302+
shift_type.last_sync_of_checkin = add_days(today, 1)
303+
shift_type.enable_auto_attendance = 1
304+
shift_type.save()
305+
create_shift_assignment(self.employee.name, shift_type.name, add_days(today, -1), add_days(today, 1))
306+
307+
# 3) Attendance request for the other half — creates half-day attendance
308+
attendance_request = frappe.get_doc(
309+
{
310+
"doctype": "Attendance Request",
311+
"employee": self.employee.name,
312+
"from_date": today,
313+
"to_date": today,
314+
"reason": "On Duty",
315+
"half_day": 1,
316+
"half_day_date": today,
317+
"company": "_Test Company",
318+
}
319+
).save()
320+
attendance_request.submit()
321+
322+
# 4) Shift auto-attendance marks the other half absent when no checkins exist
323+
frappe.get_doc("Shift Type", shift_type.name).mark_absent_for_half_day_dates(self.employee.name)
324+
325+
# Verify
326+
attendance = frappe.db.get_value(
327+
"Attendance",
328+
{"attendance_request": attendance_request.name},
329+
["name", "status", "half_day_status", "modify_half_day_status"],
330+
as_dict=True,
331+
)
332+
self.assertTrue(attendance)
333+
self.assertEqual(attendance.status, "Half Day")
334+
self.assertEqual(attendance.half_day_status, "Absent")
335+
self.assertEqual(attendance.modify_half_day_status, 0)
336+
246337
@HRMSTestSuite.change_settings("HR Settings", {"allow_multiple_shift_assignments": True})
247338
def test_overlap_with_different_shifts(self):
248339
shift_1 = create_shift("Morning Shift", "08:00:00", "12:00:00")

0 commit comments

Comments
 (0)