refactor: Allow multiple attendance records creation for different shifts
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 7f4bd83..2d8bf15 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -5,11 +5,15 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate
+from frappe.utils import cint, cstr, formatdate, get_datetime, get_link_to_form, getdate, nowdate
+from frappe.query_builder import Criterion
 
 from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee
 
 
+class DuplicateAttendanceError(frappe.ValidationError):
+	pass
+
 class Attendance(Document):
 	def validate(self):
 		from erpnext.controllers.status_updater import validate_status
@@ -35,22 +39,12 @@
 			frappe.throw(_("Attendance date can not be less than employee's joining date"))
 
 	def validate_duplicate_record(self):
-		res = frappe.db.sql(
-			"""
-			select name from `tabAttendance`
-			where employee = %s
-				and attendance_date = %s
-				and name != %s
-				and docstatus != 2
-		""",
-			(self.employee, getdate(self.attendance_date), self.name),
-		)
-		if res:
-			frappe.throw(
-				_("Attendance for employee {0} is already marked for the date {1}").format(
-					frappe.bold(self.employee), frappe.bold(self.attendance_date)
-				)
-			)
+		duplicate = get_duplicate_attendance_record(self.employee, self.attendance_date, self.shift, self.name)
+
+		if duplicate:
+			frappe.throw(_("Attendance for employee {0} is already marked for the date {1}: {2}").format(
+				frappe.bold(self.employee), frappe.bold(self.attendance_date), get_link_to_form("Attendance", duplicate[0].name)),
+				title=_("Duplicate Attendance"), exc=DuplicateAttendanceError)
 
 	def validate_employee_status(self):
 		if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
@@ -103,6 +97,40 @@
 			frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
 
 
+def get_duplicate_attendance_record(employee, attendance_date, shift, name=None):
+	attendance = frappe.qb.DocType("Attendance")
+	query = (
+		frappe.qb.from_(attendance)
+			.select(attendance.name)
+			.where(
+				(attendance.employee == employee)
+				& (attendance.docstatus < 2)
+			)
+	)
+
+	if shift:
+		query = query.where(
+			Criterion.any([
+				Criterion.all([
+					((attendance.shift.isnull()) | (attendance.shift == "")),
+					(attendance.attendance_date == attendance_date)
+				]),
+				Criterion.all([
+					((attendance.shift.isnotnull()) | (attendance.shift != "")),
+					(attendance.attendance_date == attendance_date),
+					(attendance.shift == shift)
+				])
+			])
+		)
+	else:
+		query = query.where((attendance.attendance_date == attendance_date))
+
+	if name:
+		query = query.where(attendance.name != name)
+
+	return query.run(as_dict=True)
+
+
 @frappe.whitelist()
 def get_events(start, end, filters=None):
 	events = []
@@ -139,26 +167,18 @@
 		if e not in events:
 			events.append(e)
 
-
-def mark_attendance(
-	employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False
-):
-	if not frappe.db.exists(
-		"Attendance",
-		{"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
-	):
-		company = frappe.db.get_value("Employee", employee, "company")
-		attendance = frappe.get_doc(
-			{
-				"doctype": "Attendance",
-				"employee": employee,
-				"attendance_date": attendance_date,
-				"status": status,
-				"company": company,
-				"shift": shift,
-				"leave_type": leave_type,
-			}
-		)
+def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
+	if not get_duplicate_attendance_record(employee, attendance_date, shift):
+		company = frappe.db.get_value('Employee', employee, 'company')
+		attendance = frappe.get_doc({
+			'doctype': 'Attendance',
+			'employee': employee,
+			'attendance_date': attendance_date,
+			'status': status,
+			'company': company,
+			'shift': shift,
+			'leave_type': leave_type
+		})
 		attendance.flags.ignore_validate = ignore_validate
 		attendance.insert()
 		attendance.submit()