'required|integer|is_not_unique[students.id]', 'schedule_id' => 'permit_empty|integer|is_not_unique[schedules.id]', 'checkin_type' => 'permit_empty|in_list[mapel,masuk,pulang]', 'attendance_date' => 'required|valid_date[Y-m-d]', 'device_id' => 'required|integer|is_not_unique[devices.id]', 'checkin_at' => 'required|valid_date[Y-m-d H:i:s]', 'latitude' => 'required|decimal', 'longitude' => 'required|decimal', 'confidence' => 'permit_empty|decimal', 'status' => 'required|in_list[PRESENT,LATE,OUTSIDE_ZONE,NO_SCHEDULE,INVALID_DEVICE]', ]; /** * Check if attendance already exists for student + schedule on the given date (server date). * Used for duplicate protection: one attendance per (student_id, schedule_id, attendance_date). * * @param int $studentId * @param int $scheduleId * @param string $attendanceDate Date in Y-m-d format (use server date) * @return bool */ public function hasAttendanceFor(int $studentId, int $scheduleId, string $attendanceDate): bool { $row = $this->where('student_id', $studentId) ->where('schedule_id', $scheduleId) ->where('attendance_date', $attendanceDate) ->first(); return $row !== null; } protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }