'-', 'telefono' => '', 'email' => '', 'problema' => '', 'sorgente' => '', 'riferimento' => '', ]; /** @var array> */ public array $storicoRows = []; /** @var array> */ public array $dipendentiOptions = []; public ?int $dipendenteId = null; public string $rapportoFornitore = ''; public ?int $tempoMinuti = null; /** @var array */ public array $articoliUtilizzati = ['', '', '']; public string $apparatoMarca = ''; public string $apparatoModello = ''; public string $apparatoSeriale = ''; /** @var array */ public array $fotoLavoro = []; /** @var array */ public array $documentiLavoro = []; /** @var array */ public array $descrizioneFile = ['', '', '', '', '', '']; public string $messaggioVeloce = ''; public bool $lavoroStandard = false; public bool $richiestaProforma = false; public string $proformaCodice = ''; public string $proformaNote = ''; public string $qrToken = ''; public ?array $attachmentPreview = null; public static function canAccess(): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); } public function mount(int | string $record): void { $recordId = (int) $record; abort_unless($recordId > 0, 404); $user = Auth::user(); $isAdmin = $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore']); if ($isAdmin) { $intervento = $this->resolveInterventoRecord($recordId); abort_unless($intervento instanceof TicketIntervento, 404); [$fornitore, $dipendente] = $this->resolveOperatoreContext((int) $intervento->fornitore_id); } else { [$fornitore, $dipendente] = $this->resolveOperatoreContext(); abort_unless($fornitore instanceof Fornitore, 403, 'Profilo fornitore non collegato.'); $intervento = $this->resolveInterventoRecord($recordId, $fornitore, $dipendente); abort_unless($intervento instanceof TicketIntervento, 404, 'Ticket/intervento non disponibile per questo fornitore.'); } $this->intervento = $intervento; $this->fornitore = $fornitore; $this->dipendente = $dipendente; $this->authorizeIntervento($this->intervento, $this->fornitore); $this->authorizeDipendenteIntervento($this->intervento, $this->dipendente); $this->reloadIntervento(); } public function assignDipendente(): void { if ($this->dipendente instanceof FornitoreDipendente) { Notification::make()->title('Assegnazione consentita solo al coordinatore fornitore')->warning()->send(); return; } $validated = $this->validate([ 'dipendenteId' => ['nullable', 'integer'], ]); $dipendente = null; $selectedId = (int) ($validated['dipendenteId'] ?? 0); if ($selectedId > 0) { $dipendente = FornitoreDipendente::query() ->where('fornitore_id', (int) $this->fornitore->id) ->where('attivo', true) ->find($selectedId); if (! $dipendente instanceof FornitoreDipendente) { Notification::make()->title('Collaboratore non valido per questo fornitore')->danger()->send(); return; } } $this->intervento->eseguito_da_dipendente_id = $dipendente?->id; $this->intervento->save(); $stamp = now()->format('d/m/Y H:i'); $message = $dipendente ? '[' . $stamp . '] Intervento assegnato al collaboratore fornitore: ' . $dipendente->ruolo_operativo_label . '.' : '[' . $stamp . '] Assegnazione collaboratore rimossa: intervento riportato al coordinamento del fornitore.'; $this->intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => $message, 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); $this->reloadIntervento(); Notification::make()->title('Assegnazione aggiornata')->success()->send(); } public function startIntervento(): void { $this->intervento->update([ 'stato' => 'in_corso', 'preso_in_carico_da_user_id' => Auth::id(), 'eseguito_da_dipendente_id' => $this->dipendente?->id ?? $this->intervento->eseguito_da_dipendente_id, 'iniziato_at' => $this->intervento->iniziato_at ?? now(), ]); $this->intervento->ticket->update(['stato' => 'In Lavorazione']); $this->reloadIntervento(); Notification::make()->title('Intervento preso in carico')->success()->send(); } public function saveRapporto(): void { $validated = $this->validate([ 'rapportoFornitore' => ['required', 'string', 'max:5000'], 'tempoMinuti' => ['nullable', 'integer', 'min:1', 'max:1440'], 'articoliUtilizzati' => ['nullable', 'array'], 'articoliUtilizzati.*' => ['nullable', 'string', 'max:120'], 'fotoLavoro' => ['nullable', 'array', 'max:10'], 'fotoLavoro.*' => ['nullable', 'image', 'max:10240'], 'documentiLavoro' => ['nullable', 'array', 'max:10'], 'documentiLavoro.*' => ['nullable', 'file', 'max:10240', 'mimes:pdf,doc,docx,xls,xlsx,txt,jpg,jpeg,png,webp'], 'descrizioneFile' => ['nullable', 'array'], 'descrizioneFile.*' => ['nullable', 'string', 'max:255'], 'apparatoMarca' => ['nullable', 'string', 'max:120'], 'apparatoModello' => ['nullable', 'string', 'max:120'], 'apparatoSeriale' => ['nullable', 'string', 'max:120'], 'messaggioVeloce' => ['nullable', 'string', 'max:1500'], 'lavoroStandard' => ['nullable', 'boolean'], 'richiestaProforma' => ['nullable', 'boolean'], 'proformaCodice' => ['nullable', 'string', 'max:80'], 'proformaNote' => ['nullable', 'string', 'max:2000'], 'qrToken' => ['nullable', 'string', 'max:40'], ]); $rapporto = trim((string) $validated['rapportoFornitore']); $apparato = $this->buildApparatoSummary( (string) ($validated['apparatoMarca'] ?? ''), (string) ($validated['apparatoModello'] ?? ''), (string) ($validated['apparatoSeriale'] ?? '') ); if ($apparato !== '') { $rapporto .= "\n\n" . $apparato; } $payload = [ 'rapporto_fornitore' => $rapporto, 'tempo_minuti' => $validated['tempoMinuti'] ?? null, 'terminato_at' => now(), 'stato' => ! empty($validated['lavoroStandard']) ? 'fatturabile' : 'in_verifica', 'eseguito_da_dipendente_id' => $this->dipendente?->id ?? $this->intervento->eseguito_da_dipendente_id, ]; $articoli = collect($validated['articoliUtilizzati'] ?? []) ->map(fn($item) => trim((string) $item)) ->filter() ->values() ->all(); if ($articoli !== []) { $payload['articoli_utilizzati'] = $articoli; } if ($this->fotoLavoro !== []) { $firstPhoto = $this->fotoLavoro[0] ?? null; if ($firstPhoto) { $payload['foto_path'] = $firstPhoto->store('ticket-interventi/foto/' . $this->intervento->id, 'public'); } } $allFiles = array_merge($this->fotoLavoro, $this->documentiLavoro); if ($allFiles !== [] && Schema::hasTable('ticket_attachments')) { foreach ($allFiles as $index => $file) { if (! $file) { continue; } $path = $file->store('ticket-interventi/allegati/' . $this->intervento->id, 'public'); TicketAttachment::query()->create([ 'ticket_id' => (int) $this->intervento->ticket_id, 'ticket_update_id' => null, 'user_id' => (int) Auth::id(), 'file_path' => $path, 'original_file_name' => (string) $file->getClientOriginalName(), 'mime_type' => (string) $file->getClientMimeType(), 'size' => (int) $file->getSize(), 'description' => trim((string) ($this->descrizioneFile[$index] ?? '')) ?: 'Allegato rapporto fornitore', ]); } } $tokenInserito = trim((string) ($validated['qrToken'] ?? '')); if ($tokenInserito !== '' && strcasecmp($tokenInserito, (string) $this->intervento->qr_token) === 0) { $payload['qr_scansionato_at'] = now(); } $this->intervento->update($payload); $stamp = now()->format('d/m/Y H:i'); $this->intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Rapporto intervento caricato dal fornitore.', 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); $proformaCodice = trim((string) ($validated['proformaCodice'] ?? '')); $proformaNote = trim((string) ($validated['proformaNote'] ?? '')); if (! empty($validated['richiestaProforma']) || $proformaCodice !== '' || $proformaNote !== '') { $this->intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Proforma fornitore ' . ($proformaCodice !== '' ? ('codice ' . $proformaCodice) : '(senza codice)') . ($proformaNote !== '' ? (' - ' . $proformaNote) : ''), 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); } $quickMessage = trim((string) ($validated['messaggioVeloce'] ?? '')); if ($quickMessage !== '') { $this->intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Messaggio veloce fornitore: ' . $quickMessage, 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); } $this->fotoLavoro = []; $this->documentiLavoro = []; $this->descrizioneFile = ['', '', '', '', '', '']; $this->messaggioVeloce = ''; $this->proformaCodice = ''; $this->proformaNote = ''; $this->dispatch('fornitore-local-previews-clear'); $this->reloadIntervento(); Notification::make()->title('Rapporto inviato in verifica amministratore')->success()->send(); } public function inviaSollecito(): void { $stamp = now()->format('d/m/Y H:i'); $this->intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Sollecito fornitore su ticket/intervento in corso.', 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); $this->reloadIntervento(); Notification::make()->title('Sollecito inviato allo studio amministrativo')->success()->send(); } public function getTicketsUrl(): string { return TicketOperativi::getUrl(['fornitore' => (int) $this->fornitore->id], 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 { $attachment = $this->intervento->ticket->attachments->firstWhere('id', $attachmentId); if (! $attachment instanceof TicketAttachment) { return; } $this->attachmentPreview = [ 'id' => (int) $attachment->id, 'name' => (string) ($attachment->original_file_name ?? 'allegato'), 'description' => (string) ($attachment->description ?? ''), 'url' => $this->getAttachmentPublicUrl($attachment), 'is_image' => $attachment->isImage(), 'is_pdf' => $attachment->isPdf(), 'mime' => $attachment->resolvedMimeType(), ]; } public function closeAttachmentPreview(): void { $this->attachmentPreview = null; } public function getCollaboratoriUrl(): string { return Collaboratori::getUrl(['fornitore' => (int) $this->fornitore->id], panel: 'admin-filament'); } public function canAssignDipendente(): bool { return ! ($this->dipendente instanceof FornitoreDipendente) && count($this->dipendentiOptions) > 0; } protected function resolveInterventoRecord(int $recordId, ?Fornitore $fornitore = null, ?FornitoreDipendente $dipendente = null): ?TicketIntervento { $query = TicketIntervento::query(); if ($fornitore instanceof Fornitore) { $query->where('fornitore_id', (int) $fornitore->id); } if ($dipendente instanceof FornitoreDipendente) { $query->where(function (Builder $builder) use ($dipendente): void { $builder->whereNull('eseguito_da_dipendente_id') ->orWhere('eseguito_da_dipendente_id', (int) $dipendente->id); }); } return $query ->where(function (Builder $builder) use ($recordId): void { $builder->whereKey($recordId) ->orWhere('ticket_id', $recordId); }) ->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [$recordId]) ->latest('id') ->first(); } protected function reloadIntervento(): void { $this->intervento->load([ 'ticket.stabile', 'ticket.unitaImmobiliare', 'ticket.soggettoRichiedente', 'ticket.messages.user', 'ticket.attachments.user', 'ticket.apertoDaUser', 'ticket.assegnatoAUser', 'ticket.assegnatoAFornitore', 'eseguitoDaDipendente', ]); $this->intervento->refresh(); $this->intervento->load([ 'ticket.stabile', 'ticket.unitaImmobiliare', 'ticket.soggettoRichiedente', 'ticket.messages.user', 'ticket.attachments.user', 'ticket.apertoDaUser', 'ticket.assegnatoAUser', 'ticket.assegnatoAFornitore', 'eseguitoDaDipendente', ]); $this->caller = $this->resolveCallerData($this->intervento->ticket); $this->storicoRows = TicketIntervento::query() ->with(['ticket']) ->where('fornitore_id', (int) $this->fornitore->id) ->where('id', '!=', (int) $this->intervento->id) ->when( (int) ($this->intervento->ticket?->soggetto_richiedente_id ?? 0) > 0, fn($query) => $query->whereHas('ticket', fn($ticketQuery) => $ticketQuery->where('soggetto_richiedente_id', (int) $this->intervento->ticket->soggetto_richiedente_id)), fn($query) => $query->whereHas('ticket', fn($ticketQuery) => $ticketQuery->where('stabile_id', (int) ($this->intervento->ticket?->stabile_id ?? 0))) ) ->latest('id') ->limit(12) ->get() ->map(fn(TicketIntervento $old) => [ 'ticket_id' => (int) $old->ticket_id, 'titolo' => (string) ($old->ticket->titolo ?? '-'), 'stato' => (string) $old->stato, 'updated_at' => optional($old->updated_at)->format('d/m/Y H:i') ?: '-', 'note' => (string) ($old->note_amministratore ?? ''), ]) ->all(); $this->dipendentiOptions = $this->fornitore->dipendenti() ->where('attivo', true) ->with('fornitoreEsterno') ->orderBy('cognome') ->orderBy('nome') ->get() ->map(fn(FornitoreDipendente $dipendente) => [ 'id' => (int) $dipendente->id, 'label' => $dipendente->ruolo_operativo_label . ($dipendente->email ? ' · ' . $dipendente->email : ''), ]) ->all(); $this->dipendenteId = (int) ($this->intervento->eseguito_da_dipendente_id ?? 0) ?: null; $this->rapportoFornitore = (string) ($this->intervento->rapporto_fornitore ?? ''); $this->tempoMinuti = $this->intervento->tempo_minuti ? (int) $this->intervento->tempo_minuti : null; $this->articoliUtilizzati = array_pad((array) ($this->intervento->articoli_utilizzati ?? []), 3, ''); $this->qrToken = ''; } /** * @return array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string} */ protected function resolveCallerData(?Ticket $ticket): array { $parsed = $this->extractCallerData((string) ($ticket?->descrizione ?? '')); $caller = [ 'contatto' => $parsed['contatto'], 'telefono' => $parsed['telefono'], 'email' => '', 'problema' => $parsed['problema'] !== '' ? $parsed['problema'] : (string) ($ticket?->titolo ?? ''), 'sorgente' => 'Descrizione ticket', 'riferimento' => $this->buildCallerReference($ticket), ]; $soggetto = $ticket?->soggettoRichiedente; if ($soggetto) { $label = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? '')))); if ($label !== '') { $caller['contatto'] = $label; } $caller['telefono'] = trim((string) ($soggetto->telefono ?? '')) ?: $caller['telefono']; $caller['email'] = trim((string) ($soggetto->email ?? '')); $caller['sorgente'] = 'Richiedente collegato al ticket'; return $caller; } $openedBy = $ticket?->apertoDaUser; if ($openedBy instanceof User) { $caller['contatto'] = trim((string) ($openedBy->name ?? '')) ?: $caller['contatto']; $caller['email'] = trim((string) ($openedBy->email ?? '')); $caller['sorgente'] = 'Utente che ha aperto il ticket'; } return $caller; } protected function buildCallerReference(?Ticket $ticket): string { if (! $ticket instanceof Ticket) { return ''; } $parts = []; $stabile = $ticket->stabile; if ($stabile) { $denominazione = trim((string) ($stabile->denominazione ?? '')); $indirizzo = trim(implode(' ', array_filter([ $stabile->indirizzo ?? null, $stabile->cap ?? null, $stabile->citta ?? null, ]))); if ($denominazione !== '') { $parts[] = $denominazione; } if ($indirizzo !== '') { $parts[] = $indirizzo; } } $luogoIntervento = trim((string) ($ticket->luogo_intervento ?? '')); if ($luogoIntervento !== '') { $parts[] = 'Luogo: ' . $luogoIntervento; } return implode(' · ', array_unique($parts)); } }