From 4d9e445cba57375272a1590608ac92cc420fca67 Mon Sep 17 00:00:00 2001 From: Francesco Picone Date: Tue, 20 Oct 2026 10:48:37 +0200 Subject: [PATCH] ++ gruppi: sorvegliante non piu' attivo scioglie il gruppo; dettagli del gruppo nella scheda e nel profilo - Proclamatore::booted: reso non attivo, spostato nel cestino o anonimizzato, il sorvegliante scioglie il gruppo (territori non dati in reparto, membri liberi, utenza disattivata); il motivo va nel registro. L'elenco chiede conferma, la modifica avvisa sotto "Attivo". Un membro nel cestino esce dal gruppo. - Scheda del proclamatore: gruppo guidato con membri, pacchetto, da dare, assegnazioni dal gruppo, utenza e ultimo accesso; oppure gruppo di cui e' membro; gruppi sciolti guidati in passato. - Profilo del sorvegliante: il suo gruppo con gli stessi numeri. Co-Authored-By: Claude Opus 5.5 --- HANDOFF.md | 27 +++- README.md | 15 ++- .../Proclamatori/ProclamatoreEdit.php | 7 +- .../Proclamatori/ProclamatoreIndex.php | 38 +++--- .../Proclamatori/ProclamatoreShow.php | 18 +++ app/Livewire/Profile.php | 5 + app/Models/GruppoServizio.php | 20 +++ app/Models/Proclamatore.php | 40 ++++++ app/Services/GruppiServizio.php | 10 +- .../proclamatori/proclamatore-form.blade.php | 6 + .../proclamatori/proclamatore-index.blade.php | 12 +- .../proclamatori/proclamatore-show.blade.php | 67 ++++++++++ resources/views/livewire/profile.blade.php | 35 +++++- tests/Feature/GruppiServizioTest.php | 118 ++++++++++++++++-- 14 files changed, 370 insertions(+), 48 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 315ef60..bfcb03a 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,7 +4,7 @@ Stato del progetto per chi ci mette mano dopo. Per installazione, deploy e uso quotidiano vedi [README.md](README.md); qui c'è il contesto che il README non può dare: perché le cose sono come sono, e cosa resta aperto. -Ultimo aggiornamento: **19 ottobre 2026** (gruppi di servizio, solo su dev) — vedi [Lavori in corso](#lavori-in-corso--12-ottobre-2026-riprendere-da-qui). +Ultimo aggiornamento: **20 ottobre 2026** (gruppi di servizio, solo su dev) — vedi [Lavori in corso](#lavori-in-corso--12-ottobre-2026-riprendere-da-qui). --- @@ -161,11 +161,26 @@ Casi aggiunti di mio, da far confermare all'utente: - Un ex sorvegliante può guidare un gruppo nuovo: `crea()` **riattiva** la sua utenza disattivata (un proclamatore, un'utenza: `users.proclamatore_id` è unico), con email e password temporanea nuove. -- Il sorvegliante di un gruppo **attivo** non si sposta nel cestino né si - anonimizza (`ProclamatoreIndex`): prima si scioglie il gruppo. Un membro - spostato nel cestino lascia il gruppo. `Proclamatore::anonimizza()` - anonimizza anche l'utenza dell'ex sorvegliante (nome, email, password, - ruoli). +- `Proclamatore::anonimizza()` anonimizza anche l'utenza dell'ex sorvegliante + (nome, email, password, ruoli). + +Decisioni successive dell'utente (20 ottobre 2026): +- **Sorvegliante non più attivo ⇒ gruppo sciolto** e territori non dati + rilasciati, come "Togli il ruolo". La regola sta nel modello + (`Proclamatore::booted`, eventi `updated` su `attivo` e `deleted`), così + vale dall'elenco, dalla modifica, dal cestino e dall'anonimizzazione; il + motivo finisce nel registro (`gruppo_sciolto`, proprietà `motivo`). + L'elenco chiede conferma prima di disattivare o cestinare un sorvegliante; + la modifica lo avvisa sotto la spunta "Attivo". Il cestino e + l'anonimizzazione erano prima bloccati per il sorvegliante: li ho allineati + alla stessa regola. Un membro non attivo resta membro (non riceve + territori); nel cestino esce dal gruppo. +- **Dettagli nel profilo**: la scheda del proclamatore (`ProclamatoreShow`) + mostra il gruppo che guida con i numeri (`GruppoServizio::riepilogo()`), + l'utenza (email solo all'amministratore, password temporanea o no, ultimo + accesso dal registro), oppure il gruppo di cui è membro, e i gruppi sciolti + che ha guidato. Il *Profilo* del sorvegliante mostra il suo gruppo con gli + stessi numeri e "Torna al mio gruppo". - L'utenza del sorvegliante porta nome ed email del proclamatore: nel registro delle attività `User::tapActivity()` li sostituisce con "proclamatore #id", come vale per tutti i proclamatori. Restano invece in chiaro nella tabella diff --git a/README.md b/README.md index d6a33a3..8addf69 100644 --- a/README.md +++ b/README.md @@ -759,9 +759,18 @@ docker compose --profile dev up -d --build disattivata (anche una sessione aperta si chiude) e il gruppo resta nello storico. I territori già dati restano ai membri. Per ripartire si crea un gruppo nuovo; se il sorvegliante è lo stesso, si riattiva la sua utenza -- Il sorvegliante di un gruppo attivo non si sposta nel cestino né si - anonimizza: prima si scioglie il gruppo. Anonimizzando un ex sorvegliante - si anonimizza anche la sua utenza +- **Un sorvegliante che non è più attivo scioglie il gruppo**, come togliergli + il ruolo: succede se lo si rende non attivo (dall'elenco o dalla modifica), + se lo si sposta nel cestino o lo si anonimizza. Prima di disattivarlo o + cestinarlo dall'elenco si chiede conferma; riattivarlo non ricostruisce il + gruppo. Un membro non attivo resta nel gruppo ma non riceve territori; nel + cestino esce dal gruppo. Anonimizzando un ex sorvegliante si anonimizza + anche la sua utenza +- **Dettagli del gruppo**: la scheda del proclamatore dice se è sorvegliante + (gruppo, membri, territori nel pacchetto e da dare, assegnazioni nate dal + gruppo, stato dell'utenza e ultimo accesso), membro, o sorvegliante di + gruppi ormai sciolti; il sorvegliante ritrova gli stessi numeri nel suo + *Profilo* ### Campagne - CRUD con date di inizio e fine diff --git a/app/Livewire/Proclamatori/ProclamatoreEdit.php b/app/Livewire/Proclamatori/ProclamatoreEdit.php index 653561d..6378e91 100644 --- a/app/Livewire/Proclamatori/ProclamatoreEdit.php +++ b/app/Livewire/Proclamatori/ProclamatoreEdit.php @@ -38,13 +38,17 @@ class ProclamatoreEdit extends Component $this->validate(); + // Un sorvegliante reso non attivo scioglie il gruppo (Proclamatore::booted). + $gruppo = ! $this->attivo ? $this->proclamatore->gruppoSorvegliato() : null; + $this->proclamatore->update([ 'nome' => $this->nome, 'cognome' => $this->cognome, 'attivo' => $this->attivo, ]); - session()->flash('success', "Proclamatore {$this->proclamatore->nome_completo} aggiornato."); + $avviso = "Proclamatore {$this->proclamatore->nome_completo} aggiornato." . ($gruppo ? ' ' . ProclamatoreIndex::gruppoSciolto($gruppo) : ''); + session()->flash('success', $avviso); return $this->redirect($this->destinazione(route('proclamatori.index')), navigate: true); } @@ -53,6 +57,7 @@ class ProclamatoreEdit extends Component return view('livewire.proclamatori.proclamatore-form', [ 'titolo' => "Modifica {$this->proclamatore->nome_completo}", 'btnLabel' => 'Salva Modifiche', + 'gruppoSorvegliato' => $this->proclamatore->gruppoSorvegliato(), ]); } } diff --git a/app/Livewire/Proclamatori/ProclamatoreIndex.php b/app/Livewire/Proclamatori/ProclamatoreIndex.php index 7848bc9..469344a 100644 --- a/app/Livewire/Proclamatori/ProclamatoreIndex.php +++ b/app/Livewire/Proclamatori/ProclamatoreIndex.php @@ -58,7 +58,13 @@ class ProclamatoreIndex extends Component $this->authorize('settings.manage'); $proclamatore = Proclamatore::findOrFail($id); + $gruppo = $proclamatore->attivo ? $proclamatore->gruppoSorvegliato() : null; + // Un sorvegliante reso non attivo scioglie il gruppo (Proclamatore::booted). $proclamatore->update(['attivo' => !$proclamatore->attivo]); + + if ($gruppo) { + $this->avvisa("{$proclamatore->nome_completo} non è più attivo. " . self::gruppoSciolto($gruppo)); + } } public function deleteProclamatore(int $id) @@ -71,14 +77,12 @@ class ProclamatoreIndex extends Component $this->avvisaErrore("Il proclamatore {$proclamatore->nome_completo} ha assegnazioni aperte. Rientra prima i territori."); return; } - if ($this->sorveglianteDiUnGruppo($proclamatore, 'spostarlo nel cestino')) { - return; - } - // Nel cestino non resta membro di un gruppo. - $proclamatore->update(['gruppo_servizio_id' => null]); + // Nel cestino lascia il gruppo; se ne era il sorvegliante, il gruppo + // si scioglie (Proclamatore::booted). + $gruppo = $proclamatore->gruppoSorvegliato(); $proclamatore->delete(); - $this->avvisa("Proclamatore {$proclamatore->nome_completo} spostato nel cestino."); + $this->avvisa("Proclamatore {$proclamatore->nome_completo} spostato nel cestino." . ($gruppo ? ' ' . self::gruppoSciolto($gruppo) : '')); } public function anonimizza(int $id) @@ -86,25 +90,15 @@ class ProclamatoreIndex extends Component $this->authorize('settings.manage'); $proclamatore = Proclamatore::findOrFail($id); - if ($this->sorveglianteDiUnGruppo($proclamatore, 'anonimizzarlo')) { - return; - } - + $gruppo = $proclamatore->gruppoSorvegliato(); $proclamatore->anonimizza(); - $this->avvisa("Dati personali del proclamatore anonimizzati (GDPR)."); + $this->avvisa("Dati personali del proclamatore anonimizzati (GDPR)." . ($gruppo ? ' ' . self::gruppoSciolto($gruppo) : '')); } - /** Il sorvegliante di un gruppo attivo si toglie sciogliendo il gruppo, non da qui. */ - private function sorveglianteDiUnGruppo(Proclamatore $proclamatore, string $azione): bool + /** L'avviso quando un sorvegliante esce di scena: anche per ProclamatoreEdit. */ + public static function gruppoSciolto(GruppoServizio $gruppo): string { - $gruppo = GruppoServizio::attivi()->where('sorvegliante_id', $proclamatore->id)->first(); - if (! $gruppo) { - return false; - } - - $this->avvisaErrore("{$proclamatore->nome_completo} è il sorvegliante del gruppo {$gruppo->nome}: per {$azione} va prima sciolto il gruppo (Gruppi di servizio)."); - - return true; + return "Era il sorvegliante del gruppo {$gruppo->nome}: il gruppo è stato sciolto, i territori non ancora dati sono tornati in reparto e la sua utenza è disattivata."; } public function render() @@ -176,6 +170,8 @@ class ProclamatoreIndex extends Component return view('livewire.proclamatori.proclamatore-index', [ 'proclamatori' => $proclamatori, + // Per avvisare, prima di disattivare o cestinare, che il gruppo si scioglie. + 'gruppiSorvegliati' => GruppoServizio::attivi()->pluck('nome', 'sorvegliante_id'), ]); } } diff --git a/app/Livewire/Proclamatori/ProclamatoreShow.php b/app/Livewire/Proclamatori/ProclamatoreShow.php index 568d437..9817526 100644 --- a/app/Livewire/Proclamatori/ProclamatoreShow.php +++ b/app/Livewire/Proclamatori/ProclamatoreShow.php @@ -6,6 +6,8 @@ use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Proclamatore; use App\Models\Assegnazione; +use App\Models\GruppoServizio; +use Spatie\Activitylog\Models\Activity; class ProclamatoreShow extends Component { @@ -37,9 +39,25 @@ class ProclamatoreShow extends Component ), ]; + // Gruppo di servizio: sorvegliante (con i dettagli del gruppo e della + // sua utenza), membro, o sorvegliante di gruppi ormai sciolti. + $gruppoSorvegliato = $this->proclamatore->gruppoSorvegliato(); + $utenza = $gruppoSorvegliato ? $this->proclamatore->utente : null; + return view('livewire.proclamatori.proclamatore-show', [ 'assegnazioniPerAnno' => $assegnazioni, 'stats' => $stats, + 'gruppoSorvegliato' => $gruppoSorvegliato, + 'riepilogoGruppo' => $gruppoSorvegliato?->riepilogo(), + 'gruppoMembro' => $gruppoSorvegliato ? null : $this->proclamatore->gruppo()->with('sorvegliante')->first(), + 'gruppiPassati' => GruppoServizio::whereNotNull('sciolto_at') + ->where('sorvegliante_id', $this->proclamatore->id) + ->orderByDesc('sciolto_at') + ->get(), + 'utenza' => $utenza, + 'ultimoAccesso' => $utenza + ? Activity::whereMorphedTo('causer', $utenza)->where('description', 'login')->max('created_at') + : null, ]); } } diff --git a/app/Livewire/Profile.php b/app/Livewire/Profile.php index c89df8a..3bc772d 100644 --- a/app/Livewire/Profile.php +++ b/app/Livewire/Profile.php @@ -87,8 +87,13 @@ class Profile extends Component public function render() { + // Il sorvegliante vede qui i dettagli del suo gruppo di servizio. + $gruppo = auth()->user()->gruppoSorvegliato(); + return view('livewire.profile', [ 'user' => auth()->user(), + 'gruppo' => $gruppo, + 'riepilogoGruppo' => $gruppo?->riepilogo(), ]); } } diff --git a/app/Models/GruppoServizio.php b/app/Models/GruppoServizio.php index 9740fd2..d185830 100644 --- a/app/Models/GruppoServizio.php +++ b/app/Models/GruppoServizio.php @@ -72,4 +72,24 @@ class GruppoServizio extends Model { return $query->whereNull('sciolto_at'); } + + /** + * I numeri del gruppo per la scheda del proclamatore e il profilo del + * sorvegliante: solo conteggi in SQL, nessun nome da decifrare. + * + * @return array{membri: int, pacchetto: int, da_dare: int, dati: int, assegnazioni: int} + */ + public function riepilogo(): array + { + $pacchetto = $this->territori()->count(); + $dati = $this->territori()->whereHas('assegnazioni', fn ($q) => $q->whereNull('returned_at'))->count(); + + return [ + 'membri' => $this->membri()->count(), + 'pacchetto' => $pacchetto, + 'da_dare' => $pacchetto - $dati, + 'dati' => $dati, + 'assegnazioni' => $this->assegnazioni()->count(), + ]; + } } diff --git a/app/Models/Proclamatore.php b/app/Models/Proclamatore.php index 62fc41d..a81fbd2 100644 --- a/app/Models/Proclamatore.php +++ b/app/Models/Proclamatore.php @@ -30,6 +30,46 @@ class Proclamatore extends Model return $this->hasOne(User::class, 'proclamatore_id'); } + /** + * Un sorvegliante che non è più attivo — reso non attivo, spostato nel + * cestino o anonimizzato — non guida più il gruppo: il gruppo si scioglie + * (GruppiServizio::sciogli), da qualunque pagina arrivi la modifica. Un + * membro nel cestino lascia il gruppo. + */ + protected static function booted(): void + { + static::updated(function (self $proclamatore) { + if ($proclamatore->wasChanged('attivo') && ! $proclamatore->attivo) { + $proclamatore->lasciaIlGruppo('sorvegliante non più attivo'); + } + }); + + static::deleted(function (self $proclamatore) { + $proclamatore->lasciaIlGruppo('sorvegliante eliminato'); + }); + } + + /** Il gruppo attivo di cui è sorvegliante, se c'è. */ + public function gruppoSorvegliato(): ?GruppoServizio + { + return GruppoServizio::attivi()->where('sorvegliante_id', $this->id)->first(); + } + + private function lasciaIlGruppo(string $motivo): void + { + $gruppo = $this->gruppoSorvegliato(); + if ($gruppo) { + app(\App\Services\GruppiServizio::class)->sciogli($gruppo, $motivo); + + return; + } + + if ($this->trashed() && $this->gruppo_servizio_id) { + static::withTrashed()->whereKey($this->id)->update(['gruppo_servizio_id' => null]); + $this->gruppo_servizio_id = null; + } + } + protected function casts(): array { return [ diff --git a/app/Services/GruppiServizio.php b/app/Services/GruppiServizio.php index 4e65b49..5513f07 100644 --- a/app/Services/GruppiServizio.php +++ b/app/Services/GruppiServizio.php @@ -231,13 +231,13 @@ class GruppiServizio * fatte restano com'erano, con il loro riferimento al gruppo, che resta * nello storico come sciolto. */ - public function sciogli(GruppoServizio $gruppo): void + public function sciogli(GruppoServizio $gruppo, ?string $motivo = null): void { if ($gruppo->sciolto_at) { return; } - DB::transaction(function () use ($gruppo) { + DB::transaction(function () use ($gruppo, $motivo) { // Esce dal pacchetto solo quello non ancora dato: un territorio // assegnato resta del suo gruppo finché non rientra (Assegnazione // lo libera al rientro), così non può essere ridato due volte. @@ -258,7 +258,11 @@ class GruppiServizio $gruppo->update(['sciolto_at' => now()]); activity()->causedBy(auth()->user())->performedOn($gruppo) - ->withProperties(['territori_liberati' => $liberati->all(), 'utente_disattivato' => $utente?->id]) + ->withProperties(array_filter([ + 'territori_liberati' => $liberati->all(), + 'utente_disattivato' => $utente?->id, + 'motivo' => $motivo, + ], fn ($v) => $v !== null)) ->log('gruppo_sciolto'); }); } diff --git a/resources/views/livewire/proclamatori/proclamatore-form.blade.php b/resources/views/livewire/proclamatori/proclamatore-form.blade.php index d3b3bed..b06f646 100644 --- a/resources/views/livewire/proclamatori/proclamatore-form.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-form.blade.php @@ -25,6 +25,12 @@ + @if($gruppoSorvegliato ?? null) +

+ È il sorvegliante del gruppo {{ $gruppoSorvegliato->nome }}: se non è più attivo il gruppo si scioglie, + i territori non ancora dati tornano in reparto e la sua utenza viene disattivata. +

+ @endif
{{ $btnLabel }} diff --git a/resources/views/livewire/proclamatori/proclamatore-index.blade.php b/resources/views/livewire/proclamatori/proclamatore-index.blade.php index fe7740a..f990ec9 100644 --- a/resources/views/livewire/proclamatori/proclamatore-index.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-index.blade.php @@ -73,6 +73,15 @@ title="Modifica" aria-label="Modifica il proclamatore"> + @if($proclamatore->attivo && isset($gruppiSorvegliati[$proclamatore->id])) + {{-- Sorvegliante: disattivarlo scioglie il gruppo, si chiede conferma --}} + + + + @else + @endif diff --git a/resources/views/livewire/proclamatori/proclamatore-show.blade.php b/resources/views/livewire/proclamatori/proclamatore-show.blade.php index 5bf7c0f..509dce9 100644 --- a/resources/views/livewire/proclamatori/proclamatore-show.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-show.blade.php @@ -36,6 +36,73 @@
+ {{-- Gruppo di servizio --}} + @if($gruppoSorvegliato || $gruppoMembro || $gruppiPassati->isNotEmpty()) +
+

Gruppo di servizio

+ + @if($gruppoSorvegliato) +

+ Sorvegliante + del gruppo + @can('settings.manage') + {{ $gruppoSorvegliato->nome }} + @else + {{ $gruppoSorvegliato->nome }} + @endcan + dal {{ $gruppoSorvegliato->created_at->format('d/m/Y') }} +

+ +
+
+
Membri
+
{{ $riepilogoGruppo['membri'] }}
+
+
+
Nel pacchetto
+
{{ $riepilogoGruppo['pacchetto'] }}
+
+
+
Da dare
+
{{ $riepilogoGruppo['da_dare'] }}
+
+
+
Assegnazioni dal gruppo
+
{{ $riepilogoGruppo['assegnazioni'] }}
+
+
+ + @if($utenza) +

+ Utenza + @can('settings.manage') {{ $utenza->email }} @endcan + · {{ $utenza->password_change_required ? 'password temporanea, ancora da cambiare' : 'password personale impostata' }} + · {{ $ultimoAccesso ? 'ultimo accesso il ' . \Illuminate\Support\Carbon::parse($ultimoAccesso)->format('d/m/Y H:i') : 'non è ancora entrato' }} +

+ @endif + @elseif($gruppoMembro) +

+ Membro del gruppo + @can('settings.manage') + {{ $gruppoMembro->nome }}, + @else + {{ $gruppoMembro->nome }}, + @endcan + sorvegliante {{ $gruppoMembro->sorvegliante?->nome_completo ?? '—' }}. +

+ @endif + + @if($gruppiPassati->isNotEmpty()) +

+ In passato sorvegliante di: + @foreach($gruppiPassati as $g) + {{ $g->nome }} (sciolto il {{ $g->sciolto_at->format('d/m/Y') }}){{ $loop->last ? '.' : ',' }} + @endforeach +

+ @endif +
+ @endif + {{-- Assignment history --}}

Storico Assegnazioni

diff --git a/resources/views/livewire/profile.blade.php b/resources/views/livewire/profile.blade.php index fa26883..7444c42 100644 --- a/resources/views/livewire/profile.blade.php +++ b/resources/views/livewire/profile.blade.php @@ -1,8 +1,8 @@
@@ -25,6 +25,37 @@
+ {{-- Gruppo di servizio (solo per il sorvegliante) --}} + @if($gruppo) +
+

Il tuo gruppo di servizio

+

+ Sei il sorvegliante del gruppo {{ $gruppo->nome }} dal {{ $gruppo->created_at->format('d/m/Y') }}. +

+
+
+
Membri
+
{{ $riepilogoGruppo['membri'] }}
+
+
+
Nel pacchetto
+
{{ $riepilogoGruppo['pacchetto'] }}
+
+
+
Da dare
+
{{ $riepilogoGruppo['da_dare'] }}
+
+
+
Dati ai membri
+
{{ $riepilogoGruppo['dati'] }}
+
+
+
+ Vai al mio gruppo +
+
+ @endif + {{-- Dati profilo --}}

Dati profilo

diff --git a/tests/Feature/GruppiServizioTest.php b/tests/Feature/GruppiServizioTest.php index c97e2e8..8ee8a6a 100644 --- a/tests/Feature/GruppiServizioTest.php +++ b/tests/Feature/GruppiServizioTest.php @@ -377,24 +377,120 @@ class GruppiServizioTest extends TestCase $this->assertSame($nuovo->id, $utente->gruppoSorvegliato()?->id); } - // ─── Proclamatori: cestino, anonimizzazione, registro ─────── + // ─── Sorvegliante non più attivo: il gruppo si scioglie ───── - public function test_il_sorvegliante_di_un_gruppo_attivo_non_si_cestina_ne_si_anonimizza(): void + /** Il gruppo è sciolto come da "Togli il ruolo": tutto tranne l'assegnazione già fatta. */ + private function assertSciolto(array $g, Assegnazione $data): void { - ['rossi' => $rossi, 'verdi' => $verdi] = $this->gruppo(); + $this->assertNotNull($g['gruppo']->fresh()->sciolto_at, 'Gruppo sciolto'); + $this->assertNull($g['territori'][1]->fresh()->gruppo_servizio_id, 'I non dati tornano in reparto'); + $this->assertSame('in_reparto', $g['territori'][2]->fresh()->stato); + $this->assertNull($data->fresh()->returned_at, 'Quello già dato resta al membro'); + $this->assertNull($g['bianchi']->fresh()->gruppo_servizio_id, 'Membri liberi'); + $this->assertTrue($g['sorvegliante']->fresh()->isDisattivato()); + $this->assertFalse($g['sorvegliante']->fresh()->hasRole('sorvegliante')); + } + + public function test_disattivare_il_sorvegliante_dall_elenco_scioglie_il_gruppo(): void + { + $g = $this->gruppo(); + $data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now()); Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class) - ->call('deleteProclamatore', $rossi->id) - ->call('anonimizza', $rossi->id); - $this->assertFalse($rossi->fresh()->trashed()); - $this->assertSame('Rossi', $rossi->fresh()->cognome); + ->assertSeeHtml('è il sorvegliante del gruppo Zisa') + ->call('toggleActive', $g['rossi']->id) + ->assertDispatched('avviso', fn ($nome, $parametri) => str_contains($parametri['testo'], 'il gruppo è stato sciolto')); - // Un membro qualsiasi sì, e lascia il gruppo. - Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('deleteProclamatore', $verdi->id); - $this->assertTrue($verdi->fresh()->trashed()); - $this->assertNull($verdi->fresh()->gruppo_servizio_id); + $this->assertFalse($g['rossi']->fresh()->attivo); + $this->assertSciolto($g, $data); + + // Riattivarlo non ricostruisce il gruppo: se ne crea uno nuovo. + Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('toggleActive', $g['rossi']->id); + $this->assertNotNull($g['gruppo']->fresh()->sciolto_at); + $this->assertNull($g['rossi']->fresh()->gruppoSorvegliato()); } + public function test_disattivare_il_sorvegliante_dalla_modifica_scioglie_il_gruppo(): void + { + $g = $this->gruppo(); + $data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now()); + + $this->get(route('proclamatori.edit', $g['rossi']))->assertOk()->assertSee('se non è più attivo il gruppo si scioglie'); + + Livewire::test(\App\Livewire\Proclamatori\ProclamatoreEdit::class, ['proclamatore' => $g['rossi']]) + ->set('attivo', false) + ->call('save') + ->assertHasNoErrors(); + + $this->assertSciolto($g, $data); + $this->assertStringContainsString('il gruppo è stato sciolto', session('success')); + } + + public function test_spostare_nel_cestino_il_sorvegliante_scioglie_il_gruppo(): void + { + $g = $this->gruppo(); + $data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now()); + + Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('deleteProclamatore', $g['rossi']->id); + + $this->assertTrue($g['rossi']->fresh()->trashed()); + $this->assertSciolto($g, $data); + } + + public function test_disattivare_o_cestinare_un_membro_non_scioglie_il_gruppo(): void + { + ['gruppo' => $gruppo, 'verdi' => $verdi, 'bianchi' => $bianchi] = $this->gruppo(); + + Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class) + ->call('toggleActive', $verdi->id) + ->call('deleteProclamatore', $bianchi->id); + + $this->assertNull($gruppo->fresh()->sciolto_at); + $this->assertSame($gruppo->id, $verdi->fresh()->gruppo_servizio_id, 'Non attivo resta membro, ma non riceve territori'); + $this->assertNull($bianchi->fresh()->gruppo_servizio_id, 'Nel cestino lascia il gruppo'); + } + + // ─── Dettagli del gruppo nel profilo ──────────────────────── + + public function test_la_scheda_del_proclamatore_mostra_il_gruppo_che_guida(): void + { + $g = $this->gruppo(); + app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now()); + + $this->get(route('proclamatori.show', $g['rossi']))->assertOk() + ->assertSeeInOrder(['Gruppo di servizio', 'Sorvegliante', 'Zisa', 'Membri', '3', 'Nel pacchetto', '3', 'Da dare', '2', 'Assegnazioni dal gruppo', '1']) + ->assertSee('mario@example.test') + ->assertSee('non è ancora entrato'); + + $this->get(route('proclamatori.show', $g['verdi']))->assertOk() + ->assertSee('Membro del gruppo') + ->assertSee('sorvegliante Rossi Mario'); + + app(GruppiServizio::class)->sciogli($g['gruppo']); + $this->get(route('proclamatori.show', $g['rossi']))->assertOk() + ->assertSee('In passato sorvegliante di:') + ->assertDontSee('Assegnazioni dal gruppo'); + } + + public function test_il_profilo_del_sorvegliante_mostra_il_suo_gruppo(): void + { + $g = $this->gruppo(); + app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now()); + $this->flushSession(); + + $this->actingAs($g['sorvegliante'])->get(route('profile'))->assertOk() + ->assertSee('Il tuo gruppo di servizio') + ->assertSeeInOrder(['Zisa', 'Membri', '3', 'Nel pacchetto', '3', 'Da dare', '2', 'Dati ai membri', '1']) + ->assertSee('Torna al mio gruppo'); + + // L'amministratore un gruppo suo non ce l'ha. + $this->actingAs($this->admin)->get(route('profile'))->assertOk() + ->assertDontSee('Il tuo gruppo di servizio') + ->assertSee('Torna alla home'); + } + + // ─── Proclamatori: anonimizzazione, registro ──────────────── + public function test_anonimizzare_un_ex_sorvegliante_anonimizza_anche_la_sua_utenza(): void { ['gruppo' => $gruppo, 'rossi' => $rossi, 'sorvegliante' => $s] = $this->gruppo();