fix: unlink Attendance from Employee Checkins on cancellation (#31045)
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index e43d40e..f3cae80 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -32,6 +32,9 @@
self.validate_employee_status()
self.check_leave_record()
+ def on_cancel(self):
+ self.unlink_attendance_from_checkins()
+
def validate_attendance_date(self):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
@@ -127,6 +130,33 @@
if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
+ def unlink_attendance_from_checkins(self):
+ EmployeeCheckin = frappe.qb.DocType("Employee Checkin")
+ linked_logs = (
+ frappe.qb.from_(EmployeeCheckin)
+ .select(EmployeeCheckin.name)
+ .where(EmployeeCheckin.attendance == self.name)
+ .for_update()
+ .run(as_dict=True)
+ )
+
+ if linked_logs:
+ (
+ frappe.qb.update(EmployeeCheckin)
+ .set("attendance", "")
+ .where(EmployeeCheckin.attendance == self.name)
+ ).run()
+
+ frappe.msgprint(
+ msg=_("Unlinked Attendance record from Employee Checkins: {}").format(
+ ", ".join(get_link_to_form("Employee Checkin", log.name) for log in linked_logs)
+ ),
+ title=_("Unlinked logs"),
+ indicator="blue",
+ is_minimizable=True,
+ wide=True,
+ )
+
def get_duplicate_attendance_record(employee, attendance_date, shift, name=None):
attendance = frappe.qb.DocType("Attendance")
diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
index b603b3a..eb81f7d 100644
--- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
@@ -76,6 +76,17 @@
)
self.assertEqual(attendance_count, 1)
+ def test_unlink_attendance_on_cancellation(self):
+ employee = make_employee("test_mark_attendance_and_link_log@example.com")
+ logs = make_n_checkins(employee, 3)
+
+ frappe.db.delete("Attendance", {"employee": employee})
+ attendance = mark_attendance_and_link_log(logs, "Present", nowdate(), 8.2)
+ attendance.cancel()
+
+ linked_logs = frappe.db.get_all("Employee Checkin", {"attendance": attendance.name})
+ self.assertEquals(len(linked_logs), 0)
+
def test_calculate_working_hours(self):
check_in_out_type = [
"Alternating entries as IN and OUT during the same shift",