415 lines
16 KiB
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) : '');
|
|
}
|
|
}
|