++ gruppi: email fittizia iniziale+cognome per il sorvegliante; sciolto il gruppo l'utenza si elimina

- L'utenza del sorvegliante ha un'email fittizia creata dall'app:
  iniziale del nome e cognome @termanager.it (ffontana@termanager.it),
  come gli altri utenti. Via il nome utente: login di nuovo per email.
- Sciolto il gruppo l'utenza si elimina, il sorvegliante torna un
  proclamatore come gli altri: assegnazioni a chi scioglie, registro a nome
  "Sorvegliante del gruppo ...". Via le utenze disattivate.
- User::eliminaConservandoStorico(): l'eliminazione della pagina Utenti in
  un metodo solo, usato anche dallo scioglimento.
- Migrazione 000030: elimina le utenze gia' disattivate, toglie
  users.username e users.disattivato_at, email di nuovo obbligatoria.
- XML, informativa privacy, README, HANDOFF e test aggiornati.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Francesco Picone
2026-10-20 12:09:15 +02:00
parent 69250ecb8c
commit ec2e7bd7e9
26 changed files with 347 additions and 311 deletions

View File

@@ -73,7 +73,7 @@ class GruppiServizioTest extends TestCase
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, $territori->pluck('id')->all());
$territori = $territori->map->fresh();
$sorvegliante = User::where('username', 'zisa')->sole();
$sorvegliante = User::where('email', 'mrossi@termanager.it')->sole();
// Il cambio password al primo accesso ha i suoi test: qui si lavora.
// E l'email non confermata, come nella realtà (TestCase le conferma).
$sorvegliante->forceFill(['password_change_required' => false, 'email_verified_at' => null])->save();
@@ -87,20 +87,18 @@ class GruppiServizioTest extends TestCase
{
$rossi = $this->proclamatore('Rossi', 'Mario');
// Niente email: il nome utente viene dal nome del gruppo.
// L'email non si chiede: è fittizia, iniziale del nome e cognome.
$pagina = Livewire::test(GruppiIndex::class)
->assertDontSee('Email per l')
->set('nome', 'Zisa')
->set('sorveglianteId', $rossi->id)
->call('crea')
->assertHasNoErrors()
->assertSee('Nome utente')
->assertSee('zisa');
->assertSee('mrossi@termanager.it');
$gruppo = GruppoServizio::sole();
$utente = User::where('username', 'zisa')->sole();
$utente = User::where('email', 'mrossi@termanager.it')->sole();
$this->assertNull($utente->email);
$this->assertSame($rossi->id, $gruppo->sorvegliante_id);
$this->assertSame($gruppo->id, $rossi->fresh()->gruppo_servizio_id, 'Il sorvegliante è anche membro');
$this->assertSame($rossi->id, $utente->proclamatore_id);
@@ -118,21 +116,22 @@ class GruppiServizioTest extends TestCase
$this->assertTrue(\Illuminate\Support\Facades\Hash::check($password, $utente->password));
}
public function test_il_nome_utente_viene_dal_nome_del_gruppo(): void
public function test_l_email_fittizia_viene_da_iniziale_del_nome_e_cognome(): void
{
$servizio = app(GruppiServizio::class);
$email = fn (string $cognome, string $nome) => GruppiServizio::emailFittizia($this->proclamatore($cognome, $nome));
$servizio->crea('Gruppo Noce Città', $this->proclamatore('Neri'));
$this->assertSame('gruppo.noce.citta', User::whereNotNull('username')->sole()->username);
$this->assertSame('ffontana@termanager.it', $email('Fontana', 'Francesco'));
$this->assertSame('fdangelo@termanager.it', $email("D'Angelo", 'Francesco'));
$this->assertSame('ndeluca@termanager.it', $email('De Luca', 'Niccolò'));
// Già preso (per esempio da un'utenza riattivata): un numero in coda.
User::create(['name' => 'Altro', 'username' => 'zisa', 'password' => 'password-di-prova']);
$servizio->crea('Zisa', $this->proclamatore('Gialli'));
$this->assertTrue(User::where('username', 'zisa2')->exists());
// Già presa da un altro utente: un numero in coda.
User::create(['name' => 'Francesco Fontana', 'email' => 'ffontana@termanager.it', 'password' => 'password-di-prova']);
$this->assertSame('ffontana2@termanager.it', $email('Fontana', 'Federica'));
// Due gruppi con lo stesso nome no.
app(GruppiServizio::class)->crea('Zisa', $this->proclamatore('Gialli'));
$this->expectException(InvalidArgumentException::class);
$servizio->crea('Zisa', $this->proclamatore('Blu'));
app(GruppiServizio::class)->crea('Zisa', $this->proclamatore('Blu'));
}
public function test_un_proclamatore_sta_in_un_solo_gruppo(): void
@@ -220,25 +219,16 @@ class GruppiServizioTest extends TestCase
$this->get(route('dashboard'))->assertOk()->assertDontSee('Il mio gruppo')->assertSee('Gruppi di servizio');
}
public function test_il_sorvegliante_entra_con_il_nome_utente_e_va_al_suo_gruppo(): void
public function test_il_sorvegliante_entra_con_l_email_fittizia_senza_conferma_e_va_al_suo_gruppo(): void
{
$this->gruppo();
$this->flushSession();
auth()->logout();
$password = 'una-password-nuova-1';
User::where('username', 'zisa')->sole()->forceFill(['password' => $password])->save();
User::where('email', 'mrossi@termanager.it')->sole()->forceFill(['password' => $password])->save();
$this->get(route('login'))->assertSee('Email o nome utente');
// Senza badare alle maiuscole (la tastiera del telefono le mette da sé).
$this->post(route('login.store'), ['email' => 'Zisa', 'password' => $password])
$this->post(route('login.store'), ['email' => 'mrossi@termanager.it', 'password' => $password])
->assertRedirect(route('mio-gruppo'));
// Gli altri continuano a entrare con l'email.
auth()->logout();
$this->flushSession();
$this->post(route('login.store'), ['email' => 'admin@example.test', 'password' => 'password-di-prova'])
->assertRedirect(route('dashboard'));
}
// ─── Il sorvegliante al lavoro ──────────────────────────────
@@ -350,7 +340,10 @@ class GruppiServizioTest extends TestCase
public function test_togliere_il_ruolo_scioglie_il_gruppo(): void
{
['gruppo' => $gruppo, 'sorvegliante' => $s, 'verdi' => $verdi, 'territori' => $territori] = $this->gruppo();
// L'assegnazione la fa il sorvegliante, come nella realtà.
auth()->setUser($s);
$data = app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
auth()->setUser($this->admin);
Livewire::test(GruppoShow::class, ['gruppo' => $gruppo])
->call('sciogli')
@@ -368,20 +361,26 @@ class GruppiServizioTest extends TestCase
$this->assertNull($data->fresh()->gruppo_servizio_id);
$this->assertSame('Zisa', $data->fresh()->gruppo_nome);
$this->get(route('territori.show', $territori[0]))->assertOk()->assertSee('via gruppo Zisa');
// Membri liberi; utenza disattivata, senza ruolo, e il nome utente del
// gruppo è libero.
// Membri liberi; l'utenza del sorvegliante non c'è più, lui torna un
// proclamatore come gli altri.
$this->assertSame(0, Proclamatore::whereNotNull('gruppo_servizio_id')->count());
$this->assertTrue($s->fresh()->isDisattivato());
$this->assertFalse($s->fresh()->hasRole('sorvegliante'));
$this->assertSame("ex.sorvegliante.{$s->id}", $s->fresh()->username);
$this->assertFalse(User::where('username', 'zisa')->exists());
$this->assertNull(User::find($s->id));
$this->assertNotNull(Proclamatore::find($s->proclamatore_id));
$this->assertSame(0, \Illuminate\Support\Facades\DB::table('model_has_roles')->where('model_id', $s->id)->count());
// Quello che aveva fatto passa a chi ha sciolto il gruppo; nel registro
// le sue voci restano, senza dati personali.
$this->assertSame($this->admin->id, $data->fresh()->created_by);
$voce = \Spatie\Activitylog\Models\Activity::where('description', 'assigned')->sole();
$this->assertNull($voce->causer);
$this->assertSame('Sorvegliante del gruppo Zisa', $voce->properties['causer_name']);
$this->assertNull($voce->properties['causer_email']);
// Per ripartire serve un gruppo nuovo.
$this->expectException(InvalidArgumentException::class);
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, [$territori[1]->id]);
}
public function test_un_utenza_disattivata_non_entra_e_viene_buttata_fuori(): void
public function test_sciolto_il_gruppo_l_ex_sorvegliante_non_entra_piu(): void
{
['gruppo' => $gruppo, 'sorvegliante' => $s] = $this->gruppo();
$password = 'una-password-nuova-1';
@@ -390,29 +389,25 @@ class GruppiServizioTest extends TestCase
$this->flushSession();
auth()->logout();
$this->post(route('login.store'), ['email' => 'zisa', 'password' => $password])
$this->post(route('login.store'), ['email' => 'mrossi@termanager.it', 'password' => $password])
->assertSessionHasErrors('email');
$this->assertGuest();
$this->actingAs($s->fresh())->get(route('profile'))->assertRedirect(route('login'));
}
public function test_l_ex_sorvegliante_puo_guidare_un_gruppo_nuovo_con_la_stessa_utenza(): void
public function test_l_ex_sorvegliante_puo_guidare_un_gruppo_nuovo(): void
{
['gruppo' => $vecchio, 'rossi' => $rossi, 'sorvegliante' => $s] = $this->gruppo();
['gruppo' => $vecchio, 'rossi' => $rossi] = $this->gruppo();
app(GruppiServizio::class)->sciogli($vecchio);
$pagina = Livewire::test(GruppiIndex::class);
$this->assertContains((string) $rossi->id, collect($pagina->viewData('opzioniSorveglianti'))->pluck('valore')->all(), 'Torna tra i candidati');
// Sciolto, il nome non è più occupato: si può rifare "Zisa".
// Sciolto, il nome del gruppo e l'email sono di nuovo liberi.
[$nuovo, $password] = app(GruppiServizio::class)->crea('Zisa', $rossi->fresh());
$utente = $s->fresh();
$this->assertSame(1, User::where('proclamatore_id', $rossi->id)->count(), 'Un proclamatore, un\'utenza');
$this->assertFalse($utente->isDisattivato());
$utente = User::where('proclamatore_id', $rossi->id)->sole();
$this->assertTrue($utente->hasRole('sorvegliante'));
$this->assertSame('zisa', $utente->username);
$this->assertSame('mrossi@termanager.it', $utente->email);
$this->assertTrue($utente->password_change_required);
$this->assertTrue(\Illuminate\Support\Facades\Hash::check($password, $utente->password));
$this->assertSame($nuovo->id, $utente->gruppoSorvegliato()?->id);
@@ -429,8 +424,7 @@ class GruppiServizioTest extends TestCase
$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'));
$this->assertNull(User::find($g['sorvegliante']->id), 'Utenza eliminata');
}
public function test_disattivare_il_sorvegliante_dall_elenco_scioglie_il_gruppo(): void
@@ -501,7 +495,7 @@ class GruppiServizioTest extends TestCase
$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('<code>zisa</code>', false)
->assertSee('<code>mrossi@termanager.it</code>', false)
->assertSee('non è ancora entrato');
$this->get(route('proclamatori.show', $g['verdi']))->assertOk()
@@ -563,19 +557,15 @@ class GruppiServizioTest extends TestCase
// ─── Proclamatori: anonimizzazione, registro ────────────────
public function test_anonimizzare_un_ex_sorvegliante_anonimizza_anche_la_sua_utenza(): void
public function test_anonimizzare_il_sorvegliante_scioglie_il_gruppo_e_ne_elimina_l_utenza(): void
{
['gruppo' => $gruppo, 'rossi' => $rossi, 'sorvegliante' => $s] = $this->gruppo();
app(GruppiServizio::class)->sciogli($gruppo);
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('anonimizza', $rossi->id);
$utente = $s->fresh();
$this->assertSame('Anonimo', $utente->name);
$this->assertNull($utente->email);
$this->assertSame("anonimo.{$utente->id}", $utente->username);
$this->assertTrue($utente->isDisattivato());
$this->assertCount(0, $utente->roles);
$this->assertNull(GruppoServizio::find($gruppo->id));
$this->assertNull(User::find($s->id), "Nome ed email dell'utenza spariscono con lei");
$this->assertSame('Anonimo', Proclamatore::withTrashed()->find($rossi->id)->nome);
}
public function test_nel_registro_delle_attivita_l_utenza_del_sorvegliante_non_ha_nome_ne_email(): void
@@ -624,8 +614,8 @@ class GruppiServizioTest extends TestCase
$this->assertEqualsCanonicalizing(['12', '14', '35'], $g->territori->pluck('numero')->all());
$this->assertSame($g->id, Assegnazione::sole()->gruppo_servizio_id);
$this->assertSame('Zisa', Assegnazione::sole()->gruppo_nome);
$utenza = User::where('username', 'zisa')->sole();
$this->assertNull($utenza->email);
$utenza = User::where('email', 'mrossi@termanager.it')->sole();
$this->assertTrue($utenza->hasRole('sorvegliante'));
$this->assertSame($g->sorvegliante_id, $utenza->proclamatore_id);
}
@@ -641,7 +631,7 @@ class GruppiServizioTest extends TestCase
$this->assertSame(0, GruppoServizio::count());
$this->assertNull(Assegnazione::sole()->gruppo_servizio_id);
$this->assertSame('Zisa', Assegnazione::sole()->gruppo_nome);
// L'utenza disattivata, autrice dell'assegnazione, sopravvive al giro.
$this->assertTrue(User::where('username', 'like', 'ex.sorvegliante.%')->sole()->isDisattivato());
// Dell'ex sorvegliante non resta un'utenza: solo l'amministratore.
$this->assertSame([$this->admin->id], User::pluck('id')->all());
}
}