netgescon-day0/app/Filament/Pages/Strumenti/PostItGestione.php

888 lines
31 KiB
PHP

<?php
namespace App\Filament\Pages\Strumenti;
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
use App\Models\ChiamataPostIt;
use App\Models\CommunicationMessage;
use App\Models\PbxClickToCallRequest;
use App\Models\RubricaUniversale;
use App\Models\Ticket;
use App\Models\User;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use UnitEnum;
class PostItGestione extends Page
{
protected static ?string $title = 'Post-it Gestione';
protected static ?string $navigationLabel = 'Post-it Gestione';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-clipboard-document-list';
protected static ?string $slug = 'strumenti/post-it-gestione';
protected static bool $shouldRegisterNavigation = true;
protected static UnitEnum|string|null $navigationGroup = 'Strumenti';
protected static ?int $navigationSort = 11;
protected string $view = 'filament.pages.strumenti.post-it-gestione';
public ?int $focusPostItId = null;
public string $activeTab = 'storico';
public string $tecnicoCallView = 'esterne';
public string $tecnicoDirectionFilter = 'tutte';
public string $tecnicoSearch = '';
public string $tecnicoLineFilter = '';
public string $tecnicoScopeFilter = 'tutti';
/** @var array<int,string> */
public array $riaperturaNote = [];
/** @var array<int,string> */
public array $conversioneNote = [];
/** @var array<string, int|null> */
private array $rubricaPhoneCache = [];
/** @var array<string, array{id:int|null,name:string|null}> */
private array $pbxExtensionCache = [];
/** @var array<int, CommunicationMessage|null> */
private array $postItMessageCache = [];
public function mount(): void
{
$focus = (int) request()->query('focus_post_it', 0);
$this->focusPostItId = $focus > 0 ? $focus : null;
if (in_array((string) request()->query('tab'), ['storico', 'lavagna'], true)) {
$this->activeTab = (string) request()->query('tab');
}
}
public function getPostItInserimentoUrl(): string
{
return PostIt::getUrl(panel: 'admin-filament');
}
public function getPostItPageUrl(int $postItId): string
{
return PostIt::getUrl(panel: 'admin-filament') . '?focus_post_it=' . $postItId;
}
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' => $this->buildTicketDescriptionFromPostIt($postItId, $postIt),
'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();
unset($this->conversioneNote[$postItId]);
}
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 {
$query = ChiamataPostIt::query()
->with(['rubrica', 'stabile', 'ticket', 'creatoDa', 'assegnatoAUser:id,name', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome'])
->orderByDesc('chiamata_il')
->orderByDesc('id');
if ($this->focusPostItId) {
$query->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [$this->focusPostItId]);
}
return $query->limit(80)->get();
} catch (QueryException) {
return collect();
}
}
public function getRecentiVisibiliProperty()
{
return $this->recenti
->filter(fn(ChiamataPostIt $postIt): bool => ! $this->isInternalPostIt($postIt))
->values();
}
public function getRecentiRaggruppatiProperty()
{
$groups = [];
foreach ($this->recentiVisibili as $postIt) {
if ($this->isInternalPostIt($postIt)) {
continue;
}
$groupKey = $this->buildGroupedPostItKey($postIt);
if (isset($groups[$groupKey])) {
$groups[$groupKey]['items'][] = $postIt;
$groups[$groupKey]['count']++;
$groups[$groupKey]['last_at'] = optional($postIt->chiamata_il)->format('d/m/Y H:i') ?: '-';
$groups[$groupKey]['latest'] = $this->resolveLatestGroupedPostIt($groups[$groupKey]['items']);
$groups[$groupKey]['caller_label'] = $this->resolveGroupedCallerLabel($groups[$groupKey]['items']);
continue;
}
$groups[$groupKey] = [
'key' => $groupKey,
'items' => [$postIt],
'count' => 1,
'latest' => $postIt,
'caller_label' => $postIt->nome_chiamante ?: 'Chiamante non identificato',
'phone' => (string) ($postIt->telefono ?? ''),
'first_at' => optional($postIt->chiamata_il)->format('d/m/Y H:i') ?: '-',
'last_at' => optional($postIt->chiamata_il)->format('d/m/Y H:i') ?: '-',
];
}
return collect(array_values($groups))
->map(function (array $group): array {
usort($group['items'], function (ChiamataPostIt $left, ChiamataPostIt $right): int {
return ($right->chiamata_il?->getTimestamp() ?? 0) <=> ($left->chiamata_il?->getTimestamp() ?? 0);
});
$group['latest'] = $this->resolveLatestGroupedPostIt($group['items']);
$oldest = collect($group['items'])
->sortBy(fn(ChiamataPostIt $item) => $item->chiamata_il?->getTimestamp() ?? 0)
->first();
$group['first_at'] = optional($oldest?->chiamata_il)->format('d/m/Y H:i') ?: '-';
$group['last_at'] = optional($group['latest']->chiamata_il)->format('d/m/Y H:i') ?: '-';
return $group;
})
->sortByDesc(fn(array $group) => $group['latest']->chiamata_il?->getTimestamp() ?? 0)
->values();
}
public function getChiamateTecnicheProperty()
{
if (! Schema::hasTable('communication_messages')) {
return collect();
}
try {
$query = CommunicationMessage::query()
->with(['assignedUser:id,name', 'stabile:id,denominazione'])
->latest('id');
if ($this->activeTab === 'smdr') {
$query->where('channel', 'smdr');
} elseif ($this->activeTab === 'csta') {
$query->where('channel', 'panasonic_csta');
} else {
$query->whereIn('channel', ['smdr', 'panasonic_csta']);
}
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();
}
$lineFilter = trim($this->tecnicoLineFilter);
if ($lineFilter !== '') {
$items = $items->filter(fn(CommunicationMessage $message): bool => $this->matchesTecnicoLineFilter($message, $lineFilter))->values();
}
return $items;
} catch (QueryException) {
return collect();
}
}
public function getRecentClickToCallRequestsProperty()
{
if (! Schema::hasTable('pbx_click_to_call_requests')) {
return collect();
}
$user = Auth::user();
if (! $user instanceof User) {
return collect();
}
$extension = $this->normalizeExtension((string) ($user->pbx_extension ?? ''));
return PbxClickToCallRequest::query()
->when($extension !== '', function ($query) use ($extension, $user): void {
$query->where(function ($inner) use ($extension, $user): void {
$inner->where('source_extension', $extension)
->orWhere('requested_by_user_id', (int) $user->id);
});
}, fn($query) => $query->where('requested_by_user_id', (int) $user->id))
->orderByDesc('requested_at')
->orderByDesc('id')
->limit(20)
->get();
}
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;
}
$sourceKey = (string) ($message->channel ?: 'cti');
$sourceLabel = $sourceKey === 'panasonic_csta' ? 'Panasonic CTI' : 'SMDR';
$smdr = (array) data_get($message->metadata, 'smdr', []);
$line = trim((string) ($message->message_text ?? ''));
$phone = (string) ($message->phone_number ?: ($smdr['dial_number'] ?? ''));
$nominativo = $this->getTecnicoNominativo($message);
$lineLabel = $this->getTecnicoLineaLabel($message);
$direction = match (strtolower(trim((string) $message->direction))) {
'inbound' => 'Chiamata ricevuta',
'outbound' => 'Chiamata effettuata',
'internal' => 'Chiamata interna',
default => 'Evento telefonico',
};
$oggetto = $direction;
if ($lineLabel !== '-') {
$oggetto .= (string) $message->channel === 'smdr'
? ' - linea ' . $lineLabel
: ' - ' . $lineLabel;
}
$notaParts = [];
if ($phone !== '') {
$notaParts[] = 'Numero: ' . $phone;
}
if ($nominativo) {
$notaParts[] = 'Contatto: ' . $nominativo;
}
if ($line !== '') {
$notaParts[] = 'Dettaglio sorgente: ' . $line;
}
$postIt = ChiamataPostIt::query()->create([
'stabile_id' => $message->stabile_id,
'creato_da_user_id' => Auth::id(),
'telefono' => $message->phone_number,
'nome_chiamante' => $nominativo ?: ($phone !== '' ? $phone : ($sourceLabel . ' ' . strtoupper((string) $message->direction))),
'direzione' => $this->normalizePostItDirection((string) $message->direction),
'origine' => $sourceKey . '_table',
'origine_id' => (string) $message->id,
'durata_secondi' => is_numeric($smdr['duration_seconds'] ?? null) ? (int) $smdr['duration_seconds'] : null,
'oggetto' => $oggetto,
'nota' => $notaParts !== [] ? implode("\n", $notaParts) : ('Evento ' . $sourceLabel . ' senza dettagli operativi'),
'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 getTecnicoProviderLabel(CommunicationMessage $message): string
{
$provider = strtolower(trim((string) data_get($message->metadata, 'provider', '')));
if ($provider === '') {
$provider = (string) $message->channel;
}
return match ($provider) {
'smdr' => 'SMDR',
'panasonic_csta' => 'CSTA',
'panasonic_ns1000' => 'Panasonic NS1000',
'freepbx' => 'FreePBX',
default => strtoupper(str_replace('_', ' ', $provider)),
};
}
public function getTecnicoLineaLabel(CommunicationMessage $message): string
{
if ((string) $message->channel === 'smdr') {
$line = trim((string) data_get($message->metadata, 'smdr.co', ''));
return $line !== '' ? $line : '-';
}
$eventType = trim((string) data_get($message->metadata, 'event_type', ''));
$providerCallId = trim((string) data_get($message->metadata, 'provider_call_id', ''));
$parts = array_values(array_filter([$eventType, $providerCallId], fn(string $value): bool => $value !== ''));
return $parts !== [] ? implode(' / ', $parts) : '-';
}
private function matchesTecnicoLineFilter(CommunicationMessage $message, string $filter): bool
{
$needle = mb_strtolower(trim($filter));
if ($needle === '') {
return true;
}
$haystacks = [
mb_strtolower($this->getTecnicoLineaLabel($message)),
mb_strtolower((string) ($message->target_extension ?? '')),
mb_strtolower((string) data_get($message->metadata, 'smdr.co', '')),
mb_strtolower((string) data_get($message->metadata, 'provider_call_id', '')),
mb_strtolower((string) ($message->message_text ?? '')),
];
foreach ($haystacks as $haystack) {
if ($haystack !== '' && str_contains($haystack, $needle)) {
return true;
}
}
return false;
}
private function buildTicketDescriptionFromPostIt(int $postItId, ChiamataPostIt $postIt): string
{
$parts = [];
$nota = trim((string) ($postIt->nota ?? ''));
if ($nota !== '') {
$parts[] = $nota;
}
$conversioneNote = trim((string) ($this->conversioneNote[$postItId] ?? ''));
if ($conversioneNote !== '') {
$parts[] = 'Nota operatore prima della conversione:' . "\n" . $conversioneNote;
}
$parts[] = 'Riferimento chiamata: #' . $postIt->id;
return implode("\n\n", $parts);
}
private function buildGroupedPostItKey(ChiamataPostIt $postIt): string
{
$phone = $this->normalizeGroupedPhone((string) ($postIt->telefono ?? ''));
if ($phone !== '') {
return 'phone:' . $phone;
}
return 'caller:' . mb_strtolower(trim((string) ($postIt->nome_chiamante ?? 'sconosciuto')));
}
private function isInternalPostIt(ChiamataPostIt $postIt): bool
{
$message = $this->resolveMessageFromPostIt($postIt);
if ($message instanceof CommunicationMessage) {
return $this->isInternalSmdrMessage($message);
}
return false;
}
private function resolveMessageFromPostIt(ChiamataPostIt $postIt): ?CommunicationMessage
{
$originId = (int) ($postIt->origine_id ?? 0);
if ($originId <= 0) {
return null;
}
if (array_key_exists($originId, $this->postItMessageCache)) {
return $this->postItMessageCache[$originId];
}
$message = CommunicationMessage::query()->find($originId);
$this->postItMessageCache[$originId] = $message;
return $message;
}
private function normalizeGroupedPhone(string $phone): string
{
return preg_replace('/\D+/', '', $phone) ?: '';
}
/**
* @param array<int,ChiamataPostIt> $items
*/
private function resolveLatestGroupedPostIt(array $items): ChiamataPostIt
{
usort($items, function (ChiamataPostIt $left, ChiamataPostIt $right): int {
return ($right->chiamata_il?->getTimestamp() ?? 0) <=> ($left->chiamata_il?->getTimestamp() ?? 0);
});
return $items[0];
}
/**
* @param array<int,ChiamataPostIt> $items
*/
private function resolveGroupedCallerLabel(array $items): string
{
foreach ($items as $item) {
$label = trim((string) ($item->nome_chiamante ?? ''));
if ($label !== '') {
return $label;
}
}
return 'Chiamante non identificato';
}
public function richiediClickToCallDaMessaggio(int $messageId): void
{
$user = Auth::user();
if (! $user instanceof User) {
return;
}
$extension = $this->normalizeExtension((string) ($user->pbx_extension ?? ''));
if (! (bool) ($user->pbx_click_to_call_enabled ?? false) || $extension === '') {
Notification::make()->title('Click-to-call non abilitato per il tuo utente')->warning()->send();
return;
}
$message = CommunicationMessage::query()->find($messageId);
if (! $message) {
Notification::make()->title('Messaggio tecnico non trovato')->warning()->send();
return;
}
$phone = preg_replace('/\D+/', '', (string) ($message->phone_number ?: data_get($message->metadata, 'smdr.dial_number', '')));
if (! is_string($phone) || $phone === '') {
Notification::make()->title('Numero non valido')->warning()->send();
return;
}
PbxClickToCallRequest::query()->create([
'requested_by_user_id' => (int) $user->id,
'assigned_user_id' => (int) $user->id,
'stabile_id' => $message->stabile_id,
'communication_message_id' => (int) $message->id,
'source_extension' => $extension,
'target_number' => $phone,
'status' => 'pending',
'note' => 'Richiesta da Post-it Gestione [' . $this->getTecnicoProviderLabel($message) . ']',
'requested_at' => now(),
'metadata' => [
'requested_from' => 'post_it_gestione',
'source_channel' => (string) $message->channel,
'provider' => (string) data_get($message->metadata, 'provider', $message->channel),
'target_extension' => (string) ($message->target_extension ?? ''),
],
]);
Notification::make()
->title('Richiesta click-to-call registrata')
->body('Interno ' . $extension . ' -> ' . $phone)
->success()
->send();
}
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<int,string> */
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']);
}
}