netgescon-day0/app/Filament/Pages/Supporto/TicketGestione.php

893 lines
31 KiB
PHP

<?php
namespace App\Filament\Pages\Supporto;
use App\Models\CategoriaTicket;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\TicketIntervento;
use App\Models\User;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\WithFileUploads;
use UnitEnum;
class TicketGestione extends Page
{
use WithFileUploads;
protected static ?string $navigationLabel = 'Ticket Gestione';
protected static ?string $title = 'Gestione Ticket';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-clipboard-document-list';
protected static UnitEnum|string|null $navigationGroup = 'Supporto';
protected static ?int $navigationSort = 26;
protected static ?string $slug = 'supporto/ticket-gestione';
protected string $view = 'filament.pages.supporto.ticket-gestione';
public string $status = 'open';
public string $activeTab = 'elenco';
public ?int $selectedTicketId = null;
public ?string $notaInterna = null;
public ?int $fornitoreId = null;
public ?string $noteAssegnazione = null;
/** @var array<int,mixed> */
public array $nuoviAllegati = [];
/** @var array<int,mixed> */
public array $nuoveFoto = [];
public ?string $descrizioneAllegati = null;
public ?string $lastGeneratedPassword = null;
public ?int $selectedCategoriaId = null;
public ?string $categoriaNome = null;
public ?string $categoriaDescrizione = null;
/** @var array<int,array{id:int,nome:string,descrizione:string}> */
public array $categorieOptions = [];
/** @var array<int,array{id:int,nome:string}> */
public array $fornitoriOptions = [];
/** @var array<int,array<string,mixed>> */
public array $fornitoriAttiviRows = [];
/** @var array<string,mixed>|null */
public ?array $attachmentPreview = null;
/** @var \Illuminate\Support\Collection<int, Ticket> */
public $tickets;
/** @var array<string,int> */
public array $ticketCounters = [
'open' => 0,
'urgent' => 0,
'closed' => 0,
'all' => 0,
];
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
}
public function mount(): void
{
$this->tickets = collect();
$this->selectedTicketId = (int) request()->query('ticket', 0) ?: null;
$queryTab = (string) request()->query('tab', '');
if ($queryTab === 'scheda') {
$this->activeTab = 'scheda';
}
if (request()->query('status') === 'all') {
$this->status = 'all';
}
$this->loadFornitoriOptions();
$this->loadCategorieTicketOptions();
$this->refreshFornitoriAttiviRows();
if ($this->selectedTicket) {
$this->fornitoreId = (int) ($this->selectedTicket->assegnato_a_fornitore_id ?? 0) ?: null;
}
$this->refreshData();
}
public function updatedSelectedCategoriaId($value): void
{
$id = (int) $value;
if ($id <= 0) {
$this->categoriaNome = null;
$this->categoriaDescrizione = null;
return;
}
$categoria = CategoriaTicket::query()->find($id);
if (! $categoria instanceof CategoriaTicket) {
return;
}
$this->categoriaNome = (string) $categoria->nome;
$this->categoriaDescrizione = (string) ($categoria->descrizione ?? '');
}
public function creaCategoriaTicket(): void
{
$this->validate([
'categoriaNome' => ['required', 'string', 'max:255', Rule::unique('categorie_ticket', 'nome')],
'categoriaDescrizione' => ['nullable', 'string', 'max:2000'],
]);
$categoria = CategoriaTicket::query()->create([
'nome' => trim((string) $this->categoriaNome),
'descrizione' => filled($this->categoriaDescrizione) ? trim((string) $this->categoriaDescrizione) : null,
]);
$this->loadCategorieTicketOptions();
$this->selectedCategoriaId = (int) $categoria->id;
$this->updatedSelectedCategoriaId($this->selectedCategoriaId);
Notification::make()->title('Categoria creata')->success()->send();
}
public function aggiornaCategoriaTicket(): void
{
$id = (int) ($this->selectedCategoriaId ?? 0);
if ($id <= 0) {
Notification::make()->title('Seleziona una categoria da modificare')->warning()->send();
return;
}
$this->validate([
'categoriaNome' => ['required', 'string', 'max:255', Rule::unique('categorie_ticket', 'nome')->ignore($id)],
'categoriaDescrizione' => ['nullable', 'string', 'max:2000'],
]);
$categoria = CategoriaTicket::query()->find($id);
if (! $categoria instanceof CategoriaTicket) {
Notification::make()->title('Categoria non trovata')->danger()->send();
return;
}
$categoria->update([
'nome' => trim((string) $this->categoriaNome),
'descrizione' => filled($this->categoriaDescrizione) ? trim((string) $this->categoriaDescrizione) : null,
]);
$this->loadCategorieTicketOptions();
Notification::make()->title('Categoria aggiornata')->success()->send();
$this->refreshData();
}
public function updatedStatus(): void
{
$this->refreshData();
}
public function refreshData(): void
{
$this->loadCounters();
$this->loadTickets();
$this->refreshFornitoriAttiviRows();
}
public function getTicketInserimentoUrl(): string
{
return TicketMobile::getUrl(panel: 'admin-filament');
}
public function apriScheda(int $ticketId): void
{
$this->selectedTicketId = $ticketId;
$this->activeTab = 'scheda';
$ticket = $this->selectedTicket;
$this->fornitoreId = $ticket ? ((int) ($ticket->assegnato_a_fornitore_id ?? 0) ?: null): null;
}
public function apriElenco(): void
{
$this->activeTab = 'elenco';
}
public function getFornitoreOperativoUrl(?int $fornitoreId = null): string
{
$base = route('fornitore.tickets.index');
if ($fornitoreId) {
return $base . '?fornitore=' . $fornitoreId;
}
return $base;
}
public function getFornitoreAnagraficaUrl(int $fornitoreId): string
{
return \App\Filament\Pages\Gescon\FornitoreScheda::getUrl(['record' => $fornitoreId], panel: 'admin-filament');
}
public function getAttachmentPublicUrl(TicketAttachment $attachment): string
{
return route('filament.tickets.attachments.view', ['attachment' => (int) $attachment->id]);
}
public function openAttachmentPreview(int $attachmentId): void
{
$ticket = $this->selectedTicket;
if (! $ticket) {
return;
}
$attachment = $ticket->attachments->firstWhere('id', $attachmentId);
if (! $attachment instanceof TicketAttachment) {
return;
}
$name = (string) ($attachment->original_file_name ?? 'allegato');
$mime = strtolower((string) ($attachment->mime_type ?? ''));
$ext = strtolower((string) pathinfo($name, PATHINFO_EXTENSION));
$isImage = str_starts_with($mime, 'image/') || in_array($ext, ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp'], true);
$isPdf = $mime === 'application/pdf' || $ext === 'pdf';
$this->attachmentPreview = [
'id' => (int) $attachment->id,
'name' => $name,
'description' => (string) ($attachment->description ?? ''),
'url' => $this->getAttachmentPublicUrl($attachment),
'is_image' => $isImage,
'is_pdf' => $isPdf,
];
}
public function closeAttachmentPreview(): void
{
$this->attachmentPreview = null;
}
public function getSelectedTicketProperty(): ?Ticket
{
if (! $this->selectedTicketId) {
return null;
}
$user = Auth::user();
if (! $user instanceof User) {
return null;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
return null;
}
return Ticket::query()
->with([
'stabile:id,denominazione',
'categoriaTicket:id,nome',
'assegnatoAFornitore:id,ragione_sociale,nome,cognome',
'attachments.user:id,name',
'messages.user:id,name',
'interventi.fornitore:id,ragione_sociale,nome,cognome',
'interventi.creatoDaUser:id,name',
])
->where('stabile_id', $stabileId)
->find($this->selectedTicketId);
}
public function assegnaFornitore(): void
{
$ticket = $this->selectedTicket;
if (! $ticket) {
Notification::make()->title('Ticket non trovato')->danger()->send();
return;
}
$this->validate([
'fornitoreId' => ['required', 'integer', 'exists:fornitori,id'],
'noteAssegnazione' => ['nullable', 'string', 'max:4000'],
]);
$ticket->update([
'assegnato_a_fornitore_id' => $this->fornitoreId,
'stato' => in_array($ticket->stato, ['Aperto'], true) ? 'Preso in Carico' : $ticket->stato,
]);
if (Schema::hasTable('ticket_interventi')) {
$ultimo = TicketIntervento::query()
->where('ticket_id', $ticket->id)
->where('fornitore_id', $this->fornitoreId)
->latest('id')
->first();
if (! $ultimo) {
TicketIntervento::query()->create([
'ticket_id' => $ticket->id,
'fornitore_id' => $this->fornitoreId,
'creato_da_user_id' => Auth::id(),
'stato' => 'assegnato',
'note_amministratore' => $this->noteAssegnazione,
]);
}
}
if (filled($this->noteAssegnazione)) {
$ticket->messages()->create([
'user_id' => Auth::id(),
'messaggio' => 'Assegnazione fornitore: ' . trim((string) $this->noteAssegnazione),
]);
}
$this->noteAssegnazione = null;
Notification::make()->title('Fornitore assegnato')->success()->send();
$this->refreshData();
}
public function aggiungiNotaInterna(): void
{
$ticket = $this->selectedTicket;
if (! $ticket) {
Notification::make()->title('Ticket non trovato')->danger()->send();
return;
}
$this->validate([
'notaInterna' => ['required', 'string', 'max:5000'],
]);
$stamp = now()->format('d/m/Y H:i');
$ticket->messages()->create([
'user_id' => Auth::id(),
'messaggio' => '[' . $stamp . '] ' . trim((string) $this->notaInterna),
]);
$this->notaInterna = null;
// Force refresh of selected ticket relations so the new note is visible immediately.
$this->refreshData();
Notification::make()->title('Nota aggiunta')->success()->send();
}
public function caricaAllegati(): void
{
$ticket = $this->selectedTicket;
if (! $ticket) {
Notification::make()->title('Ticket non trovato')->danger()->send();
return;
}
if (! Schema::hasTable('ticket_attachments')) {
Notification::make()->title('Tabella allegati non pronta')->danger()->send();
return;
}
$this->validate([
'nuoviAllegati' => ['nullable', 'array', 'max:8'],
'nuoviAllegati.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx,xls,xlsx,txt'],
'nuoveFoto' => ['nullable', 'array', 'max:8'],
'nuoveFoto.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp'],
'descrizioneAllegati' => ['nullable', 'string', 'max:500'],
]);
$files = array_merge($this->nuoveFoto, $this->nuoviAllegati);
if (count($files) === 0) {
Notification::make()->title('Seleziona almeno un file o una foto')->warning()->send();
return;
}
$saved = 0;
foreach ($files as $file) {
if (! is_object($file) || ! method_exists($file, 'store')) {
continue;
}
$path = $file->store('ticket-gestione/' . $ticket->id, 'public');
TicketAttachment::query()->create([
'ticket_id' => $ticket->id,
'ticket_update_id' => null,
'user_id' => Auth::id(),
'file_path' => $path,
'original_file_name' => (string) $file->getClientOriginalName(),
'mime_type' => (string) $file->getClientMimeType(),
'size' => (int) $file->getSize(),
'description' => $this->descrizioneAllegati ?: 'Allegato da gestione ticket',
]);
$saved++;
}
$this->nuoveFoto = [];
$this->nuoviAllegati = [];
$this->descrizioneAllegati = null;
$this->refreshData();
Notification::make()
->title('Allegati caricati')
->body('Caricati ' . $saved . ' allegati.')
->success()
->send();
}
public function prendiInCarico(int $ticketId): void
{
$this->aggiornaStatoTicket($ticketId, 'Preso in Carico', true);
}
public function avviaLavorazione(int $ticketId): void
{
$this->aggiornaStatoTicket($ticketId, 'In Lavorazione');
}
public function risolviTicket(int $ticketId): void
{
$this->aggiornaStatoTicket($ticketId, 'Risolto');
}
public function chiudiTicket(int $ticketId): void
{
$this->aggiornaStatoTicket($ticketId, 'Chiuso');
}
public function abilitaAccessoFornitoreDaTicket(int $fornitoreId): void
{
$user = Auth::user();
if (! $user instanceof User) {
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Stabile attivo non disponibile')->warning()->send();
return;
}
$stabile = \App\Models\Stabile::query()->find($stabileId);
$adminId = (int) ($stabile->amministratore_id ?? 0);
$fornitore = Fornitore::query()->find($fornitoreId);
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Fornitore non trovato')->danger()->send();
return;
}
if ($adminId > 0 && (int) ($fornitore->amministratore_id ?? 0) > 0 && (int) ($fornitore->amministratore_id ?? 0) !== $adminId) {
Notification::make()->title('Fornitore non valido per questo stabile')->danger()->send();
return;
}
$email = trim((string) ($fornitore->email ?? ''));
if ($email === '') {
Notification::make()->title('Email fornitore mancante')->warning()->send();
return;
}
$dipendente = FornitoreDipendente::query()
->where('fornitore_id', (int) $fornitore->id)
->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])
->first();
if (! $dipendente instanceof FornitoreDipendente) {
$dipendente = FornitoreDipendente::query()->create([
'fornitore_id' => (int) $fornitore->id,
'nome' => (string) ($fornitore->ragione_sociale ?: ($fornitore->nome ?: 'Referente fornitore')),
'cognome' => $fornitore->ragione_sociale ? null : (($fornitore->cognome ?? null) ?: null),
'email' => $email,
'telefono' => $fornitore->telefono ?: $fornitore->cellulare,
'attivo' => true,
'created_by_user_id' => Auth::id(),
'updated_by_user_id' => Auth::id(),
]);
}
$this->abilitaAccessoDipendente($dipendente);
$this->refreshData();
}
public function resetPasswordFornitoreUser(int $userId): void
{
$user = Auth::user();
if (! $user instanceof User) {
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()->title('Stabile attivo non disponibile')->warning()->send();
return;
}
$stabile = \App\Models\Stabile::query()->find($stabileId);
$adminId = (int) ($stabile->amministratore_id ?? 0);
$canReset = FornitoreDipendente::query()
->where('user_id', $userId)
->whereHas('fornitore', function ($q) use ($adminId): void {
if ($adminId > 0) {
$q->where('amministratore_id', $adminId);
}
})
->exists();
if (! $canReset) {
Notification::make()->title('Utente non autorizzato per reset')->danger()->send();
return;
}
$target = User::query()->find($userId);
if (! $target instanceof User) {
Notification::make()->title('Utente non trovato')->danger()->send();
return;
}
$newPassword = Str::random(12);
$target->password = Hash::make($newPassword);
$target->save();
$this->lastGeneratedPassword = $newPassword;
Notification::make()
->title('Password fornitore resettata')
->body('Nuova password temporanea: ' . $newPassword)
->success()
->send();
}
private function loadTickets(): void
{
$user = Auth::user();
if (! $user instanceof User) {
$this->tickets = collect();
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
$this->tickets = collect();
return;
}
$query = Ticket::query()
->with(['categoriaTicket', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome', 'assegnatoAUser:id,name'])
->withCount('attachments')
->where('stabile_id', $stabileId);
if ($this->status === 'open') {
$query->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']);
} elseif ($this->status === 'urgent') {
$query->where('priorita', 'Urgente')
->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']);
} elseif ($this->status === 'closed') {
$query->whereIn('stato', ['Risolto', 'Chiuso']);
}
$this->tickets = $query->latest('data_apertura')->limit(50)->get();
}
private function refreshFornitoriAttiviRows(): void
{
$user = Auth::user();
if (! $user instanceof User) {
$this->fornitoriAttiviRows = [];
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
$this->fornitoriAttiviRows = [];
return;
}
$ticketApertiStati = ['Aperto', 'Preso in Carico', 'In Lavorazione', 'In Attesa Approvazione', 'In Attesa Ricambi'];
$assignedTickets = Ticket::query()
->where('stabile_id', $stabileId)
->whereIn('stato', $ticketApertiStati)
->whereNotNull('assegnato_a_fornitore_id')
->get(['id', 'assegnato_a_fornitore_id', 'stato', 'updated_at']);
$assignedByFornitore = $assignedTickets
->groupBy('assegnato_a_fornitore_id')
->map(fn($rows) => $rows->count());
$ticketSummaries = $assignedTickets
->groupBy('assegnato_a_fornitore_id')
->map(function ($rows) {
return $rows->sortByDesc('updated_at')->take(6)->map(function ($ticket): string {
return '#' . ((int) $ticket->id) . ' (' . (string) $ticket->stato . ')';
})->values()->all();
});
$openInterventiByFornitore = collect();
$interventoStatiByFornitore = collect();
$lastInterventoAtByFornitore = collect();
if (Schema::hasTable('ticket_interventi')) {
$openInterventi = TicketIntervento::query()
->whereIn('stato', ['assegnato', 'in_corso', 'in_verifica'])
->whereHas('ticket', function ($q) use ($stabileId, $ticketApertiStati): void {
$q->where('stabile_id', $stabileId)->whereIn('stato', $ticketApertiStati);
})
->orderByDesc('updated_at')
->get(['fornitore_id', 'stato', 'updated_at']);
$openInterventiByFornitore = $openInterventi
->groupBy('fornitore_id')
->map(fn($rows) => $rows->count());
$interventoStatiByFornitore = $openInterventi
->groupBy('fornitore_id')
->map(function ($rows): string {
return $rows->pluck('stato')
->map(fn($stato) => str_replace('_', ' ', (string) $stato))
->unique()
->take(3)
->implode(', ');
});
$lastInterventoAtByFornitore = $openInterventi
->groupBy('fornitore_id')
->map(function ($rows): ?string {
$last = $rows->sortByDesc('updated_at')->first();
return $last && $last->updated_at ? $last->updated_at->format('d/m/Y H:i') : null;
});
}
$fornitoriIds = collect($assignedByFornitore->keys())
->merge($openInterventiByFornitore->keys())
->map(fn($v) => (int) $v)
->filter(fn(int $v) => $v > 0)
->unique()
->values();
if ($fornitoriIds->isEmpty()) {
$this->fornitoriAttiviRows = [];
return;
}
$rows = [];
$accessRows = FornitoreDipendente::query()
->whereIn('fornitore_id', $fornitoriIds->all())
->get(['fornitore_id', 'user_id']);
$dipCountByFornitore = $accessRows
->groupBy('fornitore_id')
->map(fn($r) => $r->count());
$linkedUserByFornitore = $accessRows
->filter(fn($r) => (int) ($r->user_id ?? 0) > 0)
->groupBy('fornitore_id')
->map(fn($r) => (int) ($r->first()->user_id ?? 0));
foreach (Fornitore::query()->whereIn('id', $fornitoriIds->all())->orderBy('ragione_sociale')->orderBy('cognome')->orderBy('nome')->get() as $fornitore) {
$rows[] = [
'id' => (int) $fornitore->id,
'nome' => trim((string) ($fornitore->ragione_sociale ?: trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? '')))),
'email' => (string) ($fornitore->email ?? ''),
'telefono' => (string) ($fornitore->telefono ?: $fornitore->cellulare),
'ticket_attivi' => (int) ($assignedByFornitore->get((string) $fornitore->id) ?? $assignedByFornitore->get((int) $fornitore->id) ?? 0),
'interventi_aperti' => (int) ($openInterventiByFornitore->get((string) $fornitore->id) ?? $openInterventiByFornitore->get((int) $fornitore->id) ?? 0),
'ticket_focus' => (array) ($ticketSummaries->get((string) $fornitore->id) ?? $ticketSummaries->get((int) $fornitore->id) ?? []),
'intervento_stati' => (string) ($interventoStatiByFornitore->get((string) $fornitore->id) ?? $interventoStatiByFornitore->get((int) $fornitore->id) ?? ''),
'ultimo_aggiornamento' => (string) ($lastInterventoAtByFornitore->get((string) $fornitore->id) ?? $lastInterventoAtByFornitore->get((int) $fornitore->id) ?? ''),
'dipendenti_count' => (int) ($dipCountByFornitore->get((string) $fornitore->id) ?? $dipCountByFornitore->get((int) $fornitore->id) ?? 0),
'linked_user_id' => (int) ($linkedUserByFornitore->get((string) $fornitore->id) ?? $linkedUserByFornitore->get((int) $fornitore->id) ?? 0),
];
}
usort($rows, function (array $a, array $b): int {
$lhs = ((int) $a['interventi_aperti']) * 1000 + (int) $a['ticket_attivi'];
$rhs = ((int) $b['interventi_aperti']) * 1000 + (int) $b['ticket_attivi'];
return $rhs <=> $lhs;
});
$this->fornitoriAttiviRows = $rows;
}
private function abilitaAccessoDipendente(FornitoreDipendente $dipendente): void
{
$email = trim((string) ($dipendente->email ?? ''));
if ($email === '') {
Notification::make()->title('Email dipendente mancante')->warning()->send();
return;
}
$existingUser = User::query()->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])->first();
$created = false;
if (! $existingUser instanceof User) {
$generatedPassword = Str::random(12);
$existingUser = User::query()->create([
'name' => trim((string) ($dipendente->nome_completo ?: 'Utente fornitore')),
'email' => $email,
'password' => Hash::make($generatedPassword),
'email_verified_at' => now(),
'is_active' => true,
]);
$this->lastGeneratedPassword = $generatedPassword;
$created = true;
}
$existingUser->assignRole('fornitore');
$dipendente->user_id = (int) $existingUser->id;
$dipendente->updated_by_user_id = Auth::id();
$dipendente->save();
$body = $created
? 'Accesso creato. Password temporanea: ' . ($this->lastGeneratedPassword ?? '(non disponibile)')
: 'Accesso attivato/aggiornato per utente esistente.';
Notification::make()->title('Accesso fornitore abilitato')->body($body)->success()->send();
}
private function loadFornitoriOptions(): void
{
$user = Auth::user();
if (! $user instanceof User) {
$this->fornitoriOptions = [];
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
$this->fornitoriOptions = [];
return;
}
$stabile = \App\Models\Stabile::query()->find($stabileId);
$adminId = (int) ($stabile->amministratore_id ?? 0);
$query = Fornitore::query()->orderBy('ragione_sociale')->orderBy('cognome')->orderBy('nome');
if ($adminId > 0) {
$query->where(function ($q) use ($adminId): void {
$q->where('amministratore_id', $adminId)->orWhereNull('amministratore_id');
});
}
$this->fornitoriOptions = $query->limit(400)->get()->map(function (Fornitore $f): array {
$label = trim((string) ($f->ragione_sociale ?: trim(($f->nome ?? '') . ' ' . ($f->cognome ?? ''))));
return [
'id' => (int) $f->id,
'nome' => $label !== '' ? $label : ('Fornitore #' . $f->id),
];
})->all();
}
private function loadCategorieTicketOptions(): void
{
if (! Schema::hasTable('categorie_ticket')) {
$this->categorieOptions = [];
return;
}
$this->categorieOptions = CategoriaTicket::query()
->orderBy('nome')
->get(['id', 'nome', 'descrizione'])
->map(fn(CategoriaTicket $c): array=> [
'id' => (int) $c->id,
'nome' => (string) $c->nome,
'descrizione' => (string) ($c->descrizione ?? ''),
])
->all();
}
public function getTipoInterventoLabel(Ticket $ticket): string
{
$categoria = trim((string) optional($ticket->categoriaTicket)->nome);
$titolo = trim((string) ($ticket->titolo ?? ''));
if ($categoria !== '' && $titolo !== '') {
return $categoria . ' - ' . $titolo;
}
if ($categoria !== '') {
return $categoria;
}
if ($titolo !== '') {
return $titolo;
}
return 'N/D';
}
private function loadCounters(): void
{
$user = Auth::user();
if (! $user instanceof User) {
$this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0];
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
$this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0];
return;
}
$base = Ticket::query()->where('stabile_id', $stabileId);
$this->ticketCounters = [
'open' => (clone $base)->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(),
'urgent' => (clone $base)->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(),
'closed' => (clone $base)->whereIn('stato', ['Risolto', 'Chiuso'])->count(),
'all' => (clone $base)->count(),
];
}
private function aggiornaStatoTicket(int $ticketId, string $nuovoStato, bool $assegnaAUtente = false): void
{
$user = Auth::user();
if (! $user instanceof User) {
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
return;
}
$ticket = Ticket::query()
->where('id', $ticketId)
->where('stabile_id', $stabileId)
->first();
if (! $ticket) {
Notification::make()
->title('Ticket non trovato o non accessibile')
->danger()
->send();
return;
}
$payload = ['stato' => $nuovoStato];
if ($assegnaAUtente) {
$payload['assegnato_a_user_id'] = $user->id;
}
if ($nuovoStato === 'Risolto' && ! $ticket->data_risoluzione_effettiva) {
$payload['data_risoluzione_effettiva'] = now()->toDateString();
}
if ($nuovoStato === 'Chiuso' && ! $ticket->data_chiusura_effettiva) {
$payload['data_chiusura_effettiva'] = now()->toDateString();
}
$ticket->update($payload);
Notification::make()
->title('Ticket #' . $ticket->id . ' aggiornato a ' . $nuovoStato)
->success()
->send();
$this->refreshData();
}
}