424 lines
17 KiB
PHP
Executable File
424 lines
17 KiB
PHP
Executable File
<?php
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\CategoriaTicket;
|
|
use App\Models\Documento;
|
|
use App\Models\Fornitore;
|
|
use App\Models\InsuranceClaim;
|
|
use App\Models\Stabile;
|
|
use App\Models\Ticket;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class TicketController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of the resource.
|
|
*/
|
|
public function index()
|
|
{
|
|
$tickets = Ticket::with(['stabile', 'categoriaTicket', 'apertoUser', 'assegnatoUser', 'assegnatoFornitore'])
|
|
->whereHas('stabile', function ($query) {
|
|
$query->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null);
|
|
})
|
|
->orderBy('created_at', 'desc')
|
|
->paginate(10);
|
|
|
|
return view('admin.tickets.index', compact('tickets'));
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new resource.
|
|
*/
|
|
public function create()
|
|
{
|
|
$stabili = Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)
|
|
->attivi()
|
|
->get();
|
|
|
|
$categorieTicket = CategoriaTicket::all();
|
|
$users = User::role(['admin', 'amministratore'])->get();
|
|
$fornitori = Fornitore::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)->get();
|
|
|
|
return view('admin.tickets.create', compact('stabili', 'categorieTicket', 'users', 'fornitori'));
|
|
}
|
|
|
|
/**
|
|
* Store a newly created resource in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$request->validate([
|
|
'stabile_id' => 'required|exists:stabili,id_stabile',
|
|
'categoria_ticket_id' => 'nullable|exists:categorie_ticket,id',
|
|
'titolo' => 'required|string|max:255',
|
|
'descrizione' => 'nullable|string',
|
|
'luogo_intervento' => 'nullable|string|max:255',
|
|
'stato' => 'required|in:Aperto,Preso in Carico,In Lavorazione,In Attesa Approvazione,In Attesa Ricambi,Risolto,Chiuso,Annullato',
|
|
'priorita' => 'required|in:Bassa,Media,Alta,Urgente',
|
|
'assegnato_a_user_id' => 'nullable|exists:users,id',
|
|
'assegnato_a_fornitore_id' => 'nullable|exists:fornitori,id_fornitore',
|
|
'data_scadenza_prevista' => 'nullable|date',
|
|
'data_risoluzione_effettiva' => 'nullable|date',
|
|
'data_chiusura_effettiva' => 'nullable|date',
|
|
]);
|
|
|
|
$ticket = Ticket::create([
|
|
'stabile_id' => $request->stabile_id,
|
|
'categoria_ticket_id' => $request->categoria_ticket_id,
|
|
'aperto_da_user_id' => Auth::id(),
|
|
'assegnato_a_user_id' => $request->assegnato_a_user_id,
|
|
'assegnato_a_fornitore_id' => $request->assegnato_a_fornitore_id,
|
|
'titolo' => $request->titolo,
|
|
'descrizione' => $request->descrizione,
|
|
'luogo_intervento' => $request->luogo_intervento,
|
|
'data_apertura' => now(),
|
|
'data_scadenza_prevista' => $request->data_scadenza_prevista,
|
|
'data_risoluzione_effettiva' => $request->data_risoluzione_effettiva,
|
|
'data_chiusura_effettiva' => $request->data_chiusura_effettiva,
|
|
'stato' => $request->stato,
|
|
'priorita' => $request->priorita,
|
|
]);
|
|
|
|
$this->notifySupplierAssignment($ticket);
|
|
|
|
return redirect()->route('admin.tickets.index')
|
|
->with('success', 'Ticket creato con successo.');
|
|
}
|
|
|
|
/**
|
|
* Display the specified resource.
|
|
*/
|
|
public function show(Ticket $ticket)
|
|
{
|
|
// Verifica che l'utente possa accedere a questo ticket
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$ticket->load([
|
|
'stabile',
|
|
'categoriaTicket',
|
|
'apertoUser',
|
|
'assegnatoUser',
|
|
'assegnatoFornitore',
|
|
'messages.user',
|
|
'messages.emlDocumento',
|
|
'interventi.fornitore',
|
|
'interventi.presoInCaricoDaUser',
|
|
'insuranceClaim',
|
|
]);
|
|
|
|
$comunicazioni = $ticket->messages()
|
|
->with(['user', 'emlDocumento'])
|
|
->orderByDesc('inviato_il')
|
|
->orderByDesc('created_at')
|
|
->limit(50)
|
|
->get();
|
|
|
|
$articoliPronti = [
|
|
'Uscita tecnica ordinaria',
|
|
'Sostituzione lampada scala',
|
|
'Sostituzione pulsante/citofono',
|
|
'Riarmo quadro elettrico',
|
|
'Controllo perdita idraulica',
|
|
'Pulizia filtro pompa',
|
|
];
|
|
|
|
return view('admin.tickets.show', compact('ticket', 'comunicazioni', 'articoliPronti'));
|
|
}
|
|
|
|
public function storeEmailMessage(Request $request, Ticket $ticket)
|
|
{
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'eml_file' => 'required|file|max:20480|mimes:eml,msg,txt',
|
|
'oggetto' => 'nullable|string|max:255',
|
|
'email_mittente' => 'nullable|email|max:255',
|
|
'email_destinatario' => 'nullable|email|max:255',
|
|
'inviato_il' => 'nullable|date',
|
|
'messaggio' => 'nullable|string|max:4000',
|
|
]);
|
|
|
|
$file = $request->file('eml_file');
|
|
$path = $file->store('documenti/ticket-email/' . $ticket->id, 'public');
|
|
|
|
$headers = $this->extractEmailHeadersFromEml($path);
|
|
|
|
$oggetto = trim((string) ($validated['oggetto'] ?? $headers['subject'] ?? ''));
|
|
$mittente = trim((string) ($validated['email_mittente'] ?? $headers['from'] ?? ''));
|
|
$destinatario = trim((string) ($validated['email_destinatario'] ?? $headers['to'] ?? ''));
|
|
$externalId = trim((string) ($headers['message_id'] ?? ''));
|
|
$inviatoIl = $validated['inviato_il'] ?? ($headers['date'] ?? null);
|
|
|
|
$documento = Documento::create([
|
|
'documentable_id' => $ticket->id,
|
|
'documentable_type' => Ticket::class,
|
|
'stabile_id' => $ticket->stabile_id,
|
|
'utente_id' => Auth::id(),
|
|
'nome_file' => $file->getClientOriginalName(),
|
|
'path_file' => $path,
|
|
'percorso_file' => $path,
|
|
'tipo_documento' => 'Email EML',
|
|
'tipologia' => 'comunicazione',
|
|
'nome' => $oggetto !== '' ? $oggetto : 'Email associata al ticket #' . $ticket->id,
|
|
'mime_type' => (string) ($file->getMimeType() ?: 'message/rfc822'),
|
|
'estensione' => strtolower((string) $file->getClientOriginalExtension()),
|
|
'dimensione_file' => (int) ($file->getSize() ?: 0),
|
|
'data_documento' => $inviatoIl,
|
|
'data_upload' => now(),
|
|
'descrizione' => $validated['messaggio'] ?? 'Email collegata al ticket',
|
|
'tags' => 'email,ticket,comunicazioni',
|
|
'note' => 'Documento EML importato da ticket #' . $ticket->id,
|
|
]);
|
|
|
|
$ticket->messages()->create([
|
|
'user_id' => Auth::id(),
|
|
'messaggio' => trim((string) ($validated['messaggio'] ?? '')) ?: 'Email acquisita e archiviata come documento EML.',
|
|
'canale' => 'email',
|
|
'direzione' => 'inbound',
|
|
'oggetto' => $oggetto !== '' ? $oggetto : null,
|
|
'email_mittente' => $mittente !== '' ? $mittente : null,
|
|
'email_destinatario' => $destinatario !== '' ? $destinatario : null,
|
|
'external_message_id' => $externalId !== '' ? $externalId : null,
|
|
'inviato_il' => $inviatoIl,
|
|
'eml_documento_id' => $documento->id,
|
|
'metadata' => [
|
|
'original_file_name' => $file->getClientOriginalName(),
|
|
'stored_path' => $path,
|
|
],
|
|
]);
|
|
|
|
return redirect()->route('admin.tickets.show', $ticket)
|
|
->with('success', 'Email archiviata e collegata al ticket.');
|
|
}
|
|
|
|
public function openInsuranceClaim(Request $request, Ticket $ticket)
|
|
{
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'policy_reference' => 'nullable|string|max:255',
|
|
'claim_number' => 'nullable|string|max:255',
|
|
'notes' => 'nullable|string|max:4000',
|
|
]);
|
|
|
|
$claim = InsuranceClaim::query()->updateOrCreate(
|
|
['ticket_id' => $ticket->id],
|
|
[
|
|
'stabile_id' => $ticket->stabile_id,
|
|
'policy_reference' => $validated['policy_reference'] ?? null,
|
|
'claim_number' => $validated['claim_number'] ?? null,
|
|
'status' => 'aperta',
|
|
'opened_at' => now(),
|
|
'notes' => $validated['notes'] ?? null,
|
|
]
|
|
);
|
|
|
|
$ticket->messages()->create([
|
|
'user_id' => Auth::id(),
|
|
'messaggio' => 'Apertura sinistro assicurativo collegata al ticket. Riferimento sinistro: ' . ($claim->claim_number ?: 'da definire'),
|
|
'canale' => 'assicurazione',
|
|
'direzione' => 'outbound',
|
|
'inviato_il' => now(),
|
|
'metadata' => [
|
|
'insurance_claim_id' => $claim->id,
|
|
],
|
|
]);
|
|
|
|
return redirect()->route('admin.tickets.show', $ticket)
|
|
->with('success', 'Sinistro assicurativo collegato al ticket.');
|
|
}
|
|
|
|
/**
|
|
* Estrae gli header principali da un file EML senza dipendenze esterne.
|
|
*
|
|
* @return array{subject:?string,from:?string,to:?string,date:?string,message_id:?string}
|
|
*/
|
|
private function extractEmailHeadersFromEml(string $storedPath): array
|
|
{
|
|
$fullPath = Storage::disk('public')->path($storedPath);
|
|
if (! is_readable($fullPath)) {
|
|
return [
|
|
'subject' => null,
|
|
'from' => null,
|
|
'to' => null,
|
|
'date' => null,
|
|
'message_id' => null,
|
|
];
|
|
}
|
|
|
|
$content = (string) @file_get_contents($fullPath);
|
|
if ($content === '') {
|
|
return [
|
|
'subject' => null,
|
|
'from' => null,
|
|
'to' => null,
|
|
'date' => null,
|
|
'message_id' => null,
|
|
];
|
|
}
|
|
|
|
$headerBlock = strstr($content, "\n\n", true);
|
|
$headerBlock = $headerBlock === false ? substr($content, 0, 8000) : $headerBlock;
|
|
|
|
$extract = function (string $name) use ($headerBlock): ?string {
|
|
$pattern = '/^' . preg_quote($name, '/') . ':(.*)$/im';
|
|
if (! preg_match($pattern, $headerBlock, $matches)) {
|
|
return null;
|
|
}
|
|
return trim((string) $matches[1]);
|
|
};
|
|
|
|
return [
|
|
'subject' => $extract('Subject'),
|
|
'from' => $extract('From'),
|
|
'to' => $extract('To'),
|
|
'date' => $extract('Date'),
|
|
'message_id' => $extract('Message-ID'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified resource.
|
|
*/
|
|
public function edit(Ticket $ticket)
|
|
{
|
|
// Verifica che l'utente possa modificare questo ticket
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$stabili = Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)
|
|
->attivi()
|
|
->get();
|
|
|
|
$categorieTicket = CategoriaTicket::all();
|
|
$users = User::role(['admin', 'amministratore'])->get();
|
|
$fornitori = Fornitore::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)->get();
|
|
|
|
return view('admin.tickets.edit', compact('ticket', 'stabili', 'categorieTicket', 'users', 'fornitori'));
|
|
}
|
|
|
|
/**
|
|
* Update the specified resource in storage.
|
|
*/
|
|
public function update(Request $request, Ticket $ticket)
|
|
{
|
|
// Verifica che l'utente possa modificare questo ticket
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$request->validate([
|
|
'stabile_id' => 'required|exists:stabili,id_stabile',
|
|
'categoria_ticket_id' => 'nullable|exists:categorie_ticket,id',
|
|
'titolo' => 'required|string|max:255',
|
|
'descrizione' => 'nullable|string',
|
|
'luogo_intervento' => 'nullable|string|max:255',
|
|
'stato' => 'required|in:Aperto,Preso in Carico,In Lavorazione,In Attesa Approvazione,In Attesa Ricambi,Risolto,Chiuso,Annullato',
|
|
'priorita' => 'required|in:Bassa,Media,Alta,Urgente',
|
|
'assegnato_a_user_id' => 'nullable|exists:users,id',
|
|
'assegnato_a_fornitore_id' => 'nullable|exists:fornitori,id_fornitore',
|
|
'data_scadenza_prevista' => 'nullable|date',
|
|
'data_risoluzione_effettiva' => 'nullable|date',
|
|
'data_chiusura_effettiva' => 'nullable|date',
|
|
]);
|
|
|
|
$previousSupplierId = $ticket->assegnato_a_fornitore_id;
|
|
|
|
$ticket->update($request->all());
|
|
|
|
if ((int) ($ticket->assegnato_a_fornitore_id ?? 0) > 0 && (int) $previousSupplierId !== (int) $ticket->assegnato_a_fornitore_id) {
|
|
$this->notifySupplierAssignment($ticket);
|
|
}
|
|
|
|
return redirect()->route('admin.tickets.index')
|
|
->with('success', 'Ticket aggiornato con successo.');
|
|
}
|
|
|
|
private function notifySupplierAssignment(Ticket $ticket): void
|
|
{
|
|
$ticket->loadMissing(['assegnatoAFornitore', 'stabile']);
|
|
|
|
$fornitore = $ticket->assegnatoAFornitore;
|
|
if (! $fornitore || empty($fornitore->email)) {
|
|
return;
|
|
}
|
|
|
|
$subject = sprintf('NetGescon Ticket #%d assegnato', (int) $ticket->id);
|
|
$body = implode(PHP_EOL, [
|
|
'Nuovo ticket assegnato',
|
|
'',
|
|
'ID ticket: ' . (int) $ticket->id,
|
|
'Titolo: ' . (string) $ticket->titolo,
|
|
'Stabile: ' . (string) optional($ticket->stabile)->denominazione,
|
|
'Priorita: ' . (string) $ticket->priorita,
|
|
'Stato: ' . (string) $ticket->stato,
|
|
'Luogo intervento: ' . (string) ($ticket->luogo_intervento ?? '-'),
|
|
'',
|
|
'Descrizione:',
|
|
(string) ($ticket->descrizione ?? '-'),
|
|
]);
|
|
|
|
try {
|
|
Mail::raw($body, function ($message) use ($fornitore, $subject): void {
|
|
$message->to($fornitore->email)->subject($subject);
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Ticket supplier notification failed', [
|
|
'ticket_id' => $ticket->id,
|
|
'fornitore_id' => $fornitore->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the specified resource from storage.
|
|
*/
|
|
public function destroy(Ticket $ticket)
|
|
{
|
|
// Verifica che l'utente possa eliminare questo ticket
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
$ticket->delete();
|
|
|
|
return redirect()->route('admin.tickets.index')
|
|
->with('success', 'Ticket eliminato con successo.');
|
|
}
|
|
|
|
public function close(Ticket $ticket)
|
|
{
|
|
if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
|
|
abort(403);
|
|
}
|
|
|
|
if ($ticket->stato === 'Chiuso') {
|
|
return redirect()->route('admin.tickets.show', $ticket)
|
|
->with('success', 'Il ticket era gia chiuso.');
|
|
}
|
|
|
|
$ticket->update([
|
|
'stato' => 'Chiuso',
|
|
'data_risoluzione_effettiva' => $ticket->data_risoluzione_effettiva ?? now(),
|
|
'data_chiusura_effettiva' => now(),
|
|
]);
|
|
|
|
return redirect()->route('admin.tickets.show', $ticket)
|
|
->with('success', 'Ticket chiuso con successo.');
|
|
}
|
|
}
|