541 lines
18 KiB
PHP
541 lines
18 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\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 string $activeTab = 'storico';
|
|
|
|
public string $tecnicoCallView = 'esterne';
|
|
|
|
public string $tecnicoDirectionFilter = 'tutte';
|
|
|
|
public string $tecnicoSearch = '';
|
|
|
|
public string $tecnicoScopeFilter = 'tutti';
|
|
|
|
/** @var array<int,string> */
|
|
public array $riaperturaNote = [];
|
|
|
|
/** @var array<string, int|null> */
|
|
private array $rubricaPhoneCache = [];
|
|
|
|
/** @var array<string, array{id:int|null,name:string|null}> */
|
|
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'])
|
|
->whereIn('channel', ['smdr', 'panasonic_csta'])
|
|
->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;
|
|
}
|
|
|
|
$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 ?? ''));
|
|
|
|
$postIt = ChiamataPostIt::query()->create([
|
|
'stabile_id' => $message->stabile_id,
|
|
'creato_da_user_id' => Auth::id(),
|
|
'telefono' => $message->phone_number,
|
|
'nome_chiamante' => $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' => 'Chiamata da pannello tecnico ' . $sourceLabel,
|
|
'nota' => $line !== '' ? $line : ('Evento ' . $sourceLabel . ' 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<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']);
|
|
}
|
|
}
|