1, 'Reachable' => 2, 'Unreachable' => 3, 'LOGGED_OUT' => 4, 'unpause' => 5, 'pause' => 6, ]; private $calendarService; private $SettingsHelper; private $UserHelper; public function __construct(CalendarService $calendarService) { parent::__construct(); $this->calendarService = $calendarService; $this->SettingsHelper = new SettingsHelper(); $this->UserHelper = new UserHelper(); } public function attendance(Request $request){ $user = \Auth::user(); $conds = $this->SettingsHelper->defaultConds(); if ($request->has('daterange')) { $range = explode(' - ', $request->daterange); try { $conds['from'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[0])->format('Y-m-d H:i:s'); $conds['to'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[1])->format('Y-m-d H:i:s'); } catch (\Exception $e) { // Fallback to default conditions if parsing fails $conds['from'] = Carbon::now()->startOfDay()->format('Y-m-d H:i:s'); $conds['to'] = Carbon::now()->endOfDay()->format('Y-m-d H:i:s'); } } if ($request->ajax() && empty($request->action)) { return $this->attendanceDatatable($conds, $request->user_ext, $request->group_ext); }elseif(!empty($request->action && $request->action == 'excel')){ $summaryData = $this->calculateAttendanceSummary($conds, $request->user_ext, $request->group_ext); return Excel::download(new AttendanceSummaryExport($summaryData), 'attendance_summary.xlsx'); } $calendar = $this->getOrCreateDefaultCalendar(); $fullCalendar = $this->calendarService->fullCalendarInstance($calendar, $request->user_ext, $request->group_ext, $conds); $agent_names = $this->UserHelper->getCompanyUsers(); if ($user->access_to_all_entity != '1') { $agent_names = User::whereHas('groups', function ($q) { $q->whereIn( 'groups.id', auth()->user()->accessibleQueueIds() ); }) ->whereDoesntHave('roles', function ($q) { $q->where('name', 'company_manager'); }) ->visibleInReports() ->get(); } $groups = Group::where('company_id',$user->company->id ?? 1)->pluck('name','id')->toArray(); if($user->access_to_all_entity != '1'){ $groups = Group::whereIn('id', $user->accessibleQueueIds())->pluck('name','id')->toArray(); if($user->roles[0]->name == 'user'){ $groups = null; } } $agents_options = [ 'Agents' => $agent_names->pluck('name','id')->toArray() ]; if($user->roles[0]->name == 'user' && $user->access_to_all_entity != '1'){ $agents_options = [ 'Agents' => $agent_names->where('id', $user->id)->pluck('name','id')->toArray() ]; } $groups_options = [ 'Groups' => $groups ]; $view = view('reporting::attendance'); $filter_area = view('reporting::widgets.filter_area'); $filter_area->with('conds',$conds); $filter_area->with('agents_options',$agents_options); $filter_area->with('groups_options',$groups_options); $filter_area->with('user_ext',$request->user_ext); $filter_area->with('group_ext',$request->group_ext); $filter_area->with('submit_route',route('reporting.attendance')); $view->with('fullCalendar', $fullCalendar); $view->with('calendar', $calendar); $view->with('filter_area',$filter_area); $view->with('conds',$conds); $view->with('user_ext',$request->user_ext); $view->with('group_ext',$request->group_ext); $summaryData = $this->calculateAttendanceSummary($conds, $request->user_ext, $request->group_ext); $view->with('summaryData', $summaryData); $view->with('user_code_enabled', (bool) CompanySettings::get(SettingsHelper::S_COMPANY_USER_CODE_ENABLED)); return $view; } public function getOrCreateDefaultCalendar() { $calendar = Calendar::where('name', 'Attendees Calendar') ->first(); if (empty($calendar)) { $calendar = new Calendar(); $calendar->name = 'Attendees Calendar'; $calendar->is_public = false; $calendar->owned_by_id = NULL; $calendar->owned_by_type = NULL; $calendar->default_view = Calendar::MONT_VIEW; $calendar->first_day = Calendar::WEEK_START_AT_MONDAY; $calendar->day_start_at = Calendar::DEFAULT_DAT_START_AT; $calendar->company_id = Landlord::getTenants()->first(); $calendar->save(); } return $calendar; } public function attendanceDatatable($conds, $user_ext, $group_ext) { $user = \Auth::user(); $company_users = $this->UserHelper->getCompanyUsers(); if ($user->access_to_all_entity != '1') { $company_users = User::whereHas('groups', function ($q) { $q->whereIn( 'groups.id', auth()->user()->accessibleQueueIds() ); }) ->whereDoesntHave('roles', function ($q) { $q->where('name', 'company_manager'); }) ->get(); } $company_users = $company_users->pluck('id')->toArray(); $data = DB::table(config('database.connections.mysql.database') . '.attendance') ->select( 'attendance.created_at', 'attendance.subject_id', 'attendance.description', 'attendance.log_name', 'users.name', 'users.email' ) ->whereIn('attendance.subject_id', $company_users) ->join(config('database.connections.mysql.database') . '.users', config('database.connections.mysql.database') . '.users.id', '=', config('database.connections.mysql.database') . '.attendance.subject_id') ->whereBetween('attendance.created_at', [$conds['from'], $conds['to']]); if (isset($user_ext)) { $data->where('subject_id', $user_ext); } elseif (isset($group_ext)) { $user_exts = User::join('group_user', config('database.connections.mysql.database') . '.users.id', '=', 'group_user.user_id') ->where('group_id', $group_ext)->pluck('id')->toArray(); $data->whereIn('subject_id', $user_exts); } if($user->roles[0]->name == 'user' && $user->access_to_all_entity != '1'){ $data->where('subject_id', $user->id); } $data->orderBy('attendance.subject_id')->orderBy('attendance.created_at', 'ASC'); $data = $data->get()->toArray(); $lastEntries = []; foreach ($data as $key => $item) { $nextItem = null; for ($i = $key + 1; $i < count($data); $i++) { if ($data[$i]->subject_id == $item->subject_id) { $nextItem = $data[$i]; break; } } if ($nextItem) { $diff = Carbon::parse($item->created_at)->diff(Carbon::parse($nextItem->created_at)); $days = $diff->d; $hours = $diff->h; $minutes = $diff->i; $seconds = $diff->s; $item->duration = sprintf("%02d:%02d:%02d:%02d", $days, $hours, $minutes, $seconds); } else { $item->duration = 'Ongoing'; } } return Datatables::of($data) ->addIndexColumn() ->editColumn('created_at', function ($row) { return Carbon::parse($row->created_at)->format('Y-m-d H:i:s'); }) ->addColumn('duration', function ($row) { return $row->duration; }) ->rawColumns(['duration']) ->make(true); } public function attendanceSummaryDatatable(Request $request) { $conds = $this->SettingsHelper->defaultConds(); if ($request->has('daterange')) { $range = explode(' - ', $request->daterange); try { $conds['from'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[0])->format('Y-m-d H:i:s'); $conds['to'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[1])->format('Y-m-d H:i:s'); } catch (\Exception $e) { // Fallback to default conditions if parsing fails $conds['from'] = Carbon::now()->startOfDay()->format('Y-m-d H:i:s'); $conds['to'] = Carbon::now()->endOfDay()->format('Y-m-d H:i:s'); } } $summaryData = $this->calculateAttendanceSummary($conds, $request->user_ext, $request->group_ext); $data = collect($summaryData)->map(function ($item) { // Calculate display totals excluding LOGGED_IN time $loggedInSeconds = $item['LOGGED_IN'] ?? 0; $totalSeconds = $item['total'] ?? 0; $displayTotalSeconds = max(0, $totalSeconds - $loggedInSeconds); return [ 'name' => $item['name'] ?? 'N/A', 'email' => $item['email'] ?? 'N/A', 'code' => $item['code'] ?? '', 'pause_formatted' => $item['pause_formatted'] ?? '00:00:00', 'Reachable_formatted' => $item['Reachable_formatted'] ?? '00:00:00', 'total_formatted' => $this->secondsToTime($displayTotalSeconds), 'pause_reasons' => $item['pause_reasons'] ?? [], // Add pause reasons ]; }); return Datatables::of($data)->make(true); } public function calculateAttendanceSummary($conds, $user_ext, $group_ext) { $user = \Auth::user(); $company_users = $this->UserHelper->getCompanyUsers(); if ($user->access_to_all_entity != '1') { $company_users = User::whereHas('groups', function ($q) { $q->whereIn( 'groups.id', auth()->user()->accessibleQueueIds() ); }) ->whereDoesntHave('roles', function ($q) { $q->where('name', 'company_manager'); }) ->get(); } $company_users = $company_users->pluck('id')->toArray(); $query = DB::table('attendance') ->select( 'attendance.id', 'attendance.created_at', 'attendance.subject_id', 'attendance.description', 'attendance.log_name', // This column contains the pause reason 'users.name', 'users.email', 'users.code' ) ->whereIn('attendance.subject_id', $company_users) ->join('users', 'users.id', '=', 'attendance.subject_id') ->whereBetween('attendance.created_at', [$conds['from'], $conds['to']]) ->where('description_code', '!=', self::DESCRIPTION_CODES['LOGGED_OUT']); // Exclude LOGGED_OUT events from the summary if (isset($user_ext)) { $query->where('subject_id', $user_ext); } elseif (isset($group_ext)) { $user_exts = DB::table('group_user') ->where('group_id', $group_ext) ->pluck('user_id') ->toArray(); $query->whereIn('subject_id', $user_exts); } if($user->roles[0]->name == 'user' && $user->access_to_all_entity != '1'){ $query->where('subject_id', $user->id); } $query->orderBy('attendance.subject_id') ->orderBy('attendance.id', 'ASC'); $data = $query->get(); // Get the last action before the period start for each user $previousActions = []; $userIds = $data->pluck('subject_id')->unique(); foreach ($userIds as $userId) { $previousAction = DB::table('attendance') ->where('subject_id', $userId) ->where('created_at', '<', $conds['from']) ->whereNotIn('description_code', [self::DESCRIPTION_CODES['LOGGED_OUT'], self::DESCRIPTION_CODES['LOGGED_IN']]) ->orderBy('id', 'DESC') ->first(); $previousActions[$userId] = $previousAction; } $summary = []; $endTime = Carbon::parse($conds['to']); $currentTime = Carbon::now(); $effectiveEndTime = $endTime->greaterThan($currentTime) ? $currentTime : $endTime; foreach ($data as $key => $item) { $userId = $item->subject_id; if (!isset($summary[$userId])) { $summary[$userId] = [ 'name' => $item->name, 'email' => $item->email, 'code' => $item->code ?? '', 'LOGGED_IN' => 0, 'pause' => 0, 'Reachable' => 0, 'total' => 0, 'pause_reasons' => [] // Array to store pause reasons ]; // Calculate the period from the start of the range to the first action $firstActionTime = Carbon::parse($item->created_at); $periodStart = Carbon::parse($conds['from']); if ($firstActionTime > $periodStart) { $initialDuration = $periodStart->diffInSeconds($firstActionTime); $previousAction = $previousActions[$userId] ?? null; if ($previousAction) { if ($previousAction->description == 'Reachable' || $previousAction->description == 'unpause') { $summary[$userId]['Reachable'] += $initialDuration; $summary[$userId]['total'] += $initialDuration; } elseif ($previousAction->description == 'pause') { $summary[$userId]['pause'] += $initialDuration; $summary[$userId]['total'] += $initialDuration; // Record initial pause reason if ($previousAction->log_name) { $this->addPauseReason($summary[$userId]['pause_reasons'], $previousAction->log_name, $initialDuration); } } } } } // Record pause reason when pause event occurs if ($item->description == 'pause' && $item->log_name) { $this->addPauseReason($summary[$userId]['pause_reasons'], $item->log_name, 0); // Duration will be added later } $nextItem = $data->get($key + 1); if ($nextItem && $nextItem->subject_id == $userId) { $duration = Carbon::parse($item->created_at)->diffInSeconds(Carbon::parse($nextItem->created_at)); if ($item->description == 'LOGGED_IN') { $prevEvent = $this->findPreviousEventForUser($data, $key, $userId); if ($prevEvent && ($prevEvent->description == 'Reachable' || $prevEvent->description == 'unpause')) { $summary[$userId]['Reachable'] += $duration; } elseif ($prevEvent && $prevEvent->description == 'pause') { $summary[$userId]['pause'] += $duration; // Add duration to pause reason if ($prevEvent->log_name) { $this->addPauseReason($summary[$userId]['pause_reasons'], $prevEvent->log_name, $duration); } } else { $summary[$userId]['LOGGED_IN'] += $duration; } } else { $this->addDurationToSummary($summary[$userId], $item->description, $duration, $item->log_name); } $summary[$userId]['total'] += $duration; } else { // This is the last action for the user $duration = Carbon::parse($item->created_at)->diffInSeconds($effectiveEndTime); if ($item->description == 'LOGGED_IN') { $prevEvent = $this->findPreviousEventForUser($data, $key, $userId); if ($prevEvent && ($prevEvent->description == 'Reachable' || $prevEvent->description == 'unpause')) { $summary[$userId]['Reachable'] += $duration; } elseif ($prevEvent && $prevEvent->description == 'pause') { $summary[$userId]['pause'] += $duration; // Add duration to pause reason if ($prevEvent->log_name) { $this->addPauseReason($summary[$userId]['pause_reasons'], $prevEvent->log_name, $duration); } } else { $summary[$userId]['LOGGED_IN'] += $duration; } } else { $this->addDurationToSummary($summary[$userId], $item->description, $duration, $item->log_name); } $summary[$userId]['total'] += $duration; } } foreach ($summary as &$userSummary) { $userSummary['LOGGED_IN_formatted'] = $this->secondsToTime($userSummary['LOGGED_IN']); $userSummary['pause_formatted'] = $this->secondsToTime($userSummary['pause']); $userSummary['Reachable_formatted'] = $this->secondsToTime($userSummary['Reachable']); $userSummary['total_formatted'] = $this->secondsToTime($userSummary['total']); // Clean empty arrays $userSummary['pause_reasons'] = array_filter($userSummary['pause_reasons']); } return $summary; } protected function findPreviousEventForUser($data, $currentIndex, $userId) { for ($i = $currentIndex - 1; $i >= 0; $i--) { $item = $data->get($i); if ($item->subject_id == $userId && $item->description != 'LOGGED_IN' && $item->description != 'LOGGED_OUT') { return $item; } } return null; } protected function addDurationToSummary(&$summary, $description, $duration, $reason = null) { if ($description == 'Reachable' || $description == 'unpause') { $summary['Reachable'] += $duration; } elseif ($description == 'pause') { $summary['pause'] += $duration; // Add duration to pause reason if exists if ($reason) { $this->addPauseReason($summary['pause_reasons'], $reason, $duration); } } elseif ($description == 'LOGGED_IN' || $description == 'Unreachable') { $summary['LOGGED_IN'] += $duration; } } protected function addPauseReason(&$reasonsArray, $reason, $duration) { if (!isset($reasonsArray[$reason])) { $reasonsArray[$reason] = [ 'reason' => $reason, 'duration' => 0, 'formatted_duration' => '00:00:00' ]; } $reasonsArray[$reason]['duration'] += $duration; $reasonsArray[$reason]['formatted_duration'] = $this->secondsToTime($reasonsArray[$reason]['duration']); } protected function secondsToTime($seconds) { $hours = floor($seconds / 3600); $minutes = floor(($seconds % 3600) / 60); $seconds = $seconds % 60; return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); } // New function to get all available pause reasons public function getAvailablePauseReasons() { $pauseReasons = DB::table('user_status') ->select('name', 'command', 'color', 'icon') ->where('command', 'pause') ->get(); // Get all reasons used in the system $usedReasons = DB::table('attendance') ->where('description_code', self::DESCRIPTION_CODES['pause']) ->whereNotNull('log_name') ->select('log_name as reason') ->distinct() ->get(); return [ 'available_reasons' => $pauseReasons, 'used_reasons' => $usedReasons ]; } // New function to get detailed pause reasons statistics public function getPauseReasonsStatistics(Request $request) { $conds = $this->SettingsHelper->defaultConds(); if ($request->has('daterange')) { $range = explode(' - ', $request->daterange); try { $conds['from'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[0])->format('Y-m-d H:i:s'); $conds['to'] = Carbon::createFromFormat('Y-m-d H:i:s', $range[1])->format('Y-m-d H:i:s'); } catch (\Exception $e) { $conds['from'] = Carbon::now()->startOfDay()->format('Y-m-d H:i:s'); $conds['to'] = Carbon::now()->endOfDay()->format('Y-m-d H:i:s'); } } $statistics = DB::table('attendance') ->join('users', 'users.id', '=', 'attendance.subject_id') ->whereBetween('attendance.created_at', [$conds['from'], $conds['to']]) ->where('description_code', self::DESCRIPTION_CODES['pause']) ->whereNotNull('log_name') ->select( 'log_name as reason', DB::raw('COUNT(*) as occurrence_count'), DB::raw('SUM(TIMESTAMPDIFF(SECOND, created_at, COALESCE( (SELECT created_at FROM attendance next WHERE next.subject_id = attendance.subject_id AND next.id > attendance.id AND next.description_code IN ('.self::DESCRIPTION_CODES['unpause'].', '.self::DESCRIPTION_CODES['Reachable'].') ORDER BY next.id ASC LIMIT 1), "'.Carbon::now()->format('Y-m-d H:i:s').'" ) )) as total_duration') ) ->groupBy('log_name') ->get(); // Convert duration to readable format foreach ($statistics as $stat) { $stat->formatted_duration = $this->secondsToTime($stat->total_duration); } return $statistics; } }