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",