subMinutes(70); // $startTime = Carbon::now()->subDays(2); // Get all missed calls during this period $missedCallIds = DB::table('asteriskcdrdb.queuelog') ->select('callid') ->whereIn('event', ['ABANDON', 'EXITWITHTIMEOUT']) ->whereBetween('time', [$startTime, $endTime]) ->groupBy('callid') ->pluck('callid'); if ($missedCallIds->isEmpty()) { return; } // Filter to only callids not yet seen in Redis (2-hour deduplication window) $newCallIds = $missedCallIds->filter( fn ($callid) => ! Redis::exists("missed-calls:seen:{$callid}") ); if ($newCallIds->isEmpty()) { return; } foreach ($newCallIds as $callid) { // Mark as seen for 5 minutes so subsequent runs skip it Redis::setex("missed-calls:seen:{$callid}", 300, 1); // Dispatch Redis-buffered preparation job (same pattern as answered calls) PrepareMissedCallDataJob::dispatch($callid); // For immediate processing without Redis buffering, you could call analyzeSingleCall directly here: $this->analyzeSingleCall($callid); } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { Log::info('All missed calls have been analyzed successfully.'); } } public function analyzeSingleCall($callid) { // 1. Resolve the missed call from inbound_reports $missedCall = InboundReport::where('callid', $callid) ->whereIn('hangup_type', ['ABANDON', 'EXITWITHTIMEOUT']) ->first(); if (!$missedCall) { return; } $duration = $missedCall->ringtime ?? 0; // seconds (replaces SUM(data3) from queuelog) $startTime = Carbon::parse($missedCall->call_time)->format('Y-m-d H:i:s'); $endTime = Carbon::parse($missedCall->call_time)->addSeconds($duration)->format('Y-m-d H:i:s'); // 2. Per-agent ring attempt details — granular queuelog data with no equivalent // in inbound_reports, so queuelog is still used for these two queries only. $ringDetails = DB::table('asteriskcdrdb.queuelog') ->select('agent', 'event', 'data1', 'time') ->where('callid', $callid) ->whereIn('event', ['RINGNOANSWER', 'RINGCANCELED']) ->where('data1', '>', 1000) ->orderBy('time', 'asc') ->get(); $maxRingTime = DB::table('asteriskcdrdb.queuelog') ->where('callid', $callid) ->whereIn('event', ['RINGNOANSWER', 'RINGCANCELED']) ->whereRaw('CAST(data1 AS UNSIGNED) > 1000') ->max(DB::raw('CAST(data1 AS UNSIGNED)')); // 3. Concurrent missed calls — from inbound_reports $activeMissedCalls = InboundReport::whereIn('hangup_type', ['ABANDON', 'EXITWITHTIMEOUT']) ->where('callid', '!=', $callid) ->where('call_time', '>=', $startTime) ->where('call_time', '<=', $endTime) ->get(); // 4. Concurrent answered inbound calls — from inbound_reports $inboundCalls = InboundReport::whereIn('hangup_type', ['COMPLETECALLER', 'COMPLETEAGENT']) ->where('call_time', '<=', $endTime) ->whereRaw('DATE_ADD(call_time, INTERVAL (ringtime + talktime) SECOND) >= ?', [$startTime]) ->get(); // 5. Concurrent outbound calls — from outbound_reports $outboundCalls = OutboundReport::where('call_time', '<=', $endTime) ->whereRaw('DATE_ADD(call_time, INTERVAL duration SECOND) >= ?', [$startTime]) ->whereIn('disposition', ['ANSWERED', 'BUSY', 'NO ANSWER', 'FAILED']) ->get(); $ringDetailsCountVar = count($ringDetails); $missedCallCountVar = count($activeMissedCalls); $inboundCallsCountVar = count($inboundCalls); $outboundCallsCountVar = count($outboundCalls); if ($ringDetailsCountVar === 0 && $inboundCallsCountVar === 0 && $outboundCallsCountVar === 0 && $missedCallCountVar === 0 && $duration > 4 ) { $status = 'NO AGENT'; }else if ($ringDetailsCountVar === 0 && ($inboundCallsCountVar > 0 || $outboundCallsCountVar > 0 || $missedCallCountVar > 0)){ $status = 'AGENT BUSY'; }else if ($ringDetailsCountVar > 0 && $maxRingTime > 7000) { $status = 'NO ANSWER'; }else if ($ringDetailsCountVar > 0 && $maxRingTime <= 7000) { $status = 'NO TIME'; }else if ($duration <= 4){ $status = 'EXIST'; } DB::table('asteriskcdrdb.queuelog') ->where('callid', $callid) ->whereIn('event', ['ABANDON', 'EXITWITHTIMEOUT']) ->update(['status' => $status]); DB::table('inbound_reports') ->where('linkedid', $callid) ->update([ 'status' => $status, ]); } }