394 lines
15 KiB
PHP
394 lines
15 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] = $this->resolveOperatoreContext();
|
|
|
|
$stato = (string) $request->query('stato', 'aperti');
|
|
$query = TicketIntervento::query()
|
|
->with(['ticket.stabile'])
|
|
->where('fornitore_id', $fornitore->id)
|
|
->orderByDesc('created_at');
|
|
|
|
if ($stato === 'chiusi') {
|
|
$query->whereIn('stato', ['chiuso', 'fatturato']);
|
|
} elseif ($stato === 'fatturabili') {
|
|
$query->whereIn('stato', ['fatturabile', 'fatturato']);
|
|
} else {
|
|
$query->whereNotIn('stato', ['chiuso']);
|
|
}
|
|
|
|
$interventi = $query->paginate(15)->withQueryString();
|
|
|
|
$rows = collect($interventi->items())
|
|
->map(fn(TicketIntervento $intervento): array=> $this->buildInterventoRow($intervento));
|
|
|
|
$fatturabili = TicketIntervento::query()
|
|
->where('fornitore_id', $fornitore->id)
|
|
->whereIn('stato', ['fatturabile', 'fatturato'])
|
|
->orderByDesc('updated_at')
|
|
->limit(100)
|
|
->get();
|
|
|
|
return view('fornitore.tickets.index', [
|
|
'fornitore' => $fornitore,
|
|
'interventi' => $interventi,
|
|
'rows' => $rows,
|
|
'stato' => $stato,
|
|
'fatturabili' => $fatturabili,
|
|
]);
|
|
}
|
|
|
|
public function show(TicketIntervento $intervento)
|
|
{
|
|
[$fornitore] = $this->resolveOperatoreContext();
|
|
$this->authorizeIntervento($intervento, $fornitore);
|
|
|
|
$intervento->load([
|
|
'ticket.stabile',
|
|
'ticket.messages.user',
|
|
'ticket.attachments.user',
|
|
'ticket.apertoDaUser',
|
|
'ticket.assegnatoAUser',
|
|
'ticket.assegnatoAFornitore',
|
|
'eseguitoDaDipendente',
|
|
]);
|
|
|
|
$storico = TicketIntervento::query()
|
|
->with(['ticket'])
|
|
->where('fornitore_id', $fornitore->id)
|
|
->where('id', '!=', $intervento->id)
|
|
->when(
|
|
(int) ($intervento->ticket?->soggetto_richiedente_id ?? 0) > 0,
|
|
fn($q) => $q->whereHas('ticket', fn($t) => $t->where('soggetto_richiedente_id', (int) $intervento->ticket->soggetto_richiedente_id)),
|
|
fn($q) => $q->whereHas('ticket', fn($t) => $t->where('stabile_id', (int) ($intervento->ticket?->stabile_id ?? 0)))
|
|
)
|
|
->latest('id')
|
|
->limit(12)
|
|
->get();
|
|
|
|
$caller = $this->extractCallerData((string) ($intervento->ticket?->descrizione ?? ''));
|
|
|
|
return view('fornitore.tickets.show', [
|
|
'fornitore' => $fornitore,
|
|
'intervento' => $intervento,
|
|
'storico' => $storico,
|
|
'caller' => $caller,
|
|
]);
|
|
}
|
|
|
|
public function start(TicketIntervento $intervento)
|
|
{
|
|
[$fornitore, $dipendente] = $this->resolveOperatoreContext();
|
|
$this->authorizeIntervento($intervento, $fornitore);
|
|
|
|
$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);
|
|
|
|
$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] = $this->resolveOperatoreContext();
|
|
$this->authorizeIntervento($intervento, $fornitore);
|
|
|
|
$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 = Fornitore::query()
|
|
->where('email', (string) $user->email)
|
|
->first();
|
|
|
|
if (! $fornitore && $user) {
|
|
$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)]);
|
|
})
|
|
->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);
|
|
}
|
|
|
|
/**
|
|
* @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) : '');
|
|
}
|
|
}
|