netgescon-day0/app/Http/Controllers/Fornitore/TicketOperativoController.php

415 lines
16 KiB
PHP

<?php
namespace App\Http\Controllers\Fornitore;
use App\Http\Controllers\Controller;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\TicketAttachment;
use App\Models\TicketIntervento;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
class TicketOperativoController extends Controller
{
public function index(Request $request)
{
[$fornitore, $dipendente] = $this->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) : '');
}
}