SettingsHelper = new SettingsHelper();
$this->UserHelper = new UserHelper();
}
public function inbound(Request $request)
{
$conds = $this->SettingsHelper->defaultConds();
$user = Auth::user();
// Get admins for filtering if needed
$admins = User::where('company_id', $user->company_id)
->whereHas('roles', function ($q) {
$q->where('name', 'company_manager');
})
->pluck('id')
->toArray();
$company_groups = $user->accessibleQueueIds();
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');
}
}
if ($request->ajax()) {
$data = InboundReport::whereBetween('call_time', [$conds['from'], $conds['to']])
->where('call_status', 'answered')
->whereIn('enter_queuename', $company_groups);
// Apply filters
if(isset($request->did_ext)) {
$data = $data->where('did', $request->did_ext);
}
if ($request->transfers_only == '1') {
$data = $data->where('transfers_count', '>', 0);
}
if(!empty($request->search['value'])) {
$searchValue = strtolower(trim($request->search['value']));
$data = $data->where(function ($query) use ($searchValue) {
$query->orWhere(DB::raw('LOWER(queue_description)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(agent)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(event)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(caller_number)'), 'LIKE', '%' . $searchValue . '%');
});
}
// Apply user permissions
if($user->roles[0]->name != 'company_manager'){
$data = $data->whereNotIn('agent', $admins);
}
if($user->access_to_all_entity != '1'){
$data = $data->whereIn('enter_queuename', $user->accessibleQueueIds());
if($user->roles[0]->name == 'user'){
$data = $data->where('agent', 'LIKE', "%$user->name%");
}
}
// The AI analysis state for each row, resolved in the same statement
// (correlated subquery) so a page of calls never fans out into one
// query per row.
$data = $data->addSelect([
'ai_analysis_status' => CallAiAnalysis::query()
->select('status')
->whereColumn('call_ai_analyses.linkedid', 'inbound_reports.linkedid')
->orderByDesc('id')
->limit(1),
]);
return Datatables::of($data)
->addIndexColumn()
// Internal alias only: do not ship it to the browser.
->removeColumn('ai_analysis_status')
->addColumn('network', function($row){
return 'Download';
})
->addColumn('transfer_info', function($row){
if ($row->transfers_count > 0) {
$btn = '';
} else {
$btn = 'No Transfers';
}
return $btn;
})
->addColumn('action', function($record){
$btn = '';
if(\Auth::user()->hasPermissionTo('reporting.listen')) {
$display_url = route('reporting.listen.page', ['id' => $record->linkedid]);
$btn = 'Listen';
if (empty($record->data5) || is_null($record->data5) || $record->data5 == 0) {
} elseif ($record->data5 == 1) {
$btn = 'Listen';
}
}
// AI summary: Submit while there is no analysis, the stored
// analysis once it exists (requirement: a link styled as a
// button through to the analysis page).
$analysis = $this->analysisActionHtml((string) $record->linkedid, $record->ai_analysis_status ?? null, 'inbound');
$action = trim($btn . ' ' . $analysis);
return $action !== '' ? $action : 'Not Avail';
})
->editColumn('call_time', function($row) {
return date('Y-m-d h:i:s A', strtotime($row->call_time));
})
->rawColumns(['action', 'transfer_info', 'network'])
->make(true);
}
$view = view('reporting::inbound');
// The DID filter is read from the FreePBX `asterisk` database, which does
// not exist on every box (a bare-asterisk box has no `incoming` table) and
// the application user is not granted it everywhere. A missing dropdown
// must not take the whole report down with it.
try {
$did_options = DB::table('asterisk.incoming')
->where('extension', 'like', '002022%')
->pluck('extension', 'extension')
->toArray();
} catch (\Throwable $e) {
$did_options = [];
}
$did_options = [
'dids' => $did_options
];
$agent_names = User::where('company_id', Auth::user()->company_id)
->visibleInReports()
->pluck('name','id')->toArray();
$filter_area = view('reporting::widgets.filter_area');
$filter_area->with('conds',$conds);
$filter_area->with('did_options',$did_options);
$filter_area->with('did_ext',$request->did_ext);
$filter_area->with('submit_route',route('reporting.ccinbound'));
$view->with('filter_area',$filter_area);
$view->with('conds',$conds);
$view->with('agent_names',$agent_names);
$view->with('did_ext',$request->did_ext);
return $view;
}
// Fetch transfer details for the modal
public function getTransfers($linkedid)
{
try {
// All CDR rows for this call, ordered chronologically
$cdrRows = DB::table('asteriskcdrdb.cdr')
->where('linkedid', $linkedid)
->orderBy('calldate', 'asc')
->orderBy('sequence', 'asc')
->get();
if ($cdrRows->isEmpty()) {
return response()->json(['success' => true, 'transfers' => [], 'count' => 0]);
}
// The initial agent answer = first ANSWERED Dial (the agent who originally picked up)
$initialDial = $cdrRows->where('lastapp', 'Dial')->where('disposition', 'ANSWERED')->first();
if (!$initialDial) {
return response()->json(['success' => true, 'transfers' => [], 'count' => 0]);
}
$initialUniqueId = $initialDial->uniqueid;
// ── 1. Queue-to-queue transfers ────────────────────────────────────
// CDR rows where lastapp='Queue' AND uniqueid != linkedid
// (the original queue call has uniqueid = linkedid)
$queueTransferRows = $cdrRows
->where('lastapp', 'Queue')
->filter(fn($r) => $r->uniqueid !== $linkedid)
->sortBy('calldate')
->values();
$knownUniqueIds = [$linkedid, $initialUniqueId];
$transfers = [];
foreach ($queueTransferRows as $qt) {
$queueExt = trim(explode(',', $qt->lastdata)[0] ?? '');
// The transfer agent comes from a NEW channel (different uniqueid)
// that dials after the Queue row was created
$agentDial = $cdrRows
->where('lastapp', 'Dial')
->filter(fn($r) =>
!in_array($r->uniqueid, $knownUniqueIds) &&
$r->calldate >= $qt->calldate
)
->sortBy('calldate')
->first();
if ($agentDial) {
$knownUniqueIds[] = $agentDial->uniqueid;
}
$transfers[] = [
'_type' => 'queue',
'calldate' => $qt->calldate,
'queue_ext' => $queueExt,
'agent_ext' => $agentDial?->dst,
'disposition' => $agentDial?->disposition ?? 'NO ANSWER',
'duration' => $agentDial?->duration ?? 0,
];
}
// ── 2. Direct extension transfers ─────────────────────────────────
// Additional Dial rows within the SAME uniqueid as the initial agent answer.
// These appear when the agent directly transfers to another extension
// without going through a queue.
$initialChannelDials = $cdrRows
->where('lastapp', 'Dial')
->where('uniqueid', $initialUniqueId)
->sortBy('calldate')
->values()
->slice(1); // skip the initial answer itself
foreach ($initialChannelDials as $dialRow) {
// Skip if already covered by a queue transfer (queue row at same time)
$coveredByQueue = $queueTransferRows
->filter(fn($qt) => $qt->calldate <= $dialRow->calldate)
->isNotEmpty();
if ($coveredByQueue) {
continue;
}
// Extract the actual extension from dstchannel (e.g. PJSIP/998-xxx → 998)
$ext = null;
if (!empty($dialRow->dstchannel)) {
preg_match('/[\/:](\d{3,6})[-@]/', $dialRow->dstchannel, $m);
$ext = $m[1] ?? null;
}
$ext = $ext ?? $dialRow->dst;
if (substr($ext, 0, 2) == '99' && strlen($ext) > 4) {
$ext = substr($ext, 2);
}
$transfers[] = [
'_type' => 'direct',
'calldate' => $dialRow->calldate,
'queue_ext' => null,
'agent_ext' => $ext,
'disposition' => $dialRow->disposition,
'duration' => $dialRow->duration ?? 0,
];
}
if (empty($transfers)) {
return response()->json(['success' => true, 'transfers' => [], 'count' => 0]);
}
usort($transfers, fn($a, $b) => strcmp($a['calldate'], $b['calldate']));
// ── Enrich: queue descriptions ─────────────────────────────────────
$queueExts = array_values(array_filter(array_unique(array_column($transfers, 'queue_ext'))));
$queueDescriptions = [];
if (!empty($queueExts)) {
$queueDescriptions = DB::table('asterisk.queues_config')
->whereIn('extension', $queueExts)
->pluck('descr', 'extension')
->toArray();
}
// ── Enrich: agent names ────────────────────────────────────────────
$agentExts = array_values(array_filter(array_unique(array_column($transfers, 'agent_ext'))));
$agentNames = [];
if (!empty($agentExts)) {
$agentNames = User::whereIn('id', $agentExts)
->pluck('name', 'id')
->toArray();
}
// ── Build response ─────────────────────────────────────────────────
$result = array_map(fn($t) => [
'calldate' => $t['calldate'],
'queue_ext' => $t['queue_ext'],
'queue_name' => $t['queue_ext']
? ($queueDescriptions[$t['queue_ext']] ?? $t['queue_ext'])
: 'Internal Transfer',
'user_extension' => $t['agent_ext'],
'user_name' => $t['agent_ext']
? ($agentNames[$t['agent_ext']] ?? $t['agent_ext'])
: null,
'disposition' => $t['disposition'],
'duration' => $t['duration'],
], $transfers);
return response()->json([
'success' => true,
'transfers' => array_values($result),
'count' => count($result),
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error fetching transfer data: ' . $e->getMessage()
]);
}
}
public function listenFile($id)
{
$record = \DB::table('asteriskcdrdb.cdr')
->where('linkedid', $id)
->where('recordingfile', 'like', "%$id%")
->first();
if (!$record) {
abort(404);
}
$calldate = date('Y/m/d', strtotime($record->calldate));
$record_name = $record->recordingfile;
if (is_null($record_name) || empty($record_name)) {
abort(404);
}
$file_path = "/var/spool/asterisk/monitor/" . $calldate . '/' . $record_name;
if (file_exists($file_path)) {
return response()->file($file_path);
}
if (CompanySettings::get('S_COMPANY_S3_STORAGE')) {
$remotePath = "/calls/{$calldate}/{$record_name}";
try {
if (!Storage::disk('s3')->exists($remotePath)) {
abort(404);
}
$fileContent = Storage::disk('s3')->get($remotePath);
$fileSize = Storage::disk('s3')->size($remotePath);
return response($fileContent, 200, [
'Content-Type' => 'audio/wav',
'Content-Length' => $fileSize,
'Accept-Ranges' => 'bytes',
'Content-Disposition' => 'inline'
]);
} catch (\Exception $e) {
abort(404);
}
}
abort(404);
}
public function listenPageFile($id)
{
$record = \DB::table('asteriskcdrdb.cdr')
->where('linkedid', $id)
->where('recordingfile', 'like', "%$id%")
->first();
if (!$record) {
abort(404);
}
$calldate = date('Y/m/d', strtotime($record->calldate));
$record_name = $record->recordingfile;
if (is_null($record_name) || empty($record_name)) {
abort(404);
}
$file_path = "/var/spool/asterisk/monitor/" . $calldate . '/' . $record_name;
$allowDownload = (bool) auth()->user()->allow_download_recording;
if (!file_exists($file_path)) {
if (CompanySettings::get('S_COMPANY_S3_STORAGE')) {
$remotePath = "/calls/{$calldate}/{$record_name}";
if(Storage::disk('s3')->exists($remotePath)) {
$view = view('reporting::playRecoed');
$view->with('record_id', $id);
$view->with('record_name', $record_name);
$view->with('allow_download', $allowDownload);
return $view;
} else {
abort(404);
}
}else {
abort(404);
}
}else {
$view = view('reporting::playRecoed');
$view->with('record_id', $id);
$view->with('record_name', $record_name);
$view->with('allow_download', $allowDownload);
return $view;
}
}
public function excelReport(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');
}
}
$user = Auth::user();
$admins = User::where('company_id', $user->company_id)
->whereHas('roles', function ($q) {
$q->where('name', 'company_manager');
})
->pluck('id')
->toArray();
$company_groups = $user->accessibleQueueIds();
$data = InboundReport::whereBetween('call_time', [$conds['from'], $conds['to']])
->whereIn('hangup_type', ['COMPLETECALLER','COMPLETEAGENT'])
->whereIn('enter_queuename', $company_groups);
// Apply filters
if(isset($request->did_ext)) {
$data = $data->where('did', $request->did_ext);
}
if(!empty($request->search['value'])) {
$searchValue = strtolower(trim($request->search['value']));
$data = $data->where(function ($query) use ($searchValue) {
$query->orWhere(DB::raw('LOWER(queue_description)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(agent)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(event)'), 'LIKE', '%' . $searchValue . '%')
->orWhere(DB::raw('LOWER(caller_number)'), 'LIKE', '%' . $searchValue . '%');
});
}
// Apply user permissions
if($user->roles[0]->name != 'company_manager') {
$data = $data->whereNotIn('agent', $admins);
}
if($user->access_to_all_entity != '1') {
$data = $data->whereIn('enter_queuename', $user->accessibleQueueIds());
if($user->roles[0]->name == 'user') {
$data = $data->where('agent', 'LIKE', "%$user->name%");
}
}
$data = $data->orderBy('call_time', 'DESC')->get();
$headers = [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition' => 'attachment; filename=inbound_report.xlsx',
];
$callback = function () use ($data) {
$excel = new Spreadsheet();
$sheet = $excel->getActiveSheet();
$sheet->setCellValue('A1', 'Time');
$sheet->setCellValue('B1', 'Queue');
$sheet->setCellValue('C1', 'Agent');
$sheet->setCellValue('D1', 'Hangup');
$sheet->setCellValue('E1', 'Ringtime');
$sheet->setCellValue('F1', 'Talktime');
$sheet->setCellValue('G1', 'Number');
$sheet->setCellValue('H1', 'DID/Channel');
$sheet->setCellValue('I1', 'Transfers');
$user = Auth::user();
$hasSurveyPermission = $user->hasPermissionTo('reporting.survey');
if ($hasSurveyPermission) {
$sheet->setCellValue('J1', 'Survey');
}
$headerStyle = ['font' => ['bold' => true]];
$sheet->getStyle('A1:' . ($hasSurveyPermission ? 'J1' : 'I1'))->applyFromArray($headerStyle);
$agent_names = User::where('company_id', Auth::user()->company_id)
->visibleInReports()
->pluck('name','id')->toArray();
$rowNum = 2;
foreach ($data as $row) {
$agent = $row->agent;
$extension = null;
if (preg_match('/Local\/(\d+)@/', $agent, $matches)) {
$extension = $matches[1];
}
$agent = $extension ? ($agent_names[$extension] ?? $agent) : $agent;
$sheet->setCellValue('A' . $rowNum, date('Y-m-d H:i:s', strtotime($row->call_time)));
$sheet->setCellValue('B' . $rowNum, $row->queue_description);
$sheet->setCellValue('C' . $rowNum, $agent);
$sheet->setCellValue('D' . $rowNum, $row->event);
$sheet->setCellValue('E' . $rowNum, gmdate('H:i:s', $row->ringtime));
$sheet->setCellValue('F' . $rowNum, gmdate('H:i:s', $row->talktime));
$sheet->setCellValue('G' . $rowNum, $row->caller_number);
$sheet->setCellValue('H' . $rowNum, $row->did);
$sheet->setCellValue('I' . $rowNum, $row->transfers_count);
if ($hasSurveyPermission) {
$sheet->setCellValue('J' . $rowNum, $row->survey);
}
$rowNum++;
}
$excel = IOFactory::createWriter($excel, 'Xlsx');
$excel->save('php://output');
};
return Response::stream($callback, 200, $headers);
}
function inDone($id) {
try {
DB::update("update asteriskcdrdb.queuelog set data5 = 1 where id = $id");
DB::update("update inbound_reports set data5 = 1 where linkedid = $id");
return true;
} catch (\Throwable $th) {
throw $th;
}
}
function inDist(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');
}
}
$user = Auth::user();
$filter = [
'conds' => $conds,
// Group (queue) filter — same name and semantics as the Inbound
// report API (group_ext narrows to one accessible queue).
'filter_queue' => $request->group_ext,
];
$data = $this->getInDistribution($filter);
$view = view('reporting::inDist');
// tables
$monthsDatatables = Datatables::of($data['months_table'])
->addIndexColumn()
->addColumn('offered', function($row){
return $row['answord'] + $row['missed'];
})
->addColumn('rate', function($row){
$total = $row['answord'] + $row['missed'];
return $total > 0
? round(($row['answord'] / $total) * 100, 1) . ' %'
: '0 %';
})
->make(true);
$monthsDatatablesJson = json_encode($monthsDatatables->getData()->data);
$view->with('monthsDatatablesJson',$monthsDatatablesJson);
//
$daysDatatables = Datatables::of($data['days_table'])
->addIndexColumn()
->addColumn('offered', function($row){
return $row['answord'] + $row['missed'];
})
->addColumn('rate', function($row){
$total = $row['answord'] + $row['missed'];
return $total > 0
? round(($row['answord'] / $total) * 100, 1) . ' %'
: '0 %';
})
->make(true);
$daysDatatablesJson = json_encode($daysDatatables->getData()->data);
$view->with('daysDatatablesJson',$daysDatatablesJson);
//
$hourDatatables = Datatables::of($data['hourse_table'])
->addIndexColumn()
->addColumn('offered', function($row){
return $row['answord'] + $row['missed'];
})
->addColumn('rate', function($row){
$total = $row['answord'] + $row['missed'];
return $total > 0
? round(($row['answord'] / $total) * 100, 1) . ' %'
: '0 %';
})
->make(true);
$hourDatatablesJson = json_encode($hourDatatables->getData()->data);
$view->with('hourDatatablesJson',$hourDatatablesJson);
// /////////////////////////////////////////////////////
// charts
$view->with('MonthDist', new CallDist([
'labels' => array_values($data['months_processed']),
'answered' => array_values($data['answered_by_month']),
'missed' => array_values($data['unans_by_month']),
'type' => 'in',
]));
$view->with('dayDist', new CallDist([
'labels' => array_values($data['days_processed']),
'answered' => array_values($data['answered_by_day']),
'missed' => array_values($data['unans_by_day']),
'type' => 'in',
]));
// Build the hourly chart from the exact same rows the data table
// uses, so both always share the same hour order and values.
$view->with('hourseDist', new CallDist([
'labels' => array_column($data['hourse_table'], 'hourse'),
'answered' => array_column($data['hourse_table'], 'answord'),
'missed' => array_column($data['hourse_table'], 'missed'),
'type' => 'in',
]));
////////////////////////////////////////
// Groups filter options — same UI pattern as the Attendance report
// (filter_area widget select) and the same permission scope.
$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;
}
}
$groups_options = [
'Groups' => $groups
];
$filter_area = view('reporting::widgets.filter_area');
$filter_area->with('conds',$conds);
$filter_area->with('groups_options',$groups_options);
$filter_area->with('group_ext',$request->group_ext);
$filter_area->with('submit_route',route('reporting.ccinDist'));
$view->with('filter_area',$filter_area);
return $view;
}
public function getInDistribution($data = array()){
$conds = $data['conds'];
$user = Auth::user();
// Fetch once and reuse: accessibleQueueIds() runs permission and group
// queries on every call.
$company_groups = $user->accessibleQueueIds();
// Classification relies on call_status ('answered' / 'missed') written by
// the same services that feed the Inbound and Inbound & Missed reports,
// instead of mapping queuelog event names. Rows with any other status
// ('unknown' / NULL on legacy rows) are not counted.
$query = DB::table('inbound_reports')
->select(
'call_time as time',
'enter_queuename as queuename',
'agent',
'call_status'
)
->whereBetween('call_time', [$conds['from'], $conds['to']])
->whereIn('enter_queuename', $company_groups)
->whereIn('call_status', ['answered', 'missed']);
$excludedQueues = CompanySettings::get('report_excluded_groups');
if (!empty($excludedQueues) && is_array($excludedQueues)) {
$query = $query->whereNotIn('enter_queuename', $excludedQueues);
}
if (!empty($data['filter_queue'])) {
// (array) cast: group_ext arrives as a single scalar id from the
// filter form, and whereIn() requires an array (a scalar would
// fatal in cleanBindings()). Also accepts an array unchanged.
$query = $query->whereIn('enter_queuename', (array) $data['filter_queue']);
}
if($user->roles[0]->name != 'company_manager'){
// Lazy: only queried when the exclusion is actually applied.
$admins = User::where('company_id', $user->company_id)
->whereHas('roles', function ($q) {
$q->where('name', 'company_manager');
})
->pluck('name')
->toArray();
$query = $query->whereNotIn('agent', $admins);
}
if($user->access_to_all_entity != '1'){
// Queue restriction is already enforced above via $company_groups
// (same accessibleQueueIds() result) — only the per-agent
// restriction remains here.
if($user->roles[0]->name == 'user'){
$query = $query->where('agent', 'LIKE', "%$user->name%");
}
}
$return_array = [
'days_processed' => [],
'hours_processed' => [],
'months_processed' => [],
'months_table' => [],
'days_table' => [],
'hourse_table' => [],
'answered_by_month' => [],
'answered_by_day' => [],
'answered_by_hour' => [],
'unans_by_month' => [],
'unans_by_day' => [],
'unans_by_hour' => [],
];
$results = $query->orderBy('time', 'ASC')->get();
if ($results->isNotEmpty()) {
$countMonths = 0;
$pervMonth = NULL;
$countDays = 0;
$pervDay = NULL;
foreach($results as $row){
// get days_processed
$time_exploded = explode(" ", $row->time);
$one_day = trim($time_exploded[0]);
if (!in_array($one_day, $return_array['days_processed'])) {
if ($countDays != 0) {
@$return_array['days_table'][] = [
'day' => $pervDay,
'answord' => $return_array['answered_by_day'][$pervDay] ?? 0,
'missed' => $return_array['unans_by_day'][$pervDay] ?? 0,
];
}
$return_array['days_processed'][] = $one_day;
$pervDay = $one_day;
$countDays = $countDays + 1;
}
// get hours_processed
$hour_exploded = explode(":", @$time_exploded[1]);
$one_hour = $hour_exploded[0];
if (!in_array($one_hour, $return_array['hours_processed'])) {
$return_array['hours_processed'][] = $one_hour;
}
// get months_processed
$month_year_label = Carbon::parse($row->time)->format('m-Y');
if (!in_array($month_year_label, $return_array['months_processed'])) {
if ($countMonths != 0) {
@$return_array['months_table'][] = [
'month' => $pervMonth,
'answord' => $return_array['answered_by_month'][$pervMonth] ?? 0,
'missed' => $return_array['unans_by_month'][$pervMonth] ?? 0,
];
}
$return_array['months_processed'][] = $month_year_label;
$pervMonth = $month_year_label;
$countMonths = $countMonths + 1;
}
// Classify by call_status
if ($row->call_status === 'missed') {
@$return_array['unans_by_day'][$one_day]++;
@$return_array['unans_by_hour'][$one_hour]++;
@$return_array['unans_by_month'][$month_year_label]++;
}
if ($row->call_status === 'answered') {
@$return_array['answered_by_day'][$one_day]++;
@$return_array['answered_by_hour'][$one_hour]++;
@$return_array['answered_by_month'][$month_year_label]++;
}
}
@$return_array['months_table'][] = [
'month' => $month_year_label,
'answord' => $return_array['answered_by_month'][$month_year_label] ?? 0,
'missed' => $return_array['unans_by_month'][$month_year_label] ?? 0,
];
@$return_array['days_table'][] = [
'day' => $one_day,
'answord' => $return_array['answered_by_day'][$one_day] ?? 0,
'missed' => $return_array['unans_by_day'][$one_day] ?? 0,
];
// Sort hours numerically (earliest -> latest) BEFORE building the
// table so the chart and the data table share the exact same order.
sort($return_array['hours_processed'], SORT_NUMERIC);
foreach($return_array['hours_processed'] as $hour){
@$return_array['hourse_table'][] = [
'hourse' => $hour,
'answord' => $return_array['answered_by_hour'][$hour] ?? 0,
'missed' => $return_array['unans_by_hour'][$hour] ?? 0,
];
}
}
return $return_array;
}
}