fix: add validation for overlapping shift attendance
- skip auto attendance in case of overlapping shift attendance record - this case won't occur in case of shift assignment, since it will not allow overlapping shifts to be assigned - can happen if manual attendance records are created
This commit is contained in:
parent
7bd84f2696
commit
83489be7d9
@ -8,6 +8,7 @@ from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.utils import cint, cstr, formatdate, get_datetime, get_link_to_form, getdate, nowdate
|
||||
|
||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import has_overlapping_timings
|
||||
from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee
|
||||
|
||||
|
||||
@ -15,6 +16,10 @@ class DuplicateAttendanceError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class OverlappingShiftAttendanceError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class Attendance(Document):
|
||||
def validate(self):
|
||||
from erpnext.controllers.status_updater import validate_status
|
||||
@ -23,6 +28,7 @@ class Attendance(Document):
|
||||
validate_active_employee(self.employee)
|
||||
self.validate_attendance_date()
|
||||
self.validate_duplicate_record()
|
||||
self.validate_overlapping_shift_attendance()
|
||||
self.validate_employee_status()
|
||||
self.check_leave_record()
|
||||
|
||||
@ -55,6 +61,22 @@ class Attendance(Document):
|
||||
exc=DuplicateAttendanceError,
|
||||
)
|
||||
|
||||
def validate_overlapping_shift_attendance(self):
|
||||
attendance = get_overlapping_shift_attendance(
|
||||
self.employee, self.attendance_date, self.shift, self.name
|
||||
)
|
||||
|
||||
if attendance:
|
||||
frappe.throw(
|
||||
_("Attendance for employee {0} is already marked for an overlapping shift {1}: {2}").format(
|
||||
frappe.bold(self.employee),
|
||||
frappe.bold(attendance.shift),
|
||||
get_link_to_form("Attendance", attendance.name),
|
||||
),
|
||||
title=_("Overlapping Shift Attendance"),
|
||||
exc=OverlappingShiftAttendanceError,
|
||||
)
|
||||
|
||||
def validate_employee_status(self):
|
||||
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
|
||||
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
|
||||
@ -143,6 +165,29 @@ def get_duplicate_attendance_record(employee, attendance_date, shift, name=None)
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_overlapping_shift_attendance(employee, attendance_date, shift, name=None):
|
||||
attendance = frappe.qb.DocType("Attendance")
|
||||
query = (
|
||||
frappe.qb.from_(attendance)
|
||||
.select(attendance.name, attendance.shift)
|
||||
.where(
|
||||
(attendance.employee == employee)
|
||||
& (attendance.docstatus < 2)
|
||||
& (attendance.attendance_date == attendance_date)
|
||||
& (attendance.shift != shift)
|
||||
)
|
||||
)
|
||||
|
||||
if name:
|
||||
query = query.where(attendance.name != name)
|
||||
|
||||
overlapping_attendance = query.run(as_dict=True)
|
||||
|
||||
if overlapping_attendance and has_overlapping_timings(shift, overlapping_attendance[0].shift):
|
||||
return overlapping_attendance[0]
|
||||
return {}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
events = []
|
||||
@ -190,25 +235,30 @@ def mark_attendance(
|
||||
late_entry=False,
|
||||
early_exit=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,
|
||||
"late_entry": late_entry,
|
||||
"early_exit": early_exit,
|
||||
}
|
||||
)
|
||||
attendance.flags.ignore_validate = ignore_validate
|
||||
attendance.insert()
|
||||
attendance.submit()
|
||||
return attendance.name
|
||||
if get_duplicate_attendance_record(employee, attendance_date, shift):
|
||||
return
|
||||
|
||||
if get_overlapping_shift_attendance(employee, attendance_date, shift):
|
||||
return
|
||||
|
||||
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,
|
||||
"late_entry": late_entry,
|
||||
"early_exit": early_exit,
|
||||
}
|
||||
)
|
||||
attendance.flags.ignore_validate = ignore_validate
|
||||
attendance.insert()
|
||||
attendance.submit()
|
||||
return attendance.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -7,7 +7,10 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_datetime
|
||||
|
||||
from erpnext.hr.doctype.attendance.attendance import get_duplicate_attendance_record
|
||||
from erpnext.hr.doctype.attendance.attendance import (
|
||||
get_duplicate_attendance_record,
|
||||
get_overlapping_shift_attendance,
|
||||
)
|
||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import (
|
||||
get_actual_start_end_datetime_of_shift,
|
||||
)
|
||||
@ -137,7 +140,10 @@ def mark_attendance_and_link_log(
|
||||
return None
|
||||
elif attendance_status in ("Present", "Absent", "Half Day"):
|
||||
employee_doc = frappe.get_doc("Employee", employee)
|
||||
if not get_duplicate_attendance_record(employee, attendance_date, shift):
|
||||
duplicate = get_duplicate_attendance_record(employee, attendance_date, shift)
|
||||
overlapping = get_overlapping_shift_attendance(employee, attendance_date, shift)
|
||||
|
||||
if not duplicate and not overlapping:
|
||||
doc_dict = {
|
||||
"doctype": "Attendance",
|
||||
"employee": employee,
|
||||
|
Loading…
Reference in New Issue
Block a user