resolveOperatoreContext(); $url = \App\Filament\Pages\Fornitore\TicketOperativi::getUrl( ['fornitore' => (int) $fornitore->id], panel: 'admin-filament' ); $stato = (string) $request->query('stato', 'aperti'); if ($stato !== '') { $url .= '&stato=' . urlencode($stato); } return redirect()->to($url); } public function show(TicketIntervento $intervento) { [$fornitore, $dipendente] = $this->resolveOperatoreContext(); $this->authorizeIntervento($intervento, $fornitore); $this->authorizeDipendenteIntervento($intervento, $dipendente); return redirect()->to( \App\Filament\Pages\Fornitore\TicketInterventoScheda::getUrl( ['record' => (int) $intervento->id], panel: 'admin-filament' ) ); } public function assign(Request $request, TicketIntervento $intervento) { [$fornitore] = $this->resolveOperatoreContext(); $this->authorizeIntervento($intervento, $fornitore); $validated = $request->validate([ 'dipendente_id' => 'nullable|integer', ]); $dipendenteId = (int) ($validated['dipendente_id'] ?? 0); $dipendente = null; if ($dipendenteId > 0) { $dipendente = FornitoreDipendente::query() ->where('fornitore_id', (int) $fornitore->id) ->where('attivo', true) ->find($dipendenteId); abort_unless($dipendente instanceof FornitoreDipendente, 404, 'Dipendente non valido per questo fornitore.'); } $intervento->eseguito_da_dipendente_id = $dipendente?->id; $intervento->save(); $stamp = now()->format('d/m/Y H:i'); $message = $dipendente ? '[' . $stamp . '] Intervento assegnato al dipendente fornitore: ' . $dipendente->nome_completo . '.' : '[' . $stamp . '] Assegnazione dipendente rimossa: intervento riportato al coordinamento del fornitore.'; $intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => $message, 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); return redirect()->route('fornitore.tickets.show', $intervento) ->with('success', $dipendente ? 'Intervento assegnato al dipendente selezionato.' : 'Assegnazione dipendente rimossa.'); } public function start(TicketIntervento $intervento) { [$fornitore, $dipendente] = $this->resolveOperatoreContext(); $this->authorizeIntervento($intervento, $fornitore); $this->authorizeDipendenteIntervento($intervento, $dipendente); $intervento->update([ 'stato' => 'in_corso', 'preso_in_carico_da_user_id' => Auth::id(), 'eseguito_da_dipendente_id' => $dipendente?->id, 'iniziato_at' => $intervento->iniziato_at ?? now(), ]); $intervento->ticket->update(['stato' => 'In Lavorazione']); return redirect()->route('fornitore.tickets.show', $intervento) ->with('success', 'Intervento preso in carico.'); } public function complete(Request $request, TicketIntervento $intervento) { [$fornitore, $dipendente] = $this->resolveOperatoreContext(); $this->authorizeIntervento($intervento, $fornitore); $this->authorizeDipendenteIntervento($intervento, $dipendente); $validated = $request->validate([ 'rapporto_fornitore' => 'required|string|max:5000', 'tempo_minuti' => 'nullable|integer|min:1|max:1440', 'articoli_utilizzati' => 'nullable|array', 'articoli_utilizzati.*' => 'nullable|string|max:120', 'foto_lavoro' => 'nullable|array|max:10', 'foto_lavoro.*' => 'nullable|image|max:10240', 'documenti_lavoro' => 'nullable|array|max:10', 'documenti_lavoro.*' => 'nullable|file|max:10240|mimes:pdf,doc,docx,xls,xlsx,txt,jpg,jpeg,png,webp', 'descrizione_file' => 'nullable|array', 'descrizione_file.*' => 'nullable|string|max:255', 'apparato_marca' => 'nullable|string|max:120', 'apparato_modello' => 'nullable|string|max:120', 'apparato_seriale' => 'nullable|string|max:120', 'lavoro_standard' => 'nullable|boolean', 'richiesta_proforma' => 'nullable|boolean', 'proforma_codice' => 'nullable|string|max:80', 'proforma_note' => 'nullable|string|max:2000', 'messaggio_veloce' => 'nullable|string|max:1500', 'qr_token' => 'nullable|string|max:40', ]); $rapporto = trim((string) $validated['rapporto_fornitore']); $apparato = $this->buildApparatoSummary( (string) ($validated['apparato_marca'] ?? ''), (string) ($validated['apparato_modello'] ?? ''), (string) ($validated['apparato_seriale'] ?? '') ); if ($apparato !== '') { $rapporto .= "\n\n" . $apparato; } $payload = [ 'rapporto_fornitore' => $rapporto, 'tempo_minuti' => $validated['tempo_minuti'] ?? null, 'terminato_at' => now(), 'stato' => ! empty($validated['lavoro_standard']) ? 'fatturabile' : 'in_verifica', 'eseguito_da_dipendente_id' => $dipendente?->id ?? $intervento->eseguito_da_dipendente_id, ]; $articoli = collect($validated['articoli_utilizzati'] ?? []) ->map(fn($item) => trim((string) $item)) ->filter() ->values() ->all(); if ($articoli !== []) { $payload['articoli_utilizzati'] = $articoli; } $fotoFiles = $request->file('foto_lavoro', []); if (! is_array($fotoFiles)) { $fotoFiles = $fotoFiles ? [$fotoFiles] : []; } if (count($fotoFiles) > 0) { $payload['foto_path'] = $fotoFiles[0]->store('ticket-interventi/foto/' . $intervento->id, 'public'); } $docFiles = $request->file('documenti_lavoro', []); if (! is_array($docFiles)) { $docFiles = $docFiles ? [$docFiles] : []; } $allFiles = array_merge($fotoFiles, $docFiles); $descriptions = (array) ($validated['descrizione_file'] ?? []); if (Schema::hasTable('ticket_attachments')) { foreach ($allFiles as $index => $file) { if (! $file) { continue; } $path = $file->store('ticket-interventi/allegati/' . $intervento->id, 'public'); TicketAttachment::query()->create([ 'ticket_id' => (int) $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) ($descriptions[$index] ?? '')) ?: 'Allegato rapporto fornitore', ]); } } $tokenInserito = trim((string) ($validated['qr_token'] ?? '')); if ($tokenInserito !== '' && strcasecmp($tokenInserito, (string) $intervento->qr_token) === 0) { $payload['qr_scansionato_at'] = now(); } $intervento->update($payload); $stamp = now()->format('d/m/Y H:i'); $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['proforma_codice'] ?? '')); $proformaNote = trim((string) ($validated['proforma_note'] ?? '')); if (! empty($validated['richiesta_proforma']) || $proformaCodice !== '' || $proformaNote !== '') { $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['messaggio_veloce'] ?? '')); if ($quickMessage !== '') { $intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Messaggio veloce fornitore: ' . $quickMessage, 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); } return redirect()->route('fornitore.tickets.show', $intervento) ->with('success', 'Intervento inviato in verifica amministratore.'); } public function sollecito(TicketIntervento $intervento) { [$fornitore, $dipendente] = $this->resolveOperatoreContext(); $this->authorizeIntervento($intervento, $fornitore); $this->authorizeDipendenteIntervento($intervento, $dipendente); $stamp = now()->format('d/m/Y H:i'); $intervento->ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] Sollecito fornitore su ticket/intervento in corso.', 'canale' => 'interno', 'direzione' => 'inbound', 'inviato_il' => now(), ]); return back()->with('success', 'Sollecito inviato allo studio amministrativo.'); } /** * @return array{0:Fornitore,1:?FornitoreDipendente} */ private function resolveOperatoreContext(): array { $user = Auth::user(); if ($user && $user->hasAnyRole(['super-admin', 'admin', 'amministratore'])) { $fornitoreId = (int) request()->query('fornitore', 0); if ($fornitoreId <= 0) { $interventoRoute = request()->route('intervento'); if ($interventoRoute instanceof TicketIntervento) { $fornitoreId = (int) ($interventoRoute->fornitore_id ?? 0); } } if ($fornitoreId > 0) { $fornitoreAdmin = Fornitore::query()->find($fornitoreId); abort_unless($fornitoreAdmin, 404); return [$fornitoreAdmin, null]; } } $dipendente = null; $fornitore = null; if ($user) { $email = mb_strtolower(trim((string) $user->email)); if ($email !== '') { $fornitore = Fornitore::query() ->whereRaw('LOWER(email) = ?', [$email]) ->withCount(['ticketInterventi', 'dipendenti']) ->orderByDesc('ticket_interventi_count') ->orderByDesc('dipendenti_count') ->orderByDesc('id') ->first(); } if (! $fornitore) { $dipendente = FornitoreDipendente::query() ->where('attivo', true) ->where(function ($q) use ($user): void { $q->where('user_id', (int) $user->id) ->orWhereRaw('LOWER(email) = ?', [mb_strtolower((string) $user->email)]); }) ->orderByRaw('CASE WHEN user_id = ? THEN 0 ELSE 1 END', [(int) $user->id]) ->orderByDesc('id') ->first(); if ($dipendente) { $fornitore = Fornitore::query()->find((int) $dipendente->fornitore_id); $dipendente->ultimo_accesso_at = now(); $dipendente->save(); } } } abort_unless($fornitore, 403, 'Profilo fornitore non collegato.'); return [$fornitore, $dipendente]; } private function authorizeIntervento(TicketIntervento $intervento, Fornitore $fornitore): void { abort_unless((int) $intervento->fornitore_id === (int) $fornitore->id, 403); } private function authorizeDipendenteIntervento(TicketIntervento $intervento, ?FornitoreDipendente $dipendente): void { if (! $dipendente instanceof FornitoreDipendente) { return; } $assignedDipendenteId = (int) ($intervento->eseguito_da_dipendente_id ?? 0); abort_if($assignedDipendenteId > 0 && $assignedDipendenteId !== (int) $dipendente->id, 403, 'Intervento assegnato a un altro operatore del fornitore.'); } /** * @return array{ingresso:string,contatto:string,telefono:string,problema:string,apparato:string} */ private function buildInterventoRow(TicketIntervento $intervento): array { $descrizione = (string) ($intervento->ticket->descrizione ?? ''); $caller = $this->extractCallerData($descrizione); return [ 'ingresso' => optional($intervento->created_at)->format('d/m/Y H:i') ?: '-', 'contatto' => $caller['contatto'], 'telefono' => $caller['telefono'], 'problema' => $caller['problema'] !== '' ? $caller['problema'] : ((string) ($intervento->ticket->titolo ?? '-')), 'apparato' => $this->extractApparato((string) ($intervento->rapporto_fornitore ?? '')), ]; } /** * @return array{contatto:string,telefono:string,problema:string} */ private function extractCallerData(string $descrizione): array { $contatto = '-'; $telefono = ''; $problema = ''; $lines = preg_split('/\r\n|\r|\n/', $descrizione) ?: []; foreach ($lines as $line) { $trim = trim($line); if ($problema === '' && $trim !== '') { $problema = $trim; } if (str_starts_with($trim, 'Chiamante selezionato:')) { $contatto = trim((string) str_replace('Chiamante selezionato:', '', $trim)); } if (str_starts_with($trim, 'Telefono:')) { $telefono = trim((string) str_replace('Telefono:', '', $trim)); } } return [ 'contatto' => $contatto, 'telefono' => $telefono, 'problema' => $problema, ]; } private function extractApparato(string $rapporto): string { foreach ((preg_split('/\r\n|\r|\n/', $rapporto) ?: []) as $line) { $trim = trim($line); if (str_starts_with(mb_strtolower($trim), 'apparato:')) { return trim((string) substr($trim, strlen('apparato:'))); } } return '-'; } private function buildApparatoSummary(string $marca, string $modello, string $seriale): string { $marca = trim($marca); $modello = trim($modello); $seriale = trim($seriale); if ($marca === '' && $modello === '' && $seriale === '') { return ''; } return 'Apparato: ' . ($marca !== '' ? ('marca=' . $marca . ' ') : '') . ($modello !== '' ? ('modello=' . $modello . ' ') : '') . ($seriale !== '' ? ('seriale=' . $seriale) : ''); } }