run(); }); Artisan::command( 'agihangupaction {DialRetries} {LeadId} {CampaignID} {IsDialer} {IsSurvey} {IsSurveyDial} {CustomerChoise} {dstchannel} {linkedid} {hangupcause} {src} {dest} {State} {calldate} {src_cdr} {dst_cdr} {dcontext} {lastapp} {duration} {billsec} {disposition} {cnum} {cnam} {dialer_call_id?}', function ( $DialRetries = null, $LeadId = null, $CampaignID = null, $IsDialer = null, $IsSurvey = null, $IsSurveyDial = null, $CustomerChoise = null, $dstchannel = null, $linkedid = NULL, $hangupcause = NULL, $src = NULL, $dest = NULL, $State = NULL, $calldate = NULL, $src_cdr = NULL, $dst_cdr = NULL, $dcontext = NULL, $lastapp = NULL, $duration = NULL, $billsec = NULL, $disposition = NULL, $cnum = NULL, $cnam = NULL, $dialer_call_id = NULL ) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug(PHP_EOL . "Console file agihangupaction script started" . PHP_EOL); } // get or set the call Status $status = $State . " - " . $disposition; $call_status = \DB::table('calls_dict_status') ->select('id', 'name') ->where('name', $status) ->first(); if (!$call_status) { // Create a new row with the given status $call_status_Id = \DB::table('calls_dict_status')->insertGetId([ 'name' => $status ]); $call_status = \DB::table('calls_dict_status')->select('id', 'name')->find($call_status_Id); } $call = new Call; if ($dcontext === 'trunk-dial-with-exten' || $lastapp === 'Busy') { $direction_text = 'Outgoing'; $call->phone_number = $dest; $call->owned_by_id = $cnum; $call->owned_by_type = 'Modules\Platform\User\Entities\User'; } else { $direction_text = 'Incoming'; $dstchannel = Str::between($dstchannel, "Local/", "@"); $call->phone_number = $src; $call->owned_by_id = $dstchannel; $call->owned_by_type = 'Modules\Platform\User\Entities\User'; } $call->company_id = 1; $direction_id = \DB::table('calls_dict_direction') ->select('id') ->where('company_id', $call->company_id) ->where('name', $direction_text) ->first(); if (!is_null($direction_id)) { $call->direction_id = $direction_id->id; } else { $call->direction_id = null; } $call->duration = $duration; $call->talktime = $billsec; $call->ringtime = (int)$duration - $billsec; $call->linkedid = $linkedid; $call->call_date = $calldate; $call->contact_id = NULL; $call->lead_id = NULL; $call->account_id = NULL; $possible_contact_id = Contact::where('phone', $call->phone_number)->orWhere('mobile', $call->phone_number)->first(); if (!is_null($possible_contact_id)) { $call->contact_id = $possible_contact_id->id; } $possible_lead_id = Lead::where('phone', $call->phone_number) ->orWhere('mobile', $call->phone_number) ->first(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("test3"); } if ($IsDialer && !$IsSurvey && !$IsSurveyDial) { // ── API-initiated call (dialer_call_id set, no campaign lead) ────── if ($dialer_call_id) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("agihangupaction: API call ended for dialer_call_id={$dialer_call_id}, linkedid={$linkedid}, disposition={$disposition}"); } $json_data = escapeshellarg(json_encode([ 'dialer_call_id' => $dialer_call_id, 'linkedId' => $linkedid, ])); exec("/var/www/ccsystem/artisan callbackWebhook {$json_data} > /dev/null 2>&1 &"); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("agihangupaction: callbackWebhook dispatched for dialer_call_id={$dialer_call_id}"); } } // ── Campaign dialer call ────────────────────────────────────────── if ($LeadId) { $call->is_dialer = 1; $call->campaign_id = $CampaignID; $call->lead_id = $LeadId; $call->dial_retries = ((int) $DialRetries) + 1; \DB::table('campaign_lead') ->where('lead_id', $LeadId)->where('campaign_id', $CampaignID) ->update([ 'connection_status' => $call_status->id, 'updated_at' => Carbon::now(), ]); } } elseif (!is_null($possible_lead_id)) { $call->lead_id = $possible_lead_id->id; } if ($call_status) { $call->status_id = $call_status->id; } else { $call->status_id = NULL; } $call->owned_by_type = 'Modules\Platform\User\Entities\User'; if ($call->save()) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { echo 'yes ' . PHP_EOL; \Log::debug([ 'src' => $src, 'dstchannel' => $dstchannel, 'dest' => $dest, 'State' => $State, 'linkedid' => $linkedid, 'direction_text' => $direction_text, 'hangupcause' => $hangupcause, 'calldate' => $calldate, 'src_cdr' => $src_cdr, 'dst_cdr' => $dst_cdr, 'dcontext' => $dcontext, 'lastapp' => $lastapp, 'duration' => $duration, 'billsec' => $billsec, 'disposition' => $disposition, 'cnum' => $cnum, 'cnam' => $cnam, 'status' => $status, 'status_id' => $call_status->id, 'LeadId' => $call->lead_id, 'CampaignID' => $CampaignID, 'IsDialer' => $IsDialer, 'DialRetries' => $DialRetries, 'CustomerChoise' => $CustomerChoise, ]); } if ($direction_text == 'Outgoing') { $resetTime = CompanySettings::get('dashboard_reset_time', '08:00'); list($resetHour, $resetMinute) = explode(':', $resetTime); $now = Carbon::now(); if ($now->hour < (int)$resetHour || ($now->hour == (int)$resetHour && $now->minute < (int)$resetMinute)) { $currentDay = Carbon::yesterday()->format('Y-m-d'); } else { $currentDay = Carbon::today()->format('Y-m-d'); } $totalToday = Redis::incr("calls:{$call->owned_by_id}:{$currentDay}:outgoing"); $statusToday = Redis::incr("calls:{$call->owned_by_id}:{$currentDay}:status:{$disposition}"); // Real-time outbound report: 5-second delay lets Asterisk finish writing CDR. // No Redis — idempotency is handled by the outbound_reports table. ProcessOutboundCallJob::dispatch($linkedid) ->delay(now()->addSeconds(5)); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("📅 CustomDay => {$currentDay} | Agent: {$call->owned_by_id} | Total: {$totalToday} | {$disposition}: {$statusToday}"); } } else { // Direct-extension calls (CDR call path: dcontext=followme-check or // dial-with-exten, lastapp=Dial) go to DirectCallsService; queue // calls keep the existing pipeline. The service re-validates the // path from CDR. $isDirectExtensionCall = ($lastapp === 'Dial' && in_array($dcontext, ['followme-check', 'dial-with-exten', 'dialOne-with-exten'], true)); if ($disposition == 'ANSWERED' && $dstchannel != '') { if ($isDirectExtensionCall) { ProcessDirectCallJob::dispatch($linkedid) ->delay(now()->addSeconds(5)); } else { // Single-job pipeline: loads data once, computes in memory, writes once. // No Redis payload. 5-second delay lets Asterisk finish writing CDR/queuelog. ProcessInboundCallJob::dispatch($linkedid) ->delay(now()->addSeconds(5)); } } else { if ($isDirectExtensionCall) { ProcessDirectCallJob::dispatch($linkedid) ->delay(now()->addSeconds(5)); } else { // Mark as seen for 5 minutes so subsequent runs skip it Redis::setex("missed-calls:seen:{$linkedid}", 300, 1); // Dispatch Redis-buffered preparation job (same pattern as answered calls) PrepareMissedCallDataJob::dispatch($linkedid) ->delay(now()->addSeconds(5)); } } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug(PHP_EOL . "Console file agihangupaction script Ended" . PHP_EOL); } return true; } return false; dd($call); } )->describe('Display an inspiring quote'); Artisan::command('viceMailHandler', function () { $path = '/var/spool/asterisk/voicemail/default/1000/INBOX'; $processedPath = '/var/spool/asterisk/voicemail/default/1000/processed'; if (!is_dir($processedPath)) { mkdir($processedPath, 0777, true); } $voicemailFiles = glob($path . '/*'); foreach ($voicemailFiles as $file) { if (pathinfo($file, PATHINFO_EXTENSION) === 'txt') { $txtContent = file_get_contents($file); $txtLines = explode("\n", $txtContent); $callerid = ''; $source = ''; $origdate = ''; $duration = ''; foreach ($txtLines as $line) { // Extract phone number between < > if (preg_match('/<([^>]+)>/', $line, $matches)) { $callerid = trim($matches[1]); // Keep only digits and optional leading + $callerid = preg_replace('/[^\d+]/', '', $callerid); } // Extract full callerid text between callerid=" and " if (preg_match('/callerid="([^"<]+)"/', $line, $matches)) { $source = trim($matches[1]); // e.g. bahaa test -0221282769 // Split at last '-' $lastDashPos = strrpos($source, '-'); if ($lastDashPos !== false) { $source = trim(substr($source, 0, $lastDashPos)); // get text before last '-' } } // Extract origdate if (strpos($line, 'origdate=') !== false) { $origdate = substr($line, strpos($line, '=') + 1); $origdate = Carbon::parse($origdate, 'UTC') ->setTimezone(config('app.timezone')) ->format('Y-m-d H:i:s'); } // Extract duration if (strpos($line, 'duration=') !== false) { $duration = substr($line, strpos($line, '=') + 1); } } $safeCallerId = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $callerid); $origdateNoSpaces = str_replace(' ', '', $origdate); $safeOrigDate = preg_replace('/[^0-9\-:]/', '_', $origdateNoSpaces); $safeOrigDate = str_replace(':', '-', $safeOrigDate); $newFileName = "{$safeCallerId}_{$safeOrigDate}"; try { $timeNow = Carbon::now(config('app.timezone')); DB::insert("INSERT INTO voicemails (phone_number, source, duration, call_date, record, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [ $callerid, $source, $duration, $origdate, $newFileName . '.wav', $timeNow, $timeNow ]); } catch (\Throwable $th) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { Log::error($th); } continue; } $wavFile = str_replace('.txt', '.wav', $file); if (file_exists($wavFile)) { rename($wavFile, $processedPath . '/' . $newFileName . '.wav'); } rename($file, $processedPath . '/' . $newFileName . '.txt'); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { Log::debug("File processed successfully: $newFileName"); } } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { Log::debug("Voicemail files processed successfully."); } })->describe('Voicemail'); Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->describe('Display an inspiring quote'); Artisan::command('subdomain:deploy {subdomain}', function ($subdomain) { if (!file_exists('/etc/apache2/sites-available/' . $subdomain . '.conf')) { $subdomain_file_contenets = ' ServerAdmin "info@' . $subdomain . '" DocumentRoot "/var/www/ccsystem/" ServerName "' . $subdomain . '" Options -Indexes AllowOverride All Require all granted ErrorLog /var/www/error.log.ccsystem CustomLog /var/www/access.log.ccsystem combined '; file_put_contents('/etc/apache2/sites-available/' . $subdomain . '.conf', $subdomain_file_contenets); $process = new Process(['a2ensite', $subdomain . '.conf']); $process->run(); $process = new Process(['service', 'apache2', 'restart']); $process->run(); } })->describe('Display an inspiring quote'); Artisan::command('telemarketing:shoot', function () { $dialer_concurrent_campaigns = CompanySettings::get('dialer_concurrent_campaigns') ?? 1; $maximum_channels = CompanySettings::get('maximum_channels') ?? 0; $day_starting_time = CompanySettings::get('day_starting_time'); $day_ending_time = CompanySettings::get('day_ending_time'); $total_dialed_calls = 0; try { $currentTime = Carbon::now(); $startTime = $day_starting_time ? Carbon::parse($day_starting_time) : Carbon::parse('09:00:00'); $endTime = $day_ending_time ? Carbon::parse($day_ending_time) : Carbon::parse('18:00:00'); if ($currentTime->between($startTime, $endTime)) { $call_status_new = (int) \DB::table('calls_dict_status')->where('name', 'New')->value('id'); $call_status_dialed = (int) \DB::table('calls_dict_status')->where('name', 'Dialed')->value('id'); $campaigns = Campaign::where([ ['campaign_status_id', '=', 2], ['expected_close_date', '>=', date('Y-m-d')], ['campaign_type_id', '=', 3], ['owned_by_type', '=', 'Modules\Platform\User\Entities\Group'], ['deleted_at', '=', null] ]) ->with(['campaign_lead' => function ($query) { $query->leftJoin('leads', 'leads.id', '=', 'campaign_lead.lead_id') ->leftJoin('calls_dict_status', 'calls_dict_status.id', '=', 'campaign_lead.connection_status') ->select( 'campaign_lead.lead_id', 'campaign_lead.campaign_id', 'campaign_lead.connection_status', 'campaign_lead.dial_retries', 'leads.phone', 'leads.mobile' ) ->whereNotIn('calls_dict_status.name', ['Up - ANSWERED', 'Processing', 'Done']) ->where(function ($query) { $query->where(function ($q) { $q->whereNotNull('leads.phone') ->whereRaw('CHAR_LENGTH(leads.phone) >= 10'); })->orWhere(function ($q) { $q->whereNotNull('leads.mobile') ->whereRaw('CHAR_LENGTH(leads.mobile) >= 10'); }); }); }]) ->orderBy('campaign_priority', 'DESC') ->limit($dialer_concurrent_campaigns) ->get(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Start AutoDialer"); } if ($campaigns->count()) { $astman = new \App\Http\Controllers\AstmanController(); foreach ($campaigns as $campaign) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Campaign: {$campaign->name}"); } $queue = $campaign->owned_by_id; $call_per_agent = (int) ($campaign->expected_sales_count ?? 1); $expected_response_count = (int) ($campaign->expected_response_count ?? 1); $user_ids = \DB::table('group_user') ->where('group_id', $queue) ->pluck('user_id'); if ($campaign->check_users_availability) { $avail_agents = 0; foreach ($user_ids as $user_id) { if ($astman->isExtensionFree($user_id)) { $avail_agents++; } } if ($avail_agents === 0) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("No available agents"); } continue; } $calls_count = $call_per_agent * $avail_agents; } else { $calls_count = $maximum_channels; } $validLeads = $campaign->campaign_lead ->filter(function ($lead) use ($expected_response_count) { return (int) $lead->dial_retries < $expected_response_count; }) ->sortBy('dial_retries') ->take($calls_count) ->values(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Valid leads: " . count($validLeads)); } // Resolve which lines to use for this campaign $campaignLines = $campaign->line ?? []; if (!is_array($campaignLines)) { $campaignLines = [$campaignLines]; } if (in_array('all', $campaignLines)) { $dialerLinesCount = (int) CompanySettings::get('dialer_lines', 1); $campaignLines = array_map('strval', range(1, $dialerLinesCount)); } $campaignLines = array_filter(array_values($campaignLines)); $useLines = count($campaignLines) > 0; $lineCount = $useLines ? count($campaignLines) : 1; // Load persisted rotation index from Redis (TTL 3 min) $lineIndexKey = "dialer:campaign:{$campaign->id}:line_index"; $leadIndex = (int) \Cache::get($lineIndexKey, 0); foreach ($validLeads as $lead) { if ($total_dialed_calls >= $maximum_channels) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Max channels reached"); } break 2; } $num = !empty($lead->phone) ? $lead->phone : $lead->mobile; if (!$num) { continue; } $num = PhoneNormalizer::toLocal( $num, config('sms.country_code', '20'), config('sms.trunk_prefix', '0') ); if (!$num) { continue; } // Apply line prefix (alternates across selected lines) if ($useLines) { $lineNum = $campaignLines[$leadIndex % $lineCount]; $num = $lineNum . $num; } $leadIndex++; \Cache::put($lineIndexKey, $leadIndex, 180); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Dialing: $num"); } try { $options = [ 'host' => config('pbx.ami.host'), 'scheme' => config('pbx.ami.scheme'), 'port' => config('pbx.ami.port'), 'username' => config('pbx.ami.username'), 'secret' => config('pbx.ami.secret'), 'connect_timeout' => 3, 'read_timeout' => 5, ]; $a = new ClientImpl($options); $a->open(); $actionid = uniqid(); $originate = new OriginateAction("local/$num@from-internal"); $originate->setContext('ext-did'); $originate->setExtension($queue); $originate->setPriority(1); $originate->setCallerId(config('app.didNum')); $originate->setAsync(true); $originate->setActionID($actionid); $originate->setVariable('LeadId', $lead->lead_id); $originate->setVariable('CampaignID', $lead->campaign_id); $originate->setVariable('IsDialer', true); $a->send($originate); $a->close(); // update lead $status_id = ((int)$lead->connection_status === $call_status_new) ? $call_status_dialed : (int)$lead->connection_status; \DB::table('campaign_lead') ->where('lead_id', $lead->lead_id) ->where('campaign_id', $lead->campaign_id) ->update([ 'dial_retries' => ((int)$lead->dial_retries) + 1, 'connection_status' => $status_id, 'updated_at' => now(), ]); $total_dialed_calls++; if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Dialed OK | Total: $total_dialed_calls"); } } catch (\Exception $e) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("Dial error: " . $e->getMessage()); } } sleep(1); } } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("No active campaigns"); } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("AutoDialer completed"); } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Out of allowed time"); } } } catch (\Exception $e) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error($e->getMessage()); } } })->describe('Auto dialer'); Artisan::command('survey-allocate', function () { try { $data = \DB::table('surveys') ->where('linkedid', '') ->whereNull('campaign_id') ->where("created_at", '>=', Carbon::now()->subHours(5)) ->get(); foreach ($data as $survey) { $linkedId = \DB::table('asteriskcdrdb.cdr') ->where('uniqueid', $survey->uniqueid) ->where('calldate', '>=', Carbon::now()->subHours(2)) ->value('linkedid'); if ($survey->is_dialer) { $phoneNumber = \DB::table('calls') ->where('linkedid', $linkedId) ->value('phone_number'); $latestCall = DB::table('asteriskcdrdb.queuelog') ->select('queuelog.id', 'queuelog.time', 'queuelog.callid') ->join('asteriskcdrdb.cdr', 'cdr.uniqueid', '=', 'queuelog.callid') ->leftJoin(DB::raw(" ( SELECT * FROM ( SELECT callid, time AS enter_time, queuename AS enter_queuename, ROW_NUMBER() OVER (PARTITION BY callid ORDER BY time ASC, id ASC) AS rn FROM asteriskcdrdb.queuelog WHERE event = 'ENTERQUEUE' ) AS ranked WHERE rn = 1 ) AS enterqueue "), 'enterqueue.callid', '=', 'queuelog.callid') ->leftJoin('asterisk.queues_config', 'queues_config.extension', '=', DB::raw('enterqueue.enter_queuename')) ->whereIn('queuelog.event', ['COMPLETECALLER', 'COMPLETEAGENT']) ->where('queuelog.time', '>=', Carbon::now()->subHours(2)) ->where('cdr.disposition', 'ANSWERED') ->where('cdr.src', $phoneNumber) ->orderByDesc('queuelog.time') ->where('queuelog.survey', 'Cancel') ->limit(1) ->first(); if ($latestCall) { DB::table('calls') ->where('linkedid', $latestCall->callid) ->update(['survey_choice' => $survey->customer_choice]); DB::table('inbound_reports') ->where('linkedid', $latestCall->callid) ->update(['survey' => $survey->customer_choice]); DB::table('surveys') ->where('id', $survey->id) ->update([ 'linkedid' => $latestCall->callid ]); } } else { DB::table('calls') ->where('linkedid', $linkedId) ->update([ 'survey_choice' => $survey->customer_choice, 'is_survey' => 1, ]); DB::table('inbound_reports') ->where('linkedid', $linkedId) ->update(['survey' => $survey->customer_choice]); DB::table('surveys') ->where('id', $survey->id) ->update([ 'linkedid' => $linkedId ]); } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Survey Auto-dialer completed."); } echo PHP_EOL . "Survey Auto-dialer completed." . PHP_EOL; } catch (Exception $e) { echo $e->getMessage() . "\n"; } })->describe('survey AuotDiler'); Artisan::command('survey', function () { try { $currentTime = Carbon::now(); $data = DB::table('asteriskcdrdb.queuelog') ->select( 'queuelog.id', 'cdr.src', 'queuelog.survey', 'queuelog.callid', 'queuelog.time', ) ->join('asteriskcdrdb.cdr', 'cdr.uniqueid', '=', 'queuelog.callid') ->leftJoin(DB::raw(" ( SELECT * FROM ( SELECT callid, time AS enter_time, queuename AS enter_queuename, ROW_NUMBER() OVER (PARTITION BY callid ORDER BY time ASC, id ASC) AS rn FROM asteriskcdrdb.queuelog WHERE event = 'ENTERQUEUE' ) AS ranked WHERE rn = 1 ) AS enterqueue "), 'enterqueue.callid', '=', 'queuelog.callid') ->leftJoin('asterisk.queues_config', 'queues_config.extension', '=', DB::raw('enterqueue.enter_queuename')) ->whereIn('queuelog.event', ['COMPLETECALLER', 'COMPLETEAGENT']) ->whereBetween('queuelog.time', [Carbon::today(), Carbon::now()->subMinutes(29)]) ->where('queuelog.survey', '') ->where('cdr.disposition', 'ANSWERED') ->get(); // open ClientImpl $options = array( 'host' => config('pbx.ami.host'), 'scheme' => config('pbx.ami.scheme'), 'port' => config('pbx.ami.port'), 'username' => config('pbx.ami.username'), 'secret' => config('pbx.ami.secret'), 'connect_timeout' => config('pbx.ami.connect_timeout'), 'read_timeout' => config('pbx.ami.read_timeout'), ); $a = new ClientImpl($options); // $eventListener = new TestEv($a); // $a->registerEventListener($eventListener); $a->open(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Start Survey AuotDiler"); echo PHP_EOL . "Starting Survey auto-dialer..." . PHP_EOL; } if ($data->count() >= 1) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug(" Survey AuotDiler Runinng: "); } // $queue = $campaign->owned_by_id; // $call_per_agent = $campaign->expected_sales_count ?? 1; // $user_idS = \DB::table('group_user')->where('group_id', $queue)->pluck('user_id')->toArray(); //get avail Agents // $data = Http::get('http://localhost/api/webform/getAvailAgent'); // $agents = array_values($data['agents']); // $avail_agents_in_queue = count(array_filter($agents, function ($agent) use ($user_idS) { // return $agent['status']['status'] == 'Avail' && in_array($agent['extension'], $user_idS); // })); $calls_count = 1; for ($i = 0; $i < $calls_count; $i++) { $lead = $data[$i]; $num = $lead->src; echo PHP_EOL . "lead num: $num" . PHP_EOL; $actionid = md5(uniqid()); $response = $a->send(new StatusAction()); $originateMsg = new OriginateAction("local/$num@from-internal"); $originateMsg->setContext('ext-did'); $originateMsg->setExtension('8888'); $originateMsg->setPriority('1'); $originateMsg->setCallerId(config('app.didNum')); $originateMsg->setAsync(true); $originateMsg->setActionID($actionid); $originateMsg->setVariable('LeadId', null); $originateMsg->setVariable('CampaignID', null); $originateMsg->setVariable('IsDialer', null); $originateMsg->setVariable('IsSurveyDial', true); $originateMsg->setVariable('DialRetries', null); $orgresp = $a->send($originateMsg); $orgStatus = $orgresp->getKeys()['response']; if (!empty($orgStatus) && $orgStatus == 'Success') { DB::table('asteriskcdrdb.queuelog') ->where('id', $lead->id) ->update(['survey' => 'NO ANSWER']); DB::table('inbound_reports') ->where('linkedid', $lead->callid) ->update([ 'survey' => 'NO ANSWER', ]); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("num = $num dialed"); } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("num = $num not dialed $orgStatus"); } } } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("0 Survey AuotDiler Active!"); } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Survey Auto-dialer completed."); echo PHP_EOL . "Survey Auto-dialer completed." . PHP_EOL; } $a->close(); } catch (Exception $e) { echo $e->getMessage() . "\n"; } })->describe('survey AuotDiler'); Artisan::command('survey:shoot', function () { $dialer_concurrent_campaigns = CompanySettings::get('dialer_concurrent_campaigns') ?? 1; $maximum_channels = CompanySettings::get('maximum_channels') ?? 0; $day_starting_time = CompanySettings::get('day_starting_time'); $day_ending_time = CompanySettings::get('day_ending_time'); try { $currentTime = Carbon::now(); $startTime = $day_starting_time ? Carbon::parse($day_starting_time) : Carbon::parse('09:00:00'); $endTime = $day_ending_time ? Carbon::parse($day_ending_time) : Carbon::parse('18:00:00'); if ($currentTime->between($startTime, $endTime)) { $call_status_new = (int) \DB::table('calls_dict_status')->where('name', 'New')->value('id'); $call_status_dialed = (int) \DB::table('calls_dict_status')->where('name', 'Dialed')->value('id'); $campaigns = Campaign::where([ ['campaign_status_id', '=', 2], ['expected_close_date', '>=', date('Y-m-d')], ['campaign_type_id', '=', 13], ['deleted_at', '=', null] ]) ->with(['campaign_lead' => function ($query) { $query->leftJoin('leads', 'leads.id', '=', 'campaign_lead.lead_id') ->leftJoin('calls_dict_status', 'calls_dict_status.id', '=', 'campaign_lead.connection_status') ->select( 'campaign_lead.lead_id', 'campaign_lead.campaign_id', 'campaign_lead.connection_status', 'campaign_lead.dial_retries', 'leads.phone', 'leads.mobile' ) ->whereNotIn('calls_dict_status.name', ['Up - ANSWERED', 'Processing', 'Done']) ->where(function ($query) { $query->where(function ($q) { $q->whereNotNull('leads.phone') ->whereRaw('CHAR_LENGTH(leads.phone) >= 10'); })->orWhere(function ($q) { $q->whereNotNull('leads.mobile') ->whereRaw('CHAR_LENGTH(leads.mobile) >= 10'); }); }); }]) ->orderBy('campaign_priority', 'DESC') ->limit($dialer_concurrent_campaigns) ->get(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Start AutoDialer"); } if ($campaigns->count()) { $astman = new \App\Http\Controllers\AstmanController(); foreach ($campaigns as $campaign) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Campaign: {$campaign->name}"); } $queue = $campaign->owned_by_id; $call_per_agent = (int) ($campaign->expected_sales_count ?? 1); $expected_response_count = (int) ($campaign->expected_response_count ?? 1); $user_ids = \DB::table('group_user') ->where('group_id', $queue) ->pluck('user_id'); $calls_count = $maximum_channels; $validLeads = $campaign->campaign_lead ->filter(function ($lead) use ($expected_response_count) { return (int) $lead->dial_retries < $expected_response_count; }) ->sortBy('dial_retries') ->take($calls_count) ->values(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Valid leads: " . count($validLeads)); } foreach ($validLeads as $lead) { $num = !empty($lead->phone) ? $lead->phone : $lead->mobile; if (!$num) { continue; } $num = PhoneNormalizer::toLocal( $num, config('sms.country_code', '20'), config('sms.trunk_prefix', '0') ); if (!$num) { continue; } $num = ($campaign->product ?? '') . $num; if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Dialing: $num"); } try { $options = [ 'host' => config('pbx.ami.host'), 'scheme' => config('pbx.ami.scheme'), 'port' => config('pbx.ami.port'), 'username' => config('pbx.ami.username'), 'secret' => config('pbx.ami.secret'), 'connect_timeout' => 3, 'read_timeout' => 5, ]; $a = new ClientImpl($options); $a->open(); $actionid = uniqid(); $originate = new OriginateAction("local/$num@from-internal"); $originate->setContext('ext-did'); $originate->setExtension('8888'); $originate->setPriority(1); $originate->setCallerId(config('app.didNum')); $originate->setAsync(true); $originate->setActionID($actionid); $originate->setVariable('LeadId', $lead->lead_id); $originate->setVariable('CampaignID', $lead->campaign_id); $originate->setVariable('IsDialer', true); $a->send($originate); $a->close(); // update lead $status_id = ((int)$lead->connection_status === $call_status_new) ? $call_status_dialed : (int)$lead->connection_status; \DB::table('campaign_lead') ->where('lead_id', $lead->lead_id) ->where('campaign_id', $lead->campaign_id) ->update([ 'dial_retries' => ((int)$lead->dial_retries) + 1, 'connection_status' => $status_id, 'updated_at' => now(), ]); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Dialed OK | Total: $calls_count"); } } catch (\Exception $e) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("Dial error: " . $e->getMessage()); } } sleep(1); } } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("No active campaigns"); } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("AutoDialer completed"); } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Out of allowed time"); } } } catch (\Exception $e) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error($e->getMessage()); } } })->describe('Auto dialer'); Artisan::command('get_outgoing_files', function () { $files = File::files('/var/spool/asterisk/outgoing_done'); $zipFile = date('Y-d-m_H-i-s') . '.zip'; $zip = new ARCH(); // Open zip if ($zip->open('/var/spool/asterisk/outgoing_done_completed/' . $zipFile, ARCH::CREATE) === TRUE) { foreach ($files as $file) { // Get file info, name, and date. $filename = $file->getFilename(); $filename_array = explode('-', $filename); $phone = $filename_array[1]; $date = substr($filename_array[2], 0, strlen($filename_array[2]) - 5); // $formated_date = date('Y-d-m H:i:s',$date); // Get file contents and convert them to encoded json $contents = File::get($file); $contents = preg_split('/\n/', $contents); $contents = array_filter($contents); $contents_collection = collect(); foreach ($contents as $content) { $element = explode(':', $content); $contents_collection[$element[0]] = $element[1]; } $campaign_id = explode('=', (explode('&', $contents_collection['Setvar'])[1]))[1]; $campaign_id = str_replace('>', '', $campaign_id); // If the file does not exist, insert it in the database. $exists = DB::table('outgoing_done')->where('filename', $filename)->first(); if (!$exists) { DB::table('outgoing_done')->insert([ 'filename' => $filename, 'phone' => $phone, 'call_date' => $date, 'campaign_id' => $campaign_id, 'content' => json_encode($contents_collection) ]); // Add the inserted file to the zip $zip->addFile($file, $filename); // Delete the file after inserting it in the database. // File::delete('/var/spool/asterisk/outgoing_done/'.$filename); } else { continue; } } // Close zip $zip->close(); } })->describe('Display an inspiring quote'); // allocate to queue Artisan::command('allocate', function () { $exclude = DB::table('asterisk.incoming') ->select('cidnum') ->groupBy('cidnum') ->get() ->pluck('cidnum'); // dd($exclude); $data = DB::table('asteriskcdrdb.queuelog') ->select( 'asterisk.queues_config.extension', 'cdr.src' ) ->join('asteriskcdrdb.cdr', 'asteriskcdrdb.cdr.uniqueid', 'asteriskcdrdb.queuelog.callid') ->leftJoin('asterisk.queues_config', 'asterisk.queues_config.extension', 'queuelog.queuename') ->whereNotIn('cdr.src', $exclude) ->whereNotIn('cdr.src', [ '01002507050', '01110555281', // '01118925708', // '01157479852', // config('app.didNum'), ]) ->whereDate('time', today()->format('Y-m-d')) ->whereTime('time', '>', now()->subHour()->format('H:i:s')) ->groupBy('cdr.src') ->orderBy('time', 'DESC') ->limit(100) ->get() ->toArray(); $insertions = []; foreach ($data as $one) { $insertions[] = [ 'cidnum' => $one->src, 'extension' => config('app.didNum'), 'destination' => 'ext-queues,' . $one->extension . ',1', 'privacyman' => '0', 'alertinfo' => '', 'ringing' => '', 'fanswer' => '', 'mohclass' => 'default', 'description' => 'Auto Direct To Queue', 'grppre' => '', 'delay_answer' => '0', 'pricid' => '', 'pmmaxretries' => '3', 'pmminlength' => '10', 'reversal' => '', 'rvolume' => '0', 'indication_zone' => 'default', ]; } if (!empty($insertions)) { DB::table('asterisk.incoming')->insert($insertions); $process = new Process(['fwconsole', 'reload']); $process->run(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug('allocate run'); \Log::debug($data); } // dd($data); } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug('allocate run'); \Log::debug('no data'); } } }); // allocate to IVR Artisan::command('allocate:ivr', function () { $exclude = DB::table('asterisk.incoming') ->select('cidnum') ->groupBy('cidnum') ->get() ->pluck('cidnum'); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { print_r($exclude); } // die(); $data = DB::table('asteriskcdrdb.queuelog') ->select( 'asterisk.queues_config.extension', 'cdr.src', 'asterisk.ivr_details.id', 'asterisk.ivr_details.name', ) ->join('asteriskcdrdb.cdr', 'asteriskcdrdb.cdr.uniqueid', 'asteriskcdrdb.queuelog.callid') ->leftJoin('asterisk.queues_config', 'asterisk.queues_config.extension', 'queuelog.queuename') ->leftJoin('asterisk.ivr_details', 'asterisk.ivr_details.name', 'queuelog.queuename') ->whereNotIn('cdr.src', $exclude) ->whereNotIn('cdr.src', [ '01002507050', '01110555281', '01118925708', ]) ->whereDate('time', today()->format('Y-m-d')) ->whereTime('time', '>', now()->subHour()->format('H:i:s')) ->groupBy('cdr.src') ->orderBy('time', 'DESC') ->limit(100) ->get() ->toArray(); // dd($data); // die(); $insertions = []; foreach ($data as $one) { $insertions[] = [ 'cidnum' => $one->src, 'extension' => '0020221282721', 'destination' => "ivr-$one->id,s,1", 'privacyman' => '0', 'alertinfo' => '', 'ringing' => '', 'fanswer' => '', 'mohclass' => 'default', 'description' => 'Auto Direct To Queue', 'grppre' => '', 'delay_answer' => '0', 'pricid' => '', 'pmmaxretries' => '3', 'pmminlength' => '10', 'reversal' => '', 'rvolume' => '0', 'indication_zone' => 'default', ]; } if (!empty($insertions)) { DB::table('asterisk.incoming')->insert($insertions); $process = new Process(['fwconsole', 'reload']); $process->run(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug('allocate run'); \Log::debug($data); } // dd($data); } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug('allocate run'); \Log::debug('no data'); } } }); Artisan::command('sms:shoot', function () { try { $campaigns = Campaign:: // campaigns Conditions where([ ['campaigns.campaign_status_id', '=', '2'], ['campaigns.expected_close_date', '>=', date('Y-m-d')], ['campaigns.campaign_type_id', '=', '2'], ['campaigns.deleted_at', '=', NULL], ['campaigns.sms_msg', '!=', ''], ])->whereNotNull('campaigns.sms_msg') ->select( 'campaigns.expected_close_date', 'campaigns.expected_sales_count', 'campaigns.announcement_id', 'campaigns.id', 'campaigns.name', 'campaigns.owned_by_type', 'campaigns.owned_by_id', 'campaigns.campaign_priority', 'campaigns.campaign_status_id', 'campaigns.sms_msg' ) ->with(['campaign_lead' => function ($query) { $query->leftJoin('leads', 'leads.id', '=', 'campaign_lead.lead_id') ->leftJoin('campaigns', 'campaigns.id', '=', 'campaign_lead.campaign_id') ->select( 'campaign_lead.lead_id', 'campaign_lead.campaign_id', 'campaign_lead.sms_sent', 'leads.phone', 'leads.id', 'leads.mobile' ) // campaign_lead Conditions ->where('campaign_lead.sms_sent', '0') // leads Conditions ->where(function ($query) { return $query->whereNotNull('leads.phone')->orWhereNotNull('leads.mobile'); })->limit(10); }]) ->orderBy('campaign_priority', 'DESC')->get(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug($campaigns); } // die(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Start SMS Diler"); echo PHP_EOL . "Starting SMS-dialer..." . PHP_EOL; } if ($campaigns->count() >= 1) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("campaigns Runinng: " . $campaigns->count()); } foreach ($campaigns as $campaign) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("leads Runinng in $campaign->name: " . $campaign->campaign_lead->count()); } if ($campaign->campaign_lead->count() >= 1) { foreach ($campaign->campaign_lead as $lead) { $num = !empty($lead->phone) ? $lead->phone : $lead->mobile; if (!$num) { continue; } $num = PhoneNormalizer::toLocal( $num, config('sms.country_code', '20'), config('sms.trunk_prefix', '0') ); // Skip numbers that are not a valid mobile for the // configured country (e.g. over-long/garbage numbers // like 01551084078201221054077) — never send to them. // Still mark the lead as processed so it is not // retried (and skipped again) on subsequent runs. if (!PhoneNormalizer::isValidMobile($num, config('sms.country_code', '20'))) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("Skipping invalid mobile number: $num"); } \DB::table('campaign_lead') ->where('lead_id', $lead->lead_id)->where('campaign_id', $lead->campaign_id) ->update([ 'sms_sent' => '1', 'updated_at' => Carbon::now(), ]); continue; } (new SmsService())->sendAndSave( $campaign->sms_msg, $num, [ 'lead_id' => $lead->id, 'campaign_id' => $lead->campaign_id, 'company_id' => 1, ] ); \DB::table('campaign_lead') ->where('lead_id', $lead->lead_id)->where('campaign_id', $lead->campaign_id) ->update([ 'sms_sent' => '1', 'updated_at' => Carbon::now(), ]); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("num = $num dialed"); } } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("No Leads In campaign: $campaign->name"); } } } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("0 Campaigns Active!"); } } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug("SMS-dialer completed."); echo PHP_EOL . "SMS-dialer completed." . PHP_EOL; } } catch (Exception $e) { echo $e->getMessage() . "\n"; } })->describe('Shoot sms msg for all active campaigns'); Artisan::command('sms:status', function () { try { $allSms = Sms::where('status', 'Pending')->get(); if ($allSms->count() >= 1) { foreach ($allSms as $sms) { $response = Http::withOptions(['verify' => false]) ->withBasicAuth(CompanySettings::get('S_COMPANY_SMSUSER'), CompanySettings::get('S_COMPANY_SMSPASS')) ->post("https://api.socialbrand.agency/api/SMS/CheckDLRStatus?msgId=" . $sms->msgId); $sms->status = $response['status']; $sms->save(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::debug(["response" => $response['status']]); } } } } catch (Exception $e) { echo $e->getMessage() . "\n"; } })->describe('Change SMS Status'); // ───────────────────────────────────────────────────────────────────────────── // one:shoot – download audio, merge, then originate an AMI call // Called via: exec("/var/www/ccsystem/artisan one:shoot {id} > /dev/null 2>&1 &") // ───────────────────────────────────────────────────────────────────────────── Artisan::command('one:shoot {dialer_call_id}', function ($dialer_call_id) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: starting for dialer_call_id={$dialer_call_id}"); } $dialer_call = DialerCalls::where('id', $dialer_call_id) ->where('order_status', 'New') ->first(); if (!$dialer_call) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("one:shoot: DialerCalls#{$dialer_call_id} not found or not in New status."); } return; } $dialerService = new AutodialerService(); // ── 1. Download voice files ─────────────────────────────────────────────── if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: downloading voice files for call#{$dialer_call->id}"); } $audioLinks = json_decode($dialer_call->voices, true); $downloadedFiles = $dialerService->downloadFiles($audioLinks, $dialer_call->id); if (!$downloadedFiles) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("one:shoot: download failed for call#{$dialer_call->id}"); } $dialer_call->order_status = 'Failed'; $dialer_call->save(); $dialerService->callbackWebhook([ 'error' => 'Failed to download voice files', 'call_id' => $dialer_call->id, ]); exec("rm -rf " . storage_path("app/public/temp/dialer-{$dialer_call->id}-*")); return; } // ── 2. Merge audio files ────────────────────────────────────────────────── $outputFile = "/var/lib/asterisk/sounds/customIVR/{$dialer_call->id}.ulaw"; if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: merging audio -> {$outputFile}"); } $merged = $dialerService->mergeAudiosUsingFFMpeg($downloadedFiles, $outputFile, $dialer_call->id); // Clean up temp downloads exec("rm -rf " . storage_path("app/public/temp/dialer-{$dialer_call->id}-*")); if (!$merged) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("one:shoot: merge failed for call#{$dialer_call->id}"); } $dialer_call->order_status = 'Failed'; $dialer_call->save(); $dialerService->callbackWebhook([ 'error' => 'Failed to merge voice files', 'call_id' => $dialer_call->id, ]); exec("rm -rf {$outputFile}"); return; } // ── 3. Originate call via AMI (with retry) ──────────────────────────────── $num = trim($dialer_call->number); if (strlen($num) == 10 && substr($num, 0, 1) !== '0') { $num = str_pad($num, 11, '0', STR_PAD_LEFT); } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: originating call to {$num} from DID={$dialer_call->did}"); } $options = [ 'host' => config('pbx.ami.host'), 'scheme' => config('pbx.ami.scheme'), 'port' => config('pbx.ami.port'), 'username' => config('pbx.ami.username'), 'secret' => config('pbx.ami.secret'), 'connect_timeout' => 3, 'read_timeout' => 5, ]; $maxAttempts = 3; $dialed = false; for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) { try { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: AMI connect attempt {$attempt}/{$maxAttempts}"); } $a = new ClientImpl($options); $a->open(); $actionid = md5(uniqid()); $originateMsg = new OriginateAction("local/$num@from-internal"); $originateMsg->setContext('masking-operator'); $originateMsg->setExtension('s'); $originateMsg->setPriority('1'); $originateMsg->setCallerId($dialer_call->did); $originateMsg->setAsync(true); $originateMsg->setActionID($actionid); $originateMsg->setVariable('dialer_call_id', $dialer_call->id); $originateMsg->setVariable('IsDialer', true); $originateMsg->setVariable('LeadId', null); $originateMsg->setVariable('CampaignID', null); $originateMsg->setVariable('DialRetries', 0); $orgresp = $a->send($originateMsg); $orgStatus = $orgresp->getKeys()['response'] ?? ''; $a->close(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: AMI originate response: {$orgStatus}"); } if (!empty($orgStatus) && $orgStatus === 'Success') { $dialer_call->order_status = 'Done'; $dialer_call->save(); if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: call successfully originated for call#{$dialer_call->id}"); } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::warning("one:shoot: originate returned non-success status '{$orgStatus}' for call#{$dialer_call->id}"); } $dialer_call->order_status = 'Failed'; $dialer_call->save(); $dialerService->callbackWebhook([ 'error' => "AMI originate failed with status: {$orgStatus}", 'call_id' => $dialer_call->id, ]); exec("rm -rf {$outputFile}"); } $dialed = true; break; } catch (ClientException $e) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::warning("one:shoot: AMI attempt {$attempt} failed: " . $e->getMessage()); } if ($attempt < $maxAttempts) { sleep(3); } } } if (!$dialed) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error("one:shoot: AMI connection failed after {$maxAttempts} attempts for call#{$dialer_call->id}"); } $dialer_call->order_status = 'Failed'; $dialer_call->save(); $dialerService->callbackWebhook([ 'error' => 'AMI connection failed after retries', 'call_id' => $dialer_call->id, ]); exec("rm -rf {$outputFile}"); } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info("one:shoot: finished for dialer_call_id={$dialer_call_id}"); } })->describe('Download audio, merge, and originate an API-initiated call'); // ───────────────────────────────────────────────────────────────────────────── // callbackWebhook – fetch CDR data and post result to configured webhook URL // Called from agihangupaction after an API-initiated call ends // ───────────────────────────────────────────────────────────────────────────── Artisan::command('callbackWebhook {json_data}', function ($json_data) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info('callbackWebhook: starting'); } // Allow CDR to be written to DB before we read it sleep(5); $return_data = json_decode($json_data, true); if (!$return_data) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error('callbackWebhook: invalid JSON payload: ' . $json_data); } return; } if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info('callbackWebhook: payload decoded', ['data' => $return_data]); } $dialerService = new AutodialerService(); $result = $dialerService->callbackWebhook($return_data); if ($result) { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::info('callbackWebhook: webhook posted successfully'); } } else { if (CompanySettings::get(SettingsHelper::S_COMPANY_CONSOLE_DEBUG_LOGS)) { \Log::error('callbackWebhook: webhook post failed'); } } })->describe('Post API call result to the configured webhook URL');