*/ public array $riaperturaNote = []; /** @var array */ private array $rubricaPhoneCache = []; /** @var array */ private array $pbxExtensionCache = []; public function getPostItInserimentoUrl(): string { return PostIt::getUrl(panel: 'admin-filament'); } public function convertiInTicket(int $postItId): void { if (! $this->isPostItTableReady()) { Notification::make() ->title('Tabella chiamate non pronta') ->body('Esegui prima le migrazioni per usare la gestione Post-it.') ->danger() ->send(); return; } $postIt = ChiamataPostIt::query()->find($postItId); if (! $postIt) { return; } if ($postIt->ticket_id) { Notification::make()->title('Gia convertito in ticket')->warning()->send(); return; } if (! $postIt->stabile_id) { Notification::make() ->title('Stabile mancante') ->body('Associa prima uno stabile al Post-it per poter creare il ticket.') ->danger() ->send(); return; } $ticket = Ticket::query()->create([ 'stabile_id' => $postIt->stabile_id, 'aperto_da_user_id' => Auth::id(), 'titolo' => $postIt->oggetto ?: ('Chiamata da ' . ($postIt->nome_chiamante ?: 'Contatto sconosciuto')), 'descrizione' => trim(($postIt->nota ?? '') . "\n\nRiferimento chiamata: #{$postIt->id}"), 'data_apertura' => now(), 'stato' => 'Aperto', 'priorita' => $postIt->priorita, ]); $postIt->ticket_id = $ticket->id; $postIt->stato = 'ticket'; $postIt->save(); Notification::make() ->title('Ticket creato') ->body("Ticket #{$ticket->id} creato dal Post-it #{$postIt->id}") ->success() ->send(); } public function chiudiPostIt(int $postItId): void { if (! $this->isPostItTableReady()) { return; } $postIt = ChiamataPostIt::query()->find($postItId); if (! $postIt) { return; } $postIt->stato = 'chiusa'; $postIt->save(); Notification::make() ->title('Post-it chiuso') ->success() ->send(); } public function riapriPostIt(int $postItId): void { if (! $this->isPostItTableReady()) { return; } $postIt = ChiamataPostIt::query()->find($postItId); if (! $postIt) { return; } $noteInput = trim((string) ($this->riaperturaNote[$postItId] ?? '')); $stamp = now()->format('d/m/Y H:i'); $noteLine = $noteInput !== '' ? '[' . $stamp . '] Riapertura: ' . $noteInput : '[' . $stamp . '] Riapertura da gestione Post-it'; $existing = trim((string) ($postIt->nota ?? '')); $postIt->nota = $existing !== '' ? ($existing . "\n" . $noteLine) : $noteLine; $postIt->stato = $postIt->ticket_id ? 'ticket' : 'post_it'; if (Schema::hasColumn('chiamate_post_it', 'chiusa_il')) { $postIt->chiusa_il = null; } $postIt->save(); if ($postIt->ticket_id) { $ticket = Ticket::query()->find((int) $postIt->ticket_id); if ($ticket && in_array((string) $ticket->stato, ['Chiuso', 'Risolto'], true)) { $ticket->stato = 'Preso in Carico'; if (Schema::hasColumn('tickets', 'data_chiusura_effettiva')) { $ticket->data_chiusura_effettiva = null; } if (Schema::hasColumn('tickets', 'data_risoluzione_effettiva')) { $ticket->data_risoluzione_effettiva = null; } $ticket->save(); if (method_exists($ticket, 'messages') && Schema::hasTable('ticket_messages')) { $ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => $noteLine, ]); } } } unset($this->riaperturaNote[$postItId]); Notification::make() ->title('Post-it riaperto') ->body('Riapertura completata con nota di tracciamento.') ->success() ->send(); } public function getRecentiProperty() { if (! $this->isPostItTableReady()) { return collect(); } try { return ChiamataPostIt::query() ->with(['rubrica', 'stabile', 'ticket', 'creatoDa']) ->orderByDesc('chiamata_il') ->limit(50) ->get(); } catch (QueryException) { return collect(); } } public function getChiamateTecnicheProperty() { if (! Schema::hasTable('communication_messages')) { return collect(); } try { $query = CommunicationMessage::query() ->with(['assignedUser:id,name', 'stabile:id,denominazione']) ->where('channel', 'smdr') ->latest('id'); if ($this->tecnicoCallView === 'interne') { $query->where('direction', 'internal'); } elseif ($this->tecnicoCallView === 'esterne') { $query->where('direction', '!=', 'internal'); } if (in_array($this->tecnicoDirectionFilter, ['inbound', 'outbound', 'internal'], true)) { $query->where('direction', $this->tecnicoDirectionFilter); } $search = trim($this->tecnicoSearch); if ($search !== '') { $query->where(function ($q) use ($search): void { $q->where('phone_number', 'like', '%' . $search . '%') ->orWhere('target_extension', 'like', '%' . $search . '%') ->orWhere('message_text', 'like', '%' . $search . '%') ->orWhere('sender_name', 'like', '%' . $search . '%'); }); } $items = $query ->limit(250) ->get(); if (in_array($this->tecnicoScopeFilter, ['studio', 'esterno', 'interno'], true)) { $items = $items->filter(fn(CommunicationMessage $message): bool => $this->getTecnicoScope($message) === $this->tecnicoScopeFilter)->values(); } return $items; } catch (QueryException) { return collect(); } } public function creaPostItDaMessaggio(int $messageId): void { if (! $this->isPostItTableReady()) { Notification::make()->title('Tabella post-it non pronta')->danger()->send(); return; } $message = CommunicationMessage::query()->find($messageId); if (! $message) { return; } if (! empty($message->post_it_id)) { Notification::make()->title('Post-it giĆ  associato')->warning()->send(); return; } $smdr = (array) data_get($message->metadata, 'smdr', []); $line = trim((string) ($message->message_text ?? '')); $postIt = ChiamataPostIt::query()->create([ 'stabile_id' => $message->stabile_id, 'creato_da_user_id' => Auth::id(), 'telefono' => $message->phone_number, 'nome_chiamante' => 'SMDR ' . strtoupper((string) $message->direction), 'direzione' => $this->normalizePostItDirection((string) $message->direction), 'origine' => 'smdr_table', 'origine_id' => (string) $message->id, 'durata_secondi' => is_numeric($smdr['duration_seconds'] ?? null) ? (int) $smdr['duration_seconds'] : null, 'oggetto' => 'Chiamata da pannello tecnico SMDR', 'nota' => $line !== '' ? $line : 'Evento SMDR senza testo riga', 'priorita' => 'Media', 'stato' => 'post_it', 'chiamata_il' => $message->received_at ?? now(), ]); $message->post_it_id = $postIt->id; $message->save(); Notification::make() ->title('Post-it creato da riga tecnica') ->body('Post-it #' . $postIt->id . ' collegato al messaggio #' . $message->id) ->success() ->send(); } private function normalizePostItDirection(string $direction): string { $d = strtolower(trim($direction)); if ($d === 'inbound') { return 'in_arrivo'; } if ($d === 'outbound' || $d === 'internal') { return 'in_uscita'; } return 'in_arrivo'; } public function isPostItTableReady(): bool { return Schema::hasTable('chiamate_post_it'); } public function getRubricaUrlByPhone(?string $phone): ?string { $rubricaId = $this->resolveRubricaIdByPhone($phone); if (! $rubricaId) { return null; } return RubricaUniversaleScheda::getUrl(['record' => $rubricaId], panel: 'admin-filament'); } public function getRubricaNomeByPhone(?string $phone): ?string { $rubricaId = $this->resolveRubricaIdByPhone($phone); if (! $rubricaId) { return null; } $rubrica = RubricaUniversale::query()->find($rubricaId, ['id', 'ragione_sociale', 'nome', 'cognome']); if (! $rubrica) { return null; } $nome = trim((string) ($rubrica->ragione_sociale ?: ((string) ($rubrica->nome ?? '') . ' ' . (string) ($rubrica->cognome ?? '')))); return $nome !== '' ? $nome : null; } public function getTecnicoNominativo(CommunicationMessage $message): ?string { $extension = $this->extractExtensionFromMessage($message); if ($this->isInternalSmdrMessage($message)) { return $this->getCollaboratoreNomeByExtension($extension); } $phone = (string) ($message->phone_number ?: data_get($message->metadata, 'smdr.dial_number', '')); return $this->getRubricaNomeByPhone($phone); } public function getCollaboratoreNomeByExtension(?string $extension): ?string { $normalized = $this->normalizeExtension($extension); if ($normalized === null) { return null; } if (array_key_exists($normalized, $this->pbxExtensionCache)) { return $this->pbxExtensionCache[$normalized]['name']; } if (! Schema::hasColumn('users', 'pbx_extension')) { $this->pbxExtensionCache[$normalized] = ['id' => null, 'name' => null]; return null; } $user = User::query() ->select(['id', 'name', 'pbx_extension']) ->where('pbx_extension', $normalized) ->first(); $result = [ 'id' => $user ? (int) $user->id : null, 'name' => $user ? trim((string) $user->name) : null, ]; $this->pbxExtensionCache[$normalized] = $result; return $result['name']; } public function isInternalSmdrMessage(CommunicationMessage $message): bool { if ((string) $message->direction === 'internal') { return true; } $extension = $this->extractExtensionFromMessage($message); $phone = preg_replace('/\D+/', '', (string) ($message->phone_number ?: data_get($message->metadata, 'smdr.dial_number', ''))); return $extension !== null && $phone !== '' && $phone === $extension; } public function getTecnicoInternoLabel(CommunicationMessage $message): string { $extension = $this->extractExtensionFromMessage($message); if ($extension === null) { return '-'; } $name = $this->getCollaboratoreNomeByExtension($extension); if ($name === null) { return $extension; } return $extension . ' - ' . $name; } public function getTecnicoScope(CommunicationMessage $message): string { if ($this->isInternalSmdrMessage($message)) { return 'interno'; } if ($this->isManagedStudioNumberMessage($message)) { return 'studio'; } return 'esterno'; } public function getTecnicoScopeLabel(CommunicationMessage $message): string { return match ($this->getTecnicoScope($message)) { 'interno' => 'Interno collaboratore', 'studio' => 'Numero studio gestito', default => 'Numero esterno', }; } private function resolveRubricaIdByPhone(?string $phone): ?int { $digits = preg_replace('/\D+/', '', (string) $phone); if (! is_string($digits) || $digits === '') { return null; } if ($this->isKnownPbxExtension($digits)) { return null; } if (array_key_exists($digits, $this->rubricaPhoneCache)) { return $this->rubricaPhoneCache[$digits]; } $adminId = (int) (Auth::user()?->amministratore?->id ?? 0); $query = RubricaUniversale::query(); if ($adminId > 0 && Schema::hasColumn('rubrica_universale', 'amministratore_id')) { $query->where(function ($q) use ($adminId): void { $q->where('amministratore_id', $adminId) ->orWhereNull('amministratore_id'); }); } $rubricaId = $query ->where(function ($q) use ($digits): void { $q->orWhereRaw("REPLACE(REPLACE(REPLACE(COALESCE(telefono_ufficio,''), ' ', ''), '+', ''), '-', '') LIKE ?", ['%' . $digits . '%']) ->orWhereRaw("REPLACE(REPLACE(REPLACE(COALESCE(telefono_cellulare,''), ' ', ''), '+', ''), '-', '') LIKE ?", ['%' . $digits . '%']) ->orWhereRaw("REPLACE(REPLACE(REPLACE(COALESCE(telefono_casa,''), ' ', ''), '+', ''), '-', '') LIKE ?", ['%' . $digits . '%']); }) ->orderByDesc('id') ->value('id'); $result = is_numeric($rubricaId) ? (int) $rubricaId : null; $this->rubricaPhoneCache[$digits] = $result; return $result; } private function extractExtensionFromMessage(CommunicationMessage $message): ?string { $extension = (string) ($message->target_extension ?: data_get($message->metadata, 'smdr.extension', '')); return $this->normalizeExtension($extension); } private function normalizeExtension(?string $extension): ?string { $digits = preg_replace('/\D+/', '', (string) $extension); return is_string($digits) && $digits !== '' ? $digits : null; } private function isKnownPbxExtension(string $digits): bool { if (strlen($digits) > 6 || ! Schema::hasColumn('users', 'pbx_extension')) { return false; } return User::query()->where('pbx_extension', $digits)->exists(); } private function isManagedStudioNumberMessage(CommunicationMessage $message): bool { $phoneDigits = preg_replace('/\D+/', '', (string) ($message->phone_number ?: data_get($message->metadata, 'smdr.dial_number', ''))); if (! is_string($phoneDigits) || $phoneDigits === '') { return false; } return in_array($phoneDigits, $this->getManagedStudioNumbers(), true); } /** @return array */ private function getManagedStudioNumbers(): array { $user = Auth::user(); if (! $user) { return []; } $impostazioni = (array) (($user->amministratore?->impostazioni ?? [])); $centralino = (array) ($impostazioni['centralino'] ?? []); $numbers = []; foreach (['numero_principale', 'numero_backup', 'numero_emergenza'] as $key) { $digits = preg_replace('/\D+/', '', (string) ($centralino[$key] ?? '')); if (is_string($digits) && $digits !== '') { $numbers[] = $digits; } } return array_values(array_unique($numbers)); } public static function canAccess(): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } }