when($this->option('company-id'), function ($query) { $query->where('id', $this->option('company-id')); }) ->where('is_enabled', 1) ->get(); if ($companies->isEmpty()) { $this->info('No enabled companies found.'); return Command::SUCCESS; } foreach ($companies as $company) { $this->sendAlertForCompany($company); } return Command::SUCCESS; } private function sendAlertForCompany(Company $company) { $emailSetting = CompanySettings::get(SettingsHelper::S_MISSED_CALLS_ALERT_EMAIL, null, $company->id); $emails = SettingsHelper::splitMissedCallsAlertEmails($emailSetting); if (empty($emails)) { Log::info("Company {$company->id}: missed calls alert is disabled."); $this->line("Company {$company->id}: missed calls alert is disabled."); return; } if (!SettingsHelper::hasOnlyValidMissedCallsAlertEmails($emailSetting)) { Log::warning("Company {$company->id}: missed calls alert has invalid email recipients.", [ 'recipients' => $emailSetting, ]); $this->warn("Company {$company->id}: missed calls alert has invalid email recipients."); return; } $toEmail = array_shift($emails); $ccEmails = $emails; $minutes = (int) CompanySettings::get(SettingsHelper::S_MISSED_CALLS_ALERT_MINUTES, 10, $company->id); $minutes = max(1, $minutes ?: 10); // add 1 minute buffer to ensure we capture all calls in the window, even if the cron runs a bit late $to = Carbon::now()->addMinute(); $from = $to->copy()->subMinutes($minutes + 1); $missedCallsCount = $this->countMissedCalls($company->id, $from, $to); if ($missedCallsCount < 1) { $this->line("Company {$company->id}: no missed calls in the last {$minutes} minutes."); Log::info("Company {$company->id}: no missed calls in the last {$minutes} minutes."); return; } $daterange = $from->format('Y-m-d H:i:s') . ' - ' . $to->format('Y-m-d H:i:s'); $missedCallsUrl = route('reporting.missed', ['daterange' => $daterange]); $companyLogoUrl = $this->companyLogoUrl($company->id); $hatifLogoUrl = asset(config('bap.login_logo')); // bap/logo/Logo Hatif.png $powerLogoUrl = asset('bap/logo/power.png'); // Powered By EGYtel $missedCallsData = $this->getMissedCallsData($company->id, $from, $to); Mail::send('emails.missed-calls-alert', [ 'companyName' => CompanySettings::get(SettingsHelper::S_COMPANY_NAME, $company->name, $company->id) ?: $company->name, 'companyLogoUrl' => $companyLogoUrl, 'hatifLogoUrl' => $hatifLogoUrl, 'powerLogoUrl' => $powerLogoUrl, 'missedCallsCount' => $missedCallsCount, 'minutes' => $minutes, 'from' => $from, 'to' => $to, 'missedCallsUrl' => $missedCallsUrl, 'missedCallsData' => $missedCallsData, ], function ($message) use ($toEmail, $ccEmails, $company, $missedCallsCount, $minutes) { $message->from(config('mail.from.address'), config('mail.from.name')); $message->to($toEmail); if (!empty($ccEmails)) { $message->cc($ccEmails); } $message->subject("{$missedCallsCount} missed calls in the last {$minutes} minutes - {$company->name}"); }); Log::info('Missed calls alert email sent', [ 'company_id' => $company->id, 'to' => $toEmail, 'cc' => $ccEmails, 'minutes' => $minutes, 'missed_calls' => $missedCallsCount, ]); $ccList = empty($ccEmails) ? 'none' : implode(', ', $ccEmails); $this->info("Company {$company->id}: sent missed calls alert to {$toEmail}; cc: {$ccList}."); Log::info("Company {$company->id}: sent missed calls alert to {$toEmail}; cc: {$ccList}."); } private function getMissedCallsData($companyId, Carbon $from, Carbon $to): \Illuminate\Support\Collection { $queueIds = Group::where('company_id', $companyId)->pluck('id'); if ($queueIds->isEmpty()) { return collect(); } $missedCallIds = DB::table('asteriskcdrdb.queuelog') ->whereIn('event', ['ABANDON', 'EXITWITHTIMEOUT']) ->whereBetween('time', [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]) ->whereIn('queuename', $queueIds) ->whereNotIn('callid', function ($query) { $query->select('callid') ->from('asteriskcdrdb.queuelog') ->whereIn('event', ['COMPLETECALLER', 'COMPLETEAGENT']); }) ->distinct('callid') ->pluck('callid'); if ($missedCallIds->isEmpty()) { return collect(); } $rows = DB::table('asteriskcdrdb.queuelog as q') ->select([ 'q.callid', 'q.data2 as caller_number', 'q.time', 'q.queuename', 'qc.descr as queue_name', 'q.data5', ]) ->join('asterisk.queues_config as qc', 'qc.extension', '=', 'q.queuename') ->where('q.event', 'ENTERQUEUE') ->whereIn('q.callid', $missedCallIds) ->get(); $reviewerIds = $rows->whereNotNull('data5')->where('data5', '!=', '')->pluck('data5')->unique()->filter(); $reviewers = User::whereIn('id', $reviewerIds)->pluck('name', 'id'); return $rows ->groupBy('caller_number') ->map(function ($items, $callerNumber) use ($reviewers) { $latest = $items->sortByDesc('time')->first(); $reviewerEntry = $items->first(fn ($i) => !empty($i->data5)); $reviewerName = $reviewerEntry ? ($reviewers[$reviewerEntry->data5] ?? 'Someone') : null; return (object) [ 'caller_number' => $callerNumber, 'last_time' => Carbon::parse($latest->time)->format('Y-m-d h:i:s A'), 'queue_name' => $latest->queue_name, 'count' => $items->count(), 'reviewed' => !empty($reviewerEntry), 'reviewer_name' => $reviewerName, ]; }) ->sortByDesc(fn ($row) => $row->count) ->values(); } private function countMissedCalls($companyId, Carbon $from, Carbon $to) { $queueIds = Group::where('company_id', $companyId)->pluck('id'); if ($queueIds->isEmpty()) { return 0; } return DB::table('asteriskcdrdb.queuelog') ->whereIn('event', ['ABANDON', 'EXITWITHTIMEOUT']) ->whereBetween('time', [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]) ->whereIn('queuename', $queueIds) ->whereNotIn('callid', function ($query) { $query->select('callid') ->from('asteriskcdrdb.queuelog') ->whereIn('event', ['COMPLETECALLER', 'COMPLETEAGENT']); }) ->distinct('callid') ->count('callid'); } private function companyLogoUrl($companyId) { $logoPath = CompanySettings::get(SettingsHelper::S_DISPLAY_LOGO_UPLOAD, null, $companyId); if ($logoPath && file_exists(public_path($logoPath))) { return asset($logoPath); } return null; } }