feat(ticket-postit): supplier progress, timestamped notes, reopen flow
This commit is contained in:
parent
334cc754f9
commit
2b7dcb6ab7
|
|
@ -30,6 +30,9 @@ class PostItGestione extends Page
|
||||||
|
|
||||||
protected string $view = 'filament.pages.strumenti.post-it-gestione';
|
protected string $view = 'filament.pages.strumenti.post-it-gestione';
|
||||||
|
|
||||||
|
/** @var array<int,string> */
|
||||||
|
public array $riaperturaNote = [];
|
||||||
|
|
||||||
public function getPostItInserimentoUrl(): string
|
public function getPostItInserimentoUrl(): string
|
||||||
{
|
{
|
||||||
return PostIt::getUrl(panel: 'admin-filament');
|
return PostIt::getUrl(panel: 'admin-filament');
|
||||||
|
|
@ -106,6 +109,66 @@ public function chiudiPostIt(int $postItId): void
|
||||||
->send();
|
->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function riapriPostIt(int $postItId): void
|
||||||
|
{
|
||||||
|
if (! $this->isPostItTableReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$postIt = ChiamataPostIt::query()->find($postItId);
|
||||||
|
if (! $postIt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$noteInput = trim((string) ($this->riaperturaNote[$postItId] ?? ''));
|
||||||
|
$stamp = now()->format('d/m/Y H:i');
|
||||||
|
$noteLine = $noteInput !== ''
|
||||||
|
? '[' . $stamp . '] Riapertura: ' . $noteInput
|
||||||
|
: '[' . $stamp . '] Riapertura da gestione Post-it';
|
||||||
|
|
||||||
|
$existing = trim((string) ($postIt->nota ?? ''));
|
||||||
|
$postIt->nota = $existing !== '' ? ($existing . "\n" . $noteLine) : $noteLine;
|
||||||
|
$postIt->stato = $postIt->ticket_id ? 'ticket' : 'post_it';
|
||||||
|
|
||||||
|
if (Schema::hasColumn('chiamate_post_it', 'chiusa_il')) {
|
||||||
|
$postIt->chiusa_il = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$postIt->save();
|
||||||
|
|
||||||
|
if ($postIt->ticket_id) {
|
||||||
|
$ticket = Ticket::query()->find((int) $postIt->ticket_id);
|
||||||
|
if ($ticket && in_array((string) $ticket->stato, ['Chiuso', 'Risolto'], true)) {
|
||||||
|
$ticket->stato = 'Preso in Carico';
|
||||||
|
|
||||||
|
if (Schema::hasColumn('tickets', 'data_chiusura_effettiva')) {
|
||||||
|
$ticket->data_chiusura_effettiva = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::hasColumn('tickets', 'data_risoluzione_effettiva')) {
|
||||||
|
$ticket->data_risoluzione_effettiva = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ticket->save();
|
||||||
|
|
||||||
|
if (method_exists($ticket, 'messages') && Schema::hasTable('ticket_messages')) {
|
||||||
|
$ticket->messages()->create([
|
||||||
|
'user_id' => Auth::id(),
|
||||||
|
'messaggio' => $noteLine,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->riaperturaNote[$postItId]);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Post-it riaperto')
|
||||||
|
->body('Riapertura completata con nota di tracciamento.')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
public function getRecentiProperty()
|
public function getRecentiProperty()
|
||||||
{
|
{
|
||||||
if (! $this->isPostItTableReady()) {
|
if (! $this->isPostItTableReady()) {
|
||||||
|
|
|
||||||
|
|
@ -281,9 +281,10 @@ public function aggiungiNotaInterna(): void
|
||||||
'notaInterna' => ['required', 'string', 'max:5000'],
|
'notaInterna' => ['required', 'string', 'max:5000'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$stamp = now()->format('d/m/Y H:i');
|
||||||
$ticket->messages()->create([
|
$ticket->messages()->create([
|
||||||
'user_id' => Auth::id(),
|
'user_id' => Auth::id(),
|
||||||
'messaggio' => trim((string) $this->notaInterna),
|
'messaggio' => '[' . $stamp . '] ' . trim((string) $this->notaInterna),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->notaInterna = null;
|
$this->notaInterna = null;
|
||||||
|
|
@ -417,24 +418,56 @@ private function refreshFornitoriAttiviRows(): void
|
||||||
|
|
||||||
$ticketApertiStati = ['Aperto', 'Preso in Carico', 'In Lavorazione', 'In Attesa Approvazione', 'In Attesa Ricambi'];
|
$ticketApertiStati = ['Aperto', 'Preso in Carico', 'In Lavorazione', 'In Attesa Approvazione', 'In Attesa Ricambi'];
|
||||||
|
|
||||||
$assignedByFornitore = Ticket::query()
|
$assignedTickets = Ticket::query()
|
||||||
->where('stabile_id', $stabileId)
|
->where('stabile_id', $stabileId)
|
||||||
->whereIn('stato', $ticketApertiStati)
|
->whereIn('stato', $ticketApertiStati)
|
||||||
->whereNotNull('assegnato_a_fornitore_id')
|
->whereNotNull('assegnato_a_fornitore_id')
|
||||||
->selectRaw('assegnato_a_fornitore_id as fornitore_id, COUNT(*) as ticket_attivi')
|
->get(['id', 'assegnato_a_fornitore_id', 'stato', 'updated_at']);
|
||||||
|
|
||||||
|
$assignedByFornitore = $assignedTickets
|
||||||
->groupBy('assegnato_a_fornitore_id')
|
->groupBy('assegnato_a_fornitore_id')
|
||||||
->pluck('ticket_attivi', 'fornitore_id');
|
->map(fn($rows) => $rows->count());
|
||||||
|
|
||||||
|
$ticketSummaries = $assignedTickets
|
||||||
|
->groupBy('assegnato_a_fornitore_id')
|
||||||
|
->map(function ($rows) {
|
||||||
|
return $rows->sortByDesc('updated_at')->take(6)->map(function ($ticket): string {
|
||||||
|
return '#' . ((int) $ticket->id) . ' (' . (string) $ticket->stato . ')';
|
||||||
|
})->values()->all();
|
||||||
|
});
|
||||||
|
|
||||||
$openInterventiByFornitore = collect();
|
$openInterventiByFornitore = collect();
|
||||||
|
$interventoStatiByFornitore = collect();
|
||||||
|
$lastInterventoAtByFornitore = collect();
|
||||||
if (Schema::hasTable('ticket_interventi')) {
|
if (Schema::hasTable('ticket_interventi')) {
|
||||||
$openInterventiByFornitore = TicketIntervento::query()
|
$openInterventi = TicketIntervento::query()
|
||||||
->whereIn('stato', ['assegnato', 'in_corso', 'in_verifica'])
|
->whereIn('stato', ['assegnato', 'in_corso', 'in_verifica'])
|
||||||
->whereHas('ticket', function ($q) use ($stabileId, $ticketApertiStati): void {
|
->whereHas('ticket', function ($q) use ($stabileId, $ticketApertiStati): void {
|
||||||
$q->where('stabile_id', $stabileId)->whereIn('stato', $ticketApertiStati);
|
$q->where('stabile_id', $stabileId)->whereIn('stato', $ticketApertiStati);
|
||||||
})
|
})
|
||||||
->selectRaw('fornitore_id, COUNT(*) as interventi_aperti')
|
->orderByDesc('updated_at')
|
||||||
|
->get(['fornitore_id', 'stato', 'updated_at']);
|
||||||
|
|
||||||
|
$openInterventiByFornitore = $openInterventi
|
||||||
->groupBy('fornitore_id')
|
->groupBy('fornitore_id')
|
||||||
->pluck('interventi_aperti', 'fornitore_id');
|
->map(fn($rows) => $rows->count());
|
||||||
|
|
||||||
|
$interventoStatiByFornitore = $openInterventi
|
||||||
|
->groupBy('fornitore_id')
|
||||||
|
->map(function ($rows): string {
|
||||||
|
return $rows->pluck('stato')
|
||||||
|
->map(fn($stato) => str_replace('_', ' ', (string) $stato))
|
||||||
|
->unique()
|
||||||
|
->take(3)
|
||||||
|
->implode(', ');
|
||||||
|
});
|
||||||
|
|
||||||
|
$lastInterventoAtByFornitore = $openInterventi
|
||||||
|
->groupBy('fornitore_id')
|
||||||
|
->map(function ($rows): ?string {
|
||||||
|
$last = $rows->sortByDesc('updated_at')->first();
|
||||||
|
return $last && $last->updated_at ? $last->updated_at->format('d/m/Y H:i') : null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$fornitoriIds = collect($assignedByFornitore->keys())
|
$fornitoriIds = collect($assignedByFornitore->keys())
|
||||||
|
|
@ -458,6 +491,9 @@ private function refreshFornitoriAttiviRows(): void
|
||||||
'telefono' => (string) ($fornitore->telefono ?: $fornitore->cellulare),
|
'telefono' => (string) ($fornitore->telefono ?: $fornitore->cellulare),
|
||||||
'ticket_attivi' => (int) ($assignedByFornitore->get((string) $fornitore->id) ?? $assignedByFornitore->get((int) $fornitore->id) ?? 0),
|
'ticket_attivi' => (int) ($assignedByFornitore->get((string) $fornitore->id) ?? $assignedByFornitore->get((int) $fornitore->id) ?? 0),
|
||||||
'interventi_aperti' => (int) ($openInterventiByFornitore->get((string) $fornitore->id) ?? $openInterventiByFornitore->get((int) $fornitore->id) ?? 0),
|
'interventi_aperti' => (int) ($openInterventiByFornitore->get((string) $fornitore->id) ?? $openInterventiByFornitore->get((int) $fornitore->id) ?? 0),
|
||||||
|
'ticket_focus' => (array) ($ticketSummaries->get((string) $fornitore->id) ?? $ticketSummaries->get((int) $fornitore->id) ?? []),
|
||||||
|
'intervento_stati' => (string) ($interventoStatiByFornitore->get((string) $fornitore->id) ?? $interventoStatiByFornitore->get((int) $fornitore->id) ?? ''),
|
||||||
|
'ultimo_aggiornamento' => (string) ($lastInterventoAtByFornitore->get((string) $fornitore->id) ?? $lastInterventoAtByFornitore->get((int) $fornitore->id) ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,14 @@
|
||||||
<x-filament::button size="sm" color="gray" wire:click="chiudiPostIt({{ $riga->id }})">Chiudi</x-filament::button>
|
<x-filament::button size="sm" color="gray" wire:click="chiudiPostIt({{ $riga->id }})">Chiudi</x-filament::button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@php($ticketClosed = $riga->ticket && in_array((string) $riga->ticket->stato, ['Chiuso', 'Risolto'], true))
|
||||||
|
@if($riga->stato === 'chiusa' || $ticketClosed)
|
||||||
|
<div class="flex flex-wrap items-center gap-2 rounded-md border bg-slate-50 px-2 py-1">
|
||||||
|
<input type="text" wire:model.defer="riaperturaNote.{{ (int) $riga->id }}" class="w-56 rounded-md border-gray-300 text-xs" placeholder="Nota riapertura (opzionale)" />
|
||||||
|
<x-filament::button size="sm" color="warning" wire:click="riapriPostIt({{ (int) $riga->id }})">Riapri post-it/ticket</x-filament::button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if($riga->ticket_id)
|
@if($riga->ticket_id)
|
||||||
<a href="{{ \App\Filament\Pages\Supporto\TicketGestione::getUrl(panel: 'admin-filament') }}#ticket-{{ (int) $riga->ticket_id }}" class="inline-flex items-center rounded-md border px-3 py-1 text-xs">Apri Ticket (#{{ $riga->ticket_id }})</a>
|
<a href="{{ \App\Filament\Pages\Supporto\TicketGestione::getUrl(panel: 'admin-filament') }}#ticket-{{ (int) $riga->ticket_id }}" class="inline-flex items-center rounded-md border px-3 py-1 text-xs">Apri Ticket (#{{ $riga->ticket_id }})</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
|
|
||||||
<div class="mt-4 rounded-xl border bg-white p-4">
|
<div class="mt-4 rounded-xl border bg-white p-4">
|
||||||
<div class="text-sm font-semibold">Fornitori attivi su ticket aperti</div>
|
<div class="text-sm font-semibold">Fornitori attivi su ticket aperti</div>
|
||||||
<div class="mt-1 text-xs text-gray-500">Elenco dinamico dei fornitori che stanno gestendo almeno un ticket/intervento aperto nello stabile attivo.</div>
|
<div class="mt-1 text-xs text-gray-500">Mostra solo fornitori con ticket/interventi in corso, con stato operativo e ultimo aggiornamento.</div>
|
||||||
|
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
<table class="min-w-full border-collapse border text-xs">
|
<table class="min-w-full border-collapse border text-xs">
|
||||||
|
|
@ -99,6 +99,7 @@
|
||||||
<th class="border px-2 py-2 text-left">Contatti</th>
|
<th class="border px-2 py-2 text-left">Contatti</th>
|
||||||
<th class="border px-2 py-2 text-left">Ticket attivi</th>
|
<th class="border px-2 py-2 text-left">Ticket attivi</th>
|
||||||
<th class="border px-2 py-2 text-left">Interventi aperti</th>
|
<th class="border px-2 py-2 text-left">Interventi aperti</th>
|
||||||
|
<th class="border px-2 py-2 text-left">Come procede</th>
|
||||||
<th class="border px-2 py-2 text-left">Azioni</th>
|
<th class="border px-2 py-2 text-left">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -109,6 +110,15 @@
|
||||||
<td class="border px-2 py-2">{{ $f['email'] ?: '-' }}<br>{{ $f['telefono'] ?: '-' }}</td>
|
<td class="border px-2 py-2">{{ $f['email'] ?: '-' }}<br>{{ $f['telefono'] ?: '-' }}</td>
|
||||||
<td class="border px-2 py-2">{{ (int) $f['ticket_attivi'] }}</td>
|
<td class="border px-2 py-2">{{ (int) $f['ticket_attivi'] }}</td>
|
||||||
<td class="border px-2 py-2">{{ (int) $f['interventi_aperti'] }}</td>
|
<td class="border px-2 py-2">{{ (int) $f['interventi_aperti'] }}</td>
|
||||||
|
<td class="border px-2 py-2">
|
||||||
|
<div class="space-y-1 text-[11px]">
|
||||||
|
<div><span class="font-medium">Stati intervento:</span> {{ $f['intervento_stati'] ?: 'N/D' }}</div>
|
||||||
|
<div><span class="font-medium">Ultimo aggiornamento:</span> {{ $f['ultimo_aggiornamento'] ?: '-' }}</div>
|
||||||
|
@if(!empty($f['ticket_focus']))
|
||||||
|
<div class="text-gray-600">{{ implode(', ', $f['ticket_focus']) }}</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="border px-2 py-2">
|
<td class="border px-2 py-2">
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
<a href="{{ $this->getFornitoreOperativoUrl((int) $f['id']) }}" class="inline-flex items-center rounded-md bg-indigo-700 px-2 py-1 text-[11px] font-medium text-white hover:bg-indigo-600">Scheda operativa</a>
|
<a href="{{ $this->getFornitoreOperativoUrl((int) $f['id']) }}" class="inline-flex items-center rounded-md bg-indigo-700 px-2 py-1 text-[11px] font-medium text-white hover:bg-indigo-600">Scheda operativa</a>
|
||||||
|
|
@ -118,7 +128,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="border px-2 py-4 text-center text-gray-500">Nessun fornitore attivo su ticket aperti.</td>
|
<td colspan="6" class="border px-2 py-4 text-center text-gray-500">Nessun fornitore attivo su ticket aperti.</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -170,6 +180,7 @@
|
||||||
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
<div class="rounded-xl border bg-white p-4">
|
||||||
<div class="text-sm font-semibold">Nota interna</div>
|
<div class="text-sm font-semibold">Nota interna</div>
|
||||||
|
<div class="mt-1 text-xs text-gray-500">Puoi aggiungere piu note: ogni nota viene salvata con data/ora automatica.</div>
|
||||||
<label class="mt-3 block text-sm">
|
<label class="mt-3 block text-sm">
|
||||||
<span class="mb-1 block font-medium">Nuova nota</span>
|
<span class="mb-1 block font-medium">Nuova nota</span>
|
||||||
<textarea rows="4" wire:model.defer="notaInterna" class="w-full rounded-lg border-gray-300" placeholder="Aggiungi aggiornamento interno"></textarea>
|
<textarea rows="4" wire:model.defer="notaInterna" class="w-full rounded-lg border-gray-300" placeholder="Aggiungi aggiornamento interno"></textarea>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user