'calllogs.calllogs.index', 'create' => 'calllogs.calllogs.create', 'show' => 'calllogs.calllogs.show', 'edit' => 'calllogs.calllogs.edit', 'store' => 'calllogs.calllogs.store', 'update' => 'calllogs.calllogs.update', 'destroy' => 'calllogs.calllogs.destroy', ]; protected $showMassActionButtons = true; protected $permissions = [ 'browse' => 'calllogs.browse', 'create' => 'calllogs.create', 'update' => 'calllogs.update', 'destroy' => 'calllogs.destroy', ]; protected $moduleSettingsLinks = [ ['route' => 'calllogs.status.index', 'label' => 'settings.status'], ['route' => 'calllogs.source.index', 'label' => 'settings.source'], ['route' => 'calllogs.outcome.index', 'label' => 'settings.outcome'], ]; protected $settingsPermission = 'calllogs.settings'; // ── Dynamic show-fields hook ────────────────────────────────────────────── /** * Map each Call Logs 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.status' => 'status', 'settings.source' => 'source', 'settings.outcome' => 'outcome', ]; } /** * Effective field labels from the CRM field-settings service. */ protected function moduleSettingsLinkLabelMap(): array { return app(CallLogFieldSettingsService::class)->getFieldLabelMap(Auth::user()); } /** * Called by ModuleCrudController::resolveShowFieldsForCurrentUser() on * every show / create / edit request — covers all views, not just show(). */ protected function overrideShowFieldsForCurrentUser(): array { return app(CallLogFieldSettingsService::class) ->getShowFields(Auth::user()); } // ── Softphone: pre-fill create form from URL parameters ────────────────── /** * Override create() so that URL query params (linkedid, phone_number, * direction) from the softphone are forwarded to the form as default * values. Also auto-resolves the lead/contact from phone_number. */ public function create(Request $request) { // Auto-resolve lead from phone number when supplied via softphone URL params if ($phone = $request->input('phone_number')) { if (!$request->has('lead_id')) { $lead = \Modules\Leads\Entities\Lead ::where('phone', $phone)->orWhere('mobile', $phone)->first(); if ($lead) $request->merge(['lead_id' => $lead->id]); } if (!$request->has('contact_id') && !$request->has('lead_id')) { $contact = \Modules\Contacts\Entities\Contact ::where('phone', $phone)->orWhere('mobile', $phone)->first(); if ($contact) $request->merge(['contact_id' => $contact->id]); } } return parent::create($request); } /** * Determine whether the current user can view the given call log. * * Users with the "User" role may only view their own inbound/outbound * call logs, regardless of the access_to_all_entity setting. */ private function userCanView(CallLog $callLog): bool { $user = Auth::user(); if ($user->hasRole('admin')) { return true; } if ($user->hasRole('user')) { return $callLog->user_id === $user->id && in_array($callLog->direction, ['inbound', 'outbound'], true); } if ($user->hasRole('company_manager') || $user->access_to_all_entity) { return true; } return $callLog->user_id === $user->id; } /** * Override show() to enforce view-level access control. */ public function show($identifier) { $callLog = CallLog::find($identifier); if (!$callLog || !$this->userCanView($callLog)) { flash(trans('core::core.you_dont_have_access'))->error(); return redirect()->route($this->routes['index']); } return parent::show($identifier); } /** * Override edit() to enforce view-level access control. */ public function edit($identifier) { $callLog = CallLog::find($identifier); if (!$callLog || !$this->userCanView($callLog)) { flash(trans('core::core.you_dont_have_access'))->error(); return redirect()->route($this->routes['index']); } return parent::edit($identifier); } /** * Override store() to auto-assign user_id and company_id from the * authenticated agent and auto-resolve lead/contact from phone_number. * * If the request contains a linkedid that is already present in the Redis * index, the existing record is updated instead of creating a duplicate. * Only a newly created CallLog is written back to the Redis index; the * regular update flow never touches Redis. */ public function store() { // Coerce integer fields that cannot be null before the parent inserts $request = request(); if ($request->has('duration') && $request->input('duration') === '') { $request->merge(['duration' => 0]); } if ($request->has('talktime') && $request->input('talktime') === '') { $request->merge(['talktime' => 0]); } if ($request->has('ringtime') && $request->input('ringtime') === '') { $request->merge(['ringtime' => 0]); } // Never allow manual call_date, phone_number, direction — populated by CDR sync $request->request->remove('call_date'); $request->request->remove('phone_number'); $request->request->remove('direction'); // Check the Redis index (never the database) for a CallLog created for this linkedid if ($request->filled('linkedid')) { $existing = CallLogSyncService::findByLinkedid($request->input('linkedid')); if ($existing) { $storeValues = $this->form($this->formClass)->getFieldValues(true); unset($storeValues['_method'], $storeValues['_token'], $storeValues['owned_by']); unset($storeValues['entityCreateMode'], $storeValues['relationType']); unset($storeValues['relatedField'], $storeValues['relatedEntityId']); unset($storeValues['relatedEntity'], $storeValues['api_key']); foreach ($storeValues as $field => $value) { if ($value === '') { $storeValues[$field] = null; } } $existing->fill($storeValues); $existing->user_id = Auth::user()->id; $existing->save(); $mode = $request->get('entityCreateMode', ModuleCrudController::FORM_MODE_FULL); if ($mode === ModuleCrudController::FORM_MODE_SIMPLE) { return response()->json([ 'type' => 'success', 'message' => trans('core::core.entity.updated'), 'action' => 'refresh_datatable', ]); } flash(trans('core::core.entity.updated'))->success(); return redirect(route($this->routes['index'])); } } $response = parent::store(); // Stamp user_id automatically — never surfaced in the form $user = Auth::user(); $latest = CallLog::whereNull('user_id')->orderBy('id', 'desc')->first(); if ($latest) { $latest->update(['user_id' => $user->id]); // Index the newly created CallLog so the call flows can resolve it // by linkedid through Redis. This is the only Redis write performed // by the controller; plain form updates never touch Redis. if ($latest->linkedid) { CallLogSyncService::rememberId($latest->linkedid, $latest->getKey()); } } return $response; } }