'tickets.browse', 'create' => 'tickets.create', 'update' => 'tickets.update', 'destroy' => 'tickets.destroy' ]; protected $moduleSettingsLinks = [ ['route' => 'tickets.priority.index', 'label' => 'settings.priority'], ['route' => 'tickets.status.index', 'label' => 'settings.status'], ['route' => 'tickets.severity.index', 'label' => 'settings.severity'], ['route' => 'tickets.category.index', 'label' => 'settings.category'], ]; protected $settingsPermission = 'tickets.settings'; protected function setupAssignedTo($entity, $input, $creating = false) { if ($creating) { // Suppress the follow-up save() in ModuleCrudController::store() that // persists owner bookkeeping right after creation. Spatie's LogsActivity // would otherwise log a spurious 'updated' entry for every create. $entity->disableLogging(); } return parent::setupAssignedTo($entity, $input, $creating); } protected $showFields = [ 'information' => [ 'name' => [ 'type' => 'text', ], 'ticket_num' => [ 'type' => 'text', ], 'due_date' => [ 'type' => 'date', ], 'due_time' => [ 'type' => 'text', ], 'owned_by' => [ 'type' => 'assigned_to', ], // 'ticket_owner_id' => [ // 'type' => 'manyToOne', // 'relation' => 'ticketOwner', // 'column' => 'name' // ], 'ticket_priority_id' => [ 'type' => 'manyToOne', 'relation' => 'ticketPriority', 'column' => 'name' ], 'ticket_status_id' => [ 'type' => 'manyToOne', 'relation' => 'ticketStatus', 'column' => 'name' ], 'ticket_severity_id' => [ 'type' => 'manyToOne', 'relation' => 'ticketSeverity', 'column' => 'name', ], 'ticket_category_id' => [ 'type' => 'manyToOne', 'relation' => 'ticketCategory', 'column' => 'name' ], 'lead_id' => [ 'type' => 'manyToOne', 'relation' => 'lead', 'column' => 'full_name', 'dont_translate' => true, 'route' => 'leads.leads.show' ], 'contact_id' => [ 'type' => 'manyToOne', 'relation' => 'contact', 'column' => 'full_name', 'dont_translate' => true ], 'account_id' => [ 'type' => 'manyToOne', 'relation' => 'account', 'column' => 'name', 'dont_translate' => true ], 'parent_id' => [ 'type' => 'manyToOne', 'relation' => 'parent', 'column' => 'name', 'dont_translate' => true ], ], 'description' => [ 'description' => [ 'type' => 'text', 'col-class' => 'col-lg-12 col-md-12 col-sm-12' ], ], 'resolution' => [ 'resolution' => [ 'type' => 'text', 'col-class' => 'col-lg-12 col-md-12 col-sm-12' ], ], 'notes' => [ 'notes' => [ 'type' => 'text', 'col-class' => 'col-lg-12 col-md-12 col-sm-12' ], ], ]; protected $relationTabs = [ 'tickets' => [ 'icon' => 'report_problem', 'permissions' => [ 'browse' => 'tickets.browse', 'update' => 'tickets.update', 'create' => 'tickets.create' ], 'datatable' => [ 'datatable' => TicketsTicketsDatatable::class ], 'route' => [ 'linked' => 'tickets.tickets.linked', 'create' => 'tickets.tickets.create', 'select' => 'tickets.tickets.selection', 'bind_selected' => 'tickets.tickets.link' ], 'create' => [ 'allow' => true, 'modal_title' => 'tickets::tickets.create_new', 'post_create_bind' => [ 'relationType' => 'oneToMany', 'relatedField' => 'parent_id', ] ], 'select' => [ 'allow' => false, 'modal_title' => 'tickets::tickets.module' ], ], ]; protected $languageFile = 'tickets::tickets'; protected $routes = [ 'index' => 'tickets.tickets.index', 'create' => 'tickets.tickets.create', 'show' => 'tickets.tickets.show', 'edit' => 'tickets.tickets.edit', 'store' => 'tickets.tickets.store', 'destroy' => 'tickets.tickets.destroy', 'update' => 'tickets.tickets.update' ]; public function __construct() { parent::__construct(); $this->SettingsHelper = new SettingsHelper(); } /** * Map each Tickets module "Settings" dropdown item to the corresponding * field-settings key so that renamed field labels (e.g. Status -> Test) * are reflected in the dropdown automatically. */ protected function moduleSettingsLinkFieldMap(): array { return [ 'settings.priority' => 'ticket_priority_id', 'settings.status' => 'ticket_status_id', 'settings.severity' => 'ticket_severity_id', 'settings.category' => 'ticket_category_id', ]; } /** * Effective field labels from the CRM field-settings service. */ protected function moduleSettingsLinkLabelMap(): array { return app(TicketFieldSettingsService::class)->getFieldLabelMap(auth()->user()); } protected function indexActionButtons() { $noPermissionIsRequired = (array_key_exists('create', $this->permissions) && $this->permissions['create'] == ''); $hasPermissionifRequired = (isset($this->permissions['create']) && $this->permissions['create'] != '' && \Auth::user()->hasPermissionTo($this->permissions['create'])); if ($noPermissionIsRequired || $hasPermissionifRequired) { if (auth()->user()->can('tickets.report')) { $this->indexActionButtons[] = array( 'href' => route('tickets.tickets.report'), 'attr' => [ 'class' => ['Report'] ], 'label' => 'Analytics' ); } $this->indexActionButtons[] = array( 'href' => '#', 'attr' => [ 'class' => ['quick_create'] ], 'label' => trans('core::core.btn.quick_create') ); if (isset($this->routes['import'])) { $this->indexActionButtons[] = array( 'href' => '#', 'attr' => [ 'class' => ['records_import'] ], 'label' => trans('core::core.btn.import') ); } } } protected function setupIndexTabButtons() { $this->indexTabButtons[] = array( 'href' => '#all', 'default' => true, 'attr' => [ 'class' => 'waves-effect waves-block', 'role' => 'tab', 'data-toggle' => "tab", 'onClick' => "BAP_Datatable.headerFilterReset('dataTableBuilder')" ], 'label' => trans('core::core.header_all') ); $this->indexTabButtons[] = array( 'href' => '#assigned', 'attr' => [ 'class' => ' waves-effect waves-block', 'role' => 'tab', 'data-toggle' => "tab", 'onClick' => "BAP_Datatable.headerFilterSet('assigned_to','dataTableBuilder',window.CURRENT_USER.name)" ], 'label' => trans('core::core.header_assigned') ); } public function setupCustomButtons() { $this->customShowButtons[] = array( 'href' => route('tickets.tickets.print', $this->entity->id), 'attr' => [ 'class' => 'btn bg-pink waves-effect pull-right', ], 'label' => trans('tickets::tickets.print') ); } /** * Invoice print * * @param $identifier * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ public function print($identifier) { if ($this->permissions['browse'] != '' && !\Auth::user()->hasPermissionTo($this->permissions['browse'])) { flash(trans('core::core.you_dont_have_access'))->error(); return redirect()->route($this->routes['index']); } $repository = $this->getRepository(); $entity = $repository->find($identifier); $this->entity = $entity; if (empty($entity)) { flash(trans('core::core.entity.entity_not_found'))->error(); return redirect(route($this->routes['index'])); } if ($this->blockEntityOwnableAccess()) { flash(trans('core::core.you_dont_have_access'))->error(); return redirect()->route($this->routes['index']); } $this->entityIdentifier = $entity->id; $this->entity = $entity; $printData = [ 'entity' => $this->entity ]; $pdf = \PDF::loadView('tickets::pdf.print', $printData); return $pdf->inline($entity->name . '_TICKET.pdf'); } public function afterStore($request, &$entity = NULL) { // Record the creator only when the ticket is initially created. // It must never be overwritten when the ticket is edited/updated later. if (empty($entity->ticket_owner_id)) { $entity->ticket_owner_id = \Auth::id(); } // If entity has a lead_id, log creation activity for the Lead too if (!empty($entity->lead_id)) { $extension = \Auth::id(); // ID of the authenticated user $time = now(); // Current timestamp $insertions[] = [ 'description' => 'Craete Ticket', 'subject_id' => $entity->lead_id, 'subject_type' => 'Modules\Leads\Entities\Lead', 'causer_id' => $extension, 'causer_type' => 'Modules\Platform\User\Entities\User', 'properties' => '[]', 'created_at' => $time, 'updated_at' => $time ]; \DB::table('activity_log')->insert($insertions); } $serverCode = config('app.server_code'); $ticketNum = $serverCode . '-' . Carbon::now()->format('YmdHis') . '-' . $entity->id; $entity->ticket_num = $ticketNum; $entity->save(); // Create flow is complete — restore automatic activity logging for any // subsequent saves on this entity. $entity->enableLogging(); } public function afterUpdate($request, &$entity = NULL) { if(!$entity->ticket_num) { $serverCode = config('app.server_code'); $ticketNum = $serverCode . '-' . Carbon::now()->format('YmdHis') . '-' . $entity->id; // ticket_num is bookkeeping, not a logged attribute — suppress the // no-op 'updated' entry Spatie would otherwise record. $entity->disableLogging(); $entity->ticket_num = $ticketNum; $entity->save(); $entity->enableLogging(); } } public function report(Request $request) { $user = Auth::user(); $conds = $this->SettingsHelper->defaultConds(); $default_from = Carbon::now()->startOfMonth()->format('Y-m-d 00:00:00'); $default_to = Carbon::now()->format('Y-m-d 23:59:59'); if ($request->has('daterange')) { $range = explode(' - ', $request->daterange); $filter_from = new Carbon($range[0]); $conds['from'] = $filter_from->format('Y-m-d 00:00:00'); $filter_to = new Carbon($range[1]); $conds['to'] = $filter_to->format('Y-m-d 23:59:59'); } else { $conds['from'] = $default_from; $conds['to'] = $default_to; } $data = \DB::table('tickets') ->whereNull('tickets.deleted_at') // Exclude deleted data ->whereBetween('tickets.created_at', [$conds['from'], $conds['to']]); if ($user->roles[0]->name == 'user') { $request->group_ext = $user->groups[0]->id; } if (isset($request->account)) { $data->where('account_id', $request->account); } if (isset($request->assignedToChoise)) { $data->where('owned_by_id', $request->assignedToChoise); } if (isset($request->user_ext)) { $data->where('ticket_owner_id', $request->user_ext); } $total_tickets = $data->count(); $total_new_tickets = (clone $data)->where('ticket_status_id', 1)->count(); $total_progress_tickets = (clone $data)->where('ticket_status_id', 2)->count(); $total_closed_tickets = (clone $data)->where('ticket_status_id', 4)->count(); $total_wait_tickets = (clone $data)->where('ticket_status_id', 3)->count(); $closed_ticket_percentage = $total_tickets > 0 ? ($total_closed_tickets / $total_tickets) * 100 : 0; $average_resolution_seconds = (clone $data) ->where('ticket_status_id', 4) ->selectRaw('AVG(TIMESTAMPDIFF(SECOND, created_at, updated_at)) as avg_resolution_seconds') ->first() ->avg_resolution_seconds; $average_resolution_time = $this->secondsToTimeFormat($average_resolution_seconds); $company_extensions = User::where('company_id', $user->company->id ?? 1)->pluck('id')->toArray(); $agent_names = User::where('company_id', $user->company->id ?? 1); $accounts = Account::all(); $assignedToChoises = FormHelper::assignedToChoises(); foreach ($assignedToChoises as $category => &$items) { foreach ($items as $key => $value) { $parts = explode('-', $key); if (isset($parts[1])) { $items[$parts[1]] = $value; unset($items[$key]); } } } unset($items); if ($user->roles[0]->name == 'user') { $agent_names = $agent_names->where('id', $user->id); $groups = $user->groups; } $agents_options = [ 'Agents' => $agent_names->pluck('name', 'id')->toArray() ]; $accounts_options = [ 'Projects' => $accounts->pluck('name', 'id')->toArray() ]; $assignedToChoises_options = [ 'assignedToChoises' => $assignedToChoises ]; $agent_names = $agent_names->pluck('id', 'name')->toArray(); $filter_area = view('reporting::widgets.filter_area'); $filter_area->with('conds', $conds); $filter_area->with('agents_options', $agents_options); $filter_area->with('accounts_options', $accounts_options); $filter_area->with('assignedToChoises_options', $assignedToChoises_options); $filter_area->with('user_ext', $request->user_ext); $filter_area->with('account', $request->account); $filter_area->with('assignedToChoise', $request->assignedToChoise); $filter_area->with('submit_route', route('tickets.tickets.report')); $view = view('tickets::report'); $view->with('total_tickets', $total_tickets); $view->with('total_new_tickets', $total_new_tickets); $view->with('total_progress_tickets', $total_progress_tickets); $view->with('total_wait_tickets', $total_wait_tickets); $view->with('total_closed_tickets', $total_closed_tickets); $view->with('closed_ticket_percentage', $closed_ticket_percentage); $view->with('average_resolution_time', $average_resolution_time); $view->with('conds', $conds); $view->with('filter_area', $filter_area); $view->with('agent_names', $agent_names); $dailyStatusCounts = (clone $data) ->select( \DB::raw('DATE(created_at) as date'), \DB::raw('COUNT(CASE WHEN ticket_status_id = 1 THEN 1 END) as new_count'), \DB::raw('COUNT(CASE WHEN ticket_status_id = 2 THEN 1 END) as progress_count'), \DB::raw('COUNT(CASE WHEN ticket_status_id = 3 THEN 1 END) as wait_count'), \DB::raw('COUNT(CASE WHEN ticket_status_id = 4 THEN 1 END) as closed_count'), \DB::raw('COUNT(*) as total') ) ->groupBy('date') ->orderBy('date') ->get(); $period = new \DatePeriod( new \DateTime($conds['from']), new \DateInterval('P1D'), new \DateTime($conds['to']) ); $dailyStats = []; foreach ($period as $date) { $formattedDate = $date->format('Y-m-d'); $dayData = $dailyStatusCounts->firstWhere('date', $formattedDate); $dailyStats[] = [ 'date' => $formattedDate, 'new' => $dayData->new_count ?? 0, 'progress' => $dayData->progress_count ?? 0, 'wait' => $dayData->wait_count ?? 0, 'closed' => $dayData->closed_count ?? 0, 'total' => $dayData->total ?? 0, ]; } $dates = array_column($dailyStats, 'date'); $newData = array_column($dailyStats, 'new'); $progressData = array_column($dailyStats, 'progress'); $waitData = array_column($dailyStats, 'wait'); $closedData = array_column($dailyStats, 'closed'); $view->with('dates', $dates); $view->with('newData', $newData); $view->with('progressData', $progressData); $view->with('waitData', $waitData); $view->with('closedData', $closedData); $view->with('dailyStats', $dailyStats); // Replace call type data with ticket category data $categoryCounts = (clone $data) ->leftJoin('tickets_dict_category', 'tickets.ticket_category_id', '=', 'tickets_dict_category.id') ->select('tickets_dict_category.name as category_name', \DB::raw('COUNT(*) as count')) ->groupBy('tickets_dict_category.name') ->pluck('count', 'category_name') ->toArray(); $view->with('categoryData', $categoryCounts); return $view; } private function secondsToTimeFormat($seconds) { $days = floor($seconds / (60 * 60 * 24)); $hours = floor(($seconds % (60 * 60 * 24)) / (60 * 60)); $minutes = floor(($seconds % (60 * 60)) / 60); $seconds = $seconds % 60; return sprintf('%02d:%02d:%02d:%02d', $days, $hours, $minutes, $seconds); } /** * Called by ModuleCrudController::resolveShowFieldsForCurrentUser() on * every show / create / edit request — covers all views, not just show(). */ protected function overrideShowFieldsForCurrentUser(): array { return app(TicketFieldSettingsService::class) ->getShowFields(Auth::user()); } }