++ 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:
67
HANDOFF.md
67
HANDOFF.md
@@ -150,21 +150,12 @@ e non vede nient'altro. Scelte dell'utente:
|
||||
pacchetti li gestisce **solo l'amministratore**.
|
||||
- Utenza: password temporanea da cambiare al primo accesso, **senza
|
||||
conferma dell'email** (`User::hasVerifiedEmail()` è vero per il
|
||||
sorvegliante). *Dal 20 ottobre: niente email, nome utente dal nome del
|
||||
gruppo — vedi sotto.*
|
||||
- **Togliere il ruolo = sciogliere il gruppo** (`GruppiServizio::sciogli`):
|
||||
non dati in reparto, membri liberi, utenza senza ruolo e disattivata
|
||||
(`users.disattivato_at`: login rifiutato, sessione aperta chiusa da
|
||||
`LimitaSorvegliante`). I territori già dati restano ai membri. Si riparte
|
||||
con un gruppo nuovo. *Dal 20 ottobre il gruppo sciolto si elimina — vedi
|
||||
sorvegliante). *Dal 20 ottobre l'email è fittizia, creata dall'app — vedi
|
||||
sotto.*
|
||||
|
||||
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 nome utente e password temporanea nuovi.
|
||||
- `Proclamatore::anonimizza()` anonimizza anche l'utenza dell'ex sorvegliante
|
||||
(nome, email, password, ruoli).
|
||||
- **Togliere il ruolo = sciogliere il gruppo** (`GruppiServizio::sciogli`):
|
||||
non dati in reparto, membri liberi. I territori già dati restano ai membri.
|
||||
Si riparte con un gruppo nuovo. *Dal 20 ottobre gruppo e utenza del
|
||||
sorvegliante si eliminano — vedi sotto.*
|
||||
|
||||
Decisioni successive dell'utente (20 ottobre 2026):
|
||||
- **Sorvegliante non più attivo ⇒ gruppo sciolto** e territori non dati
|
||||
@@ -179,7 +170,7 @@ Decisioni successive dell'utente (20 ottobre 2026):
|
||||
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 (nome utente solo all'amministratore, password temporanea o no,
|
||||
l'utenza (email solo all'amministratore, password temporanea o no,
|
||||
ultimo accesso dal registro), oppure il gruppo di cui è membro. Il
|
||||
*Profilo* del sorvegliante mostra il suo gruppo con gli stessi numeri e
|
||||
"Torna al mio gruppo".
|
||||
@@ -192,19 +183,33 @@ Decisioni successive dell'utente (20 ottobre 2026):
|
||||
territorio): **confermato dall'utente il 20 ottobre 2026**. Dopo lo
|
||||
scioglimento si torna all'elenco dei gruppi; nella scheda del proclamatore
|
||||
non c'è più "In passato sorvegliante di".
|
||||
- **Nome utente al posto dell'email**: creando il gruppo non si chiede
|
||||
l'email; `GruppiServizio::nomeUtente()` ricava `users.username` dal nome
|
||||
del gruppo (`Str::slug` con il punto: «Gruppo Zisa» → `gruppo.zisa`,
|
||||
numero in coda se già preso; mai una chiocciola). `users.email` è diventata
|
||||
facoltativa. Il login ha un solo campo, "Email o nome utente"
|
||||
(`LoginController`: con la chiocciola cerca l'email, senza il nome utente,
|
||||
in minuscolo). Dove si mostrava l'email dell'utente ora c'è
|
||||
`User::identificativo` (email, o nome utente). Il profilo del sorvegliante
|
||||
mostra il nome utente e non chiede l'email. Il nome del gruppo è unico.
|
||||
Sciolto il gruppo, il nome utente si libera e l'utenza disattivata prende
|
||||
`ex.sorvegliante.<id>` (l'anonimizzata `anonimo.<id>`): resta riconoscibile
|
||||
per l'import XML, che individua gli utenti per email o, senza, per nome
|
||||
utente. Un gruppo con `<sciolto_at>` in un XML vecchio non si importa.
|
||||
- **Email fittizia, niente nome utente** (seconda decisione del 20 ottobre,
|
||||
che sostituisce il nome utente ricavato dal nome del gruppo fatto qualche
|
||||
ora prima): creando il gruppo l'email non si chiede,
|
||||
`GruppiServizio::emailFittizia()` la ricava da iniziale del nome e cognome,
|
||||
minuscoli, senza accenti, spazi né apostrofi, sul dominio
|
||||
`GruppiServizio::DOMINIO_EMAIL` = `termanager.it` (Francesco Fontana →
|
||||
`ffontana@termanager.it`, D'Angelo → `fdangelo@…`, numero in coda se già
|
||||
presa) — la stessa convenzione degli altri utenti. Il login è di nuovo solo
|
||||
per email. Il profilo del sorvegliante mostra l'email ma non la fa
|
||||
cambiare. Il nome del gruppo è unico.
|
||||
- **Sciolto il gruppo, l'utenza del sorvegliante si elimina** ("torna
|
||||
proclamatore normale, non tenere traccia"): `User::eliminaConservandoStorico()`
|
||||
— la stessa eliminazione della pagina *Utenti*, ora in un metodo solo — passa
|
||||
le assegnazioni che aveva fatto o fatto rientrare a chi scioglie (o al primo
|
||||
amministratore) e lascia le sue voci nel registro a nome "Sorvegliante del
|
||||
gruppo …", senza email. Niente più utenze disattivate: via
|
||||
`users.disattivato_at` e i controlli in `LoginController` e
|
||||
`LimitaSorvegliante`; una sessione aperta resta senza utente.
|
||||
`Proclamatore::anonimizza()` non tocca più utenze: se era sorvegliante, il
|
||||
salvataggio scioglie il gruppo e l'utenza sparisce.
|
||||
- Migrazione `2026_10_20_000030_sorveglianti_con_email_fittizia`: elimina le
|
||||
utenze già disattivate (assegnazioni al primo amministratore, registro a
|
||||
nome "Ex sorvegliante di gruppo"), dà l'email fittizia a chi non l'aveva,
|
||||
toglie `users.username` e `users.disattivato_at`, rende di nuovo
|
||||
obbligatoria `users.email`. XML: niente più `<username>` né
|
||||
`<disattivato_at>`; da un file vecchio non si importano le utenze
|
||||
disattivate né i gruppi con `<sciolto_at>`.
|
||||
- **Anche i membri si segnalano**: `Proclamatore::gruppo_etichetta`
|
||||
("Gruppo Zisa" / "Sorvegliante del gruppo Zisa") come badge nell'elenco
|
||||
(più il filtro *In un gruppo di servizio*, `filtroStato=gruppo`) e in testa
|
||||
@@ -220,8 +225,8 @@ Decisioni successive dell'utente (20 ottobre 2026):
|
||||
Dove sta:
|
||||
- Migrazione `2026_10_20_000010_gruppi_di_servizio`: tabella
|
||||
`gruppi_servizio`, colonne `gruppo_servizio_id` su proclamatori, territori e
|
||||
assegnazioni, `users.proclamatore_id` (unico) e `users.disattivato_at`,
|
||||
permesso `gruppo.gestisci` e ruolo `sorvegliante`. Il seeder dà
|
||||
assegnazioni, `users.proclamatore_id` (unico) e `users.disattivato_at`
|
||||
(tolto dalla `000030`), permesso `gruppo.gestisci` e ruolo `sorvegliante`. Il seeder dà
|
||||
all'amministratore tutti i permessi **tranne** `gruppo.gestisci`.
|
||||
- Regole in `App\Services\GruppiServizio` (crea, aggiungiMembri, togliMembro,
|
||||
aggiungiAlPacchetto, rimandaInReparto, assegna, sciogli); i componenti
|
||||
@@ -238,7 +243,7 @@ Dove sta:
|
||||
- La voce di menu "Il mio gruppo" guarda il **ruolo**, non `@can`:
|
||||
l'amministratore passa ogni `@can` (`Gate::before`).
|
||||
- XML: si esportano e reimportano gruppi, membri, pacchetto, provenienza delle
|
||||
assegnazioni (id e nome del gruppo), nome utente e legame utenza ↔
|
||||
assegnazioni (id e nome del gruppo) e legame utenza ↔
|
||||
proclamatore.
|
||||
- Informativa privacy aggiornata (utenza del sorvegliante, gruppo tra i dati
|
||||
del proclamatore).
|
||||
|
||||
32
README.md
32
README.md
@@ -741,12 +741,13 @@ docker compose --profile dev up -d --build
|
||||
**sorvegliante** (un proclamatore attivo), **membri** e **pacchetto** di
|
||||
territori. Un proclamatore sta in un solo gruppo; il sorvegliante ne è
|
||||
anche membro
|
||||
- Creando il gruppo il sorvegliante riceve un'**utenza senza email**: un
|
||||
**nome utente ricavato dal nome del gruppo** («Gruppo Zisa» →
|
||||
`gruppo.zisa`; se è già preso, un numero in coda) e una password
|
||||
temporanea, mostrati una volta sola, da cambiare al primo accesso. Al login
|
||||
il campo accetta l'email o il nome utente. Due gruppi non possono avere lo
|
||||
stesso nome. Se perde la password, l'amministratore ne genera un'altra
|
||||
- Creando il gruppo il sorvegliante riceve un'**utenza** con un'**email
|
||||
fittizia** creata dall'applicazione: iniziale del nome e cognome sul
|
||||
dominio `termanager.it` (Francesco Fontana → `ffontana@termanager.it`; se
|
||||
è già presa, un numero in coda), e una password temporanea. Si vedono una
|
||||
volta sola; la password va cambiata al primo accesso. L'email non si
|
||||
chiede, non riceve posta e non va confermata. Due gruppi non possono avere
|
||||
lo stesso nome. Se perde la password, l'amministratore ne genera un'altra
|
||||
dalla pagina del gruppo
|
||||
- I territori del pacchetto si aggiungono in blocco tra quelli in reparto.
|
||||
Restano **"In gruppo …"**: non sono un'assegnazione (statistiche e medie non
|
||||
@@ -759,18 +760,19 @@ docker compose --profile dev up -d --build
|
||||
- Al **rientro** il territorio torna in reparto ed esce dal pacchetto
|
||||
- **Sciogliere il gruppo** toglie il ruolo al sorvegliante e **elimina il
|
||||
gruppo**, che non resta nello storico: i territori solo nel pacchetto
|
||||
tornano in reparto, i membri si liberano, l'utenza viene disattivata (anche
|
||||
una sessione aperta si chiude) e il suo nome utente torna libero. I
|
||||
territori già dati restano ai membri come assegnazioni qualsiasi, che del
|
||||
gruppo conservano solo il nome ("via gruppo …"). Per ripartire si crea un
|
||||
gruppo nuovo; se il sorvegliante è lo stesso, si riattiva la sua utenza
|
||||
tornano in reparto, i membri si liberano e l'**utenza del sorvegliante si
|
||||
elimina** (anche una sessione aperta si chiude): torna un proclamatore come
|
||||
gli altri. Le assegnazioni che aveva fatto passano a chi scioglie il
|
||||
gruppo; nel registro delle attività le sue voci restano a nome
|
||||
"Sorvegliante del gruppo …". I territori già dati restano ai membri come
|
||||
assegnazioni qualsiasi, che del gruppo conservano solo il nome ("via
|
||||
gruppo …"). Per ripartire si crea un gruppo nuovo
|
||||
- **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
|
||||
cestino esce dal gruppo
|
||||
- **Chi fa parte di un gruppo è segnalato**: badge "Gruppo …" o
|
||||
"Sorvegliante del gruppo …" nell'elenco dei proclamatori (con il filtro
|
||||
*In un gruppo di servizio*) e in testa alla scheda; nella scelta del
|
||||
@@ -778,7 +780,7 @@ docker compose --profile dev up -d --build
|
||||
resta comunque sceglibile
|
||||
- **Dettagli del gruppo**: la scheda del proclamatore dice se è sorvegliante
|
||||
(gruppo, membri, territori nel pacchetto e da dare, assegnazioni nate dal
|
||||
gruppo, nome utente, stato della password e ultimo accesso) o membro; il
|
||||
gruppo, email per entrare, stato della password e ultimo accesso) o membro; il
|
||||
sorvegliante ritrova gli stessi numeri nel suo
|
||||
*Profilo*
|
||||
|
||||
@@ -892,7 +894,7 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina.
|
||||
|
||||
| Tabella | Descrizione |
|
||||
|-------------------|--------------------------------------------------------------|
|
||||
| `users` | Utenti; `password_change_required` blocca l'accesso finché non si sceglie una password nuova; `email` facoltativa e `username` per il sorvegliante, che entra con il nome utente del gruppo; `proclamatore_id` lega l'utenza del sorvegliante al suo proclamatore, `disattivato_at` la chiude allo scioglimento del gruppo |
|
||||
| `users` | Utenti; `password_change_required` blocca l'accesso finché non si sceglie una password nuova; `proclamatore_id` lega l'utenza del sorvegliante (email fittizia) al suo proclamatore; sciolto il gruppo l'utenza si elimina |
|
||||
| `settings` | Configurazione singleton |
|
||||
| `zone` | Zone territoriali |
|
||||
| `tipologie` | Tipologie di territorio |
|
||||
|
||||
@@ -13,11 +13,8 @@ class LoginController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): RedirectResponse
|
||||
{
|
||||
// Il campo si chiama "email" ma accetta anche il nome utente del
|
||||
// sorvegliante di gruppo, che l'email non ce l'ha: con la chiocciola è
|
||||
// un'email, senza un nome utente (che la chiocciola non la contiene mai).
|
||||
$credentials = $request->validate([
|
||||
'email' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string', 'min:6'],
|
||||
'remember' => ['nullable', 'boolean'],
|
||||
]);
|
||||
@@ -32,11 +29,8 @@ class LoginController extends Controller
|
||||
->withInput($request->only('email', 'remember'));
|
||||
}
|
||||
|
||||
$identificativo = trim($credentials['email']);
|
||||
$campo = str_contains($identificativo, '@') ? 'email' : 'username';
|
||||
|
||||
if (! Auth::attempt([
|
||||
$campo => $campo === 'username' ? Str::lower($identificativo) : $identificativo,
|
||||
'email' => $credentials['email'],
|
||||
'password' => $credentials['password'],
|
||||
], $request->boolean('remember'))) {
|
||||
RateLimiter::hit($throttleKey);
|
||||
@@ -46,16 +40,6 @@ class LoginController extends Controller
|
||||
->withInput($request->only('email', 'remember'));
|
||||
}
|
||||
|
||||
// Utenza disattivata (gruppo di servizio sciolto): la password è giusta
|
||||
// ma non si entra.
|
||||
if (Auth::user()->isDisattivato()) {
|
||||
Auth::logout();
|
||||
|
||||
return back()
|
||||
->withErrors(['email' => 'Questa utenza è stata disattivata.'])
|
||||
->withInput($request->only('email', 'remember'));
|
||||
}
|
||||
|
||||
RateLimiter::clear($throttleKey);
|
||||
$request->session()->regenerate();
|
||||
activity()->causedBy(auth()->user())->log('login');
|
||||
|
||||
@@ -4,23 +4,18 @@ namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Due regole per gli utenti autenticati.
|
||||
*
|
||||
* 1. Un'utenza disattivata (gruppo di servizio sciolto) non può più lavorare:
|
||||
* anche una sessione già aperta viene chiusa alla richiesta successiva.
|
||||
*
|
||||
* 2. Il sorvegliante di gruppo vede solo il proprio gruppo e il proprio
|
||||
* profilo. La Home lo porta a "Il mio gruppo"; ogni altra pagina è 403,
|
||||
* anche scrivendone l'indirizzo a mano. Le azioni Livewire passano: i
|
||||
* componenti che può aprire controllano da sé che territori e assegnazioni
|
||||
* siano del suo gruppo, e gli altri non può caricarli.
|
||||
* Il sorvegliante di gruppo vede solo il proprio gruppo e il proprio
|
||||
* profilo. La Home lo porta a "Il mio gruppo"; ogni altra pagina è 403,
|
||||
* anche scrivendone l'indirizzo a mano. Le azioni Livewire passano: i
|
||||
* componenti che può aprire controllano da sé che territori e assegnazioni
|
||||
* siano del suo gruppo, e gli altri non può caricarli.
|
||||
*
|
||||
* Vale solo per chi ha il ruolo sorvegliante e nessun altro: un utente con
|
||||
* anche un altro ruolo resta regolato dai permessi di quel ruolo.
|
||||
* anche un altro ruolo resta regolato dai permessi di quel ruolo. Sciolto il
|
||||
* gruppo l'utenza si elimina: una sessione ancora aperta non ha più utente.
|
||||
*/
|
||||
class LimitaSorvegliante
|
||||
{
|
||||
@@ -43,14 +38,6 @@ class LimitaSorvegliante
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($user->isDisattivato()) {
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login')->withErrors(['email' => 'Questa utenza è stata disattivata.']);
|
||||
}
|
||||
|
||||
if (! $user->isSorvegliante() || $user->roles->count() > 1) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Gruppi di servizio (solo amministratore): elenco e creazione. Creando un
|
||||
* gruppo si sceglie il sorvegliante, che riceve un'utenza con un nome utente
|
||||
* ricavato dal nome del gruppo (niente email) e una password temporanea,
|
||||
* mostrati qui una volta sola. Membri e pacchetto si gestiscono nella pagina
|
||||
* del gruppo (GruppoShow).
|
||||
* gruppo si sceglie il sorvegliante, che riceve un'utenza con un'email
|
||||
* fittizia ricavata dal suo nome (GruppiServizio::emailFittizia) e una
|
||||
* password temporanea, mostrate qui una volta sola. Membri e pacchetto si
|
||||
* gestiscono nella pagina del gruppo (GruppoShow).
|
||||
*/
|
||||
class GruppiIndex extends Component
|
||||
{
|
||||
@@ -72,11 +72,11 @@ class GruppiIndex extends Component
|
||||
->orderBy('nome')
|
||||
->get();
|
||||
|
||||
// Possono diventare sorveglianti i proclamatori attivi senza gruppo e
|
||||
// senza un'utenza attiva (quella di un gruppo sciolto si riattiva).
|
||||
// Possono diventare sorveglianti i proclamatori attivi senza gruppo (e
|
||||
// quindi senza utenza: sciolto il gruppo, la si elimina).
|
||||
$candidati = Proclamatore::attivi()
|
||||
->whereNull('gruppo_servizio_id')
|
||||
->whereDoesntHave('utente', fn ($q) => $q->whereNull('disattivato_at'))
|
||||
->whereDoesntHave('utente')
|
||||
->get();
|
||||
|
||||
return view('livewire.gruppi.gruppi-index', [
|
||||
|
||||
@@ -124,7 +124,7 @@ class GruppoShow extends Component
|
||||
$nome = $this->gruppo->nome;
|
||||
$this->servizio()->sciogli($this->gruppo);
|
||||
|
||||
session()->flash('success', "Gruppo {$nome} sciolto: i territori non ancora dati sono tornati in reparto e l'utenza del sorvegliante è disattivata.");
|
||||
session()->flash('success', "Gruppo {$nome} sciolto: i territori non ancora dati sono tornati in reparto e l'utenza del sorvegliante è stata eliminata.");
|
||||
return $this->redirect(route('gruppi.index'), navigate: true);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class ProclamatoreIndex extends Component
|
||||
/** L'avviso quando un sorvegliante esce di scena: anche per ProclamatoreEdit. */
|
||||
public static function gruppoSciolto(GruppoServizio $gruppo): string
|
||||
{
|
||||
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.";
|
||||
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 è stata eliminata.";
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
||||
@@ -20,24 +20,24 @@ class Profile extends Component
|
||||
{
|
||||
$user = auth()->user();
|
||||
$this->name = $user->name;
|
||||
$this->email = (string) $user->email;
|
||||
$this->email = $user->email;
|
||||
}
|
||||
|
||||
public function updateProfile()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// Il sorvegliante di gruppo entra con il nome utente: l'email non gli
|
||||
// si chiede, e il nome utente lo decide il nome del gruppo.
|
||||
$senzaEmail = $user->isSorvegliante();
|
||||
// Il sorvegliante di gruppo entra con l'email fittizia creata col
|
||||
// gruppo (GruppiServizio::emailFittizia): dal profilo non la cambia.
|
||||
$emailFissa = $user->isSorvegliante();
|
||||
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
] + ($senzaEmail ? [] : [
|
||||
] + ($emailFissa ? [] : [
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)],
|
||||
]));
|
||||
|
||||
$user->fill(['name' => $validated['name']] + ($senzaEmail ? [] : ['email' => $validated['email']]));
|
||||
$user->fill(['name' => $validated['name']] + ($emailFissa ? [] : ['email' => $validated['email']]));
|
||||
$emailCambiata = $user->isDirty('email');
|
||||
$user->save();
|
||||
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Livewire\Concerns\Avvisa;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class UsersIndex extends Component
|
||||
@@ -115,7 +112,7 @@ class UsersIndex extends Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Le utenze dei sorveglianti di gruppo (attive o disattivate) si gestiscono
|
||||
* Le utenze dei sorveglianti di gruppo si gestiscono
|
||||
* da Gruppi di servizio: cambiarle o cancellarle da qui lascerebbe un
|
||||
* gruppo senza sorvegliante, o un sorvegliante con un ruolo diverso.
|
||||
*/
|
||||
@@ -232,37 +229,7 @@ class UsersIndex extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($user, $currentUser) {
|
||||
$causerName = $user->name;
|
||||
$causerEmail = $user->email;
|
||||
$deletedAt = now()->toDateTimeString();
|
||||
|
||||
Activity::query()
|
||||
->where('causer_type', User::class)
|
||||
->where('causer_id', $user->id)
|
||||
->chunkById(200, function ($activities) use ($causerName, $causerEmail, $deletedAt) {
|
||||
foreach ($activities as $activity) {
|
||||
$properties = $activity->properties?->toArray() ?? [];
|
||||
$properties['causer_name'] = $causerName;
|
||||
$properties['causer_email'] = $causerEmail;
|
||||
$properties['causer_deleted_at'] = $deletedAt;
|
||||
|
||||
$activity->properties = $properties;
|
||||
$activity->save();
|
||||
}
|
||||
});
|
||||
|
||||
Assegnazione::query()
|
||||
->where('created_by', $user->id)
|
||||
->update(['created_by' => $currentUser->id]);
|
||||
|
||||
Assegnazione::query()
|
||||
->where('returned_by', $user->id)
|
||||
->update(['returned_by' => $currentUser->id]);
|
||||
|
||||
$user->syncRoles([]);
|
||||
$user->delete();
|
||||
});
|
||||
$user->eliminaConservandoStorico($currentUser, $user->name, $user->email);
|
||||
|
||||
if ($this->editingUserId === $userId) {
|
||||
$this->cancelEdit();
|
||||
|
||||
@@ -525,18 +525,22 @@ class XmlExchange extends Component
|
||||
$utentiDaCollegare = [];
|
||||
foreach (($xml->users->user ?? []) as $userNode) {
|
||||
$email = trim((string) ($userNode->email ?? ''));
|
||||
$username = trim((string) ($userNode->username ?? ''));
|
||||
$legacyId = (string) ($userNode['legacy_id'] ?? '');
|
||||
|
||||
// Il sorvegliante di gruppo non ha email: si riconosce dal nome utente.
|
||||
if ($email === '' && $username === '') {
|
||||
$this->pushImportIssue('utente', $legacyId, 'Email e nome utente mancanti');
|
||||
// Nei file del 19-20 ottobre 2026 c'erano le utenze disattivate
|
||||
// degli ex sorveglianti: ora, sciolto il gruppo, l'utenza non
|
||||
// esiste più. Le loro assegnazioni passano a chi importa.
|
||||
if (trim((string) ($userNode->disattivato_at ?? '')) !== '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = User::query()->firstOrNew($email !== '' ? ['email' => $email] : ['username' => $username]);
|
||||
$user->name = (string) ($userNode->name ?? ($email !== '' ? $email : $username));
|
||||
$user->username = $username !== '' ? $username : null;
|
||||
if ($email === '') {
|
||||
$this->pushImportIssue('utente', $legacyId, 'Email mancante');
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = User::query()->firstOrNew(['email' => $email]);
|
||||
$user->name = (string) ($userNode->name ?? $email);
|
||||
if (! $user->exists) {
|
||||
// password is NOT NULL; the real hash is written raw below.
|
||||
$user->password = Str::password(24);
|
||||
@@ -597,10 +601,7 @@ class XmlExchange extends Component
|
||||
|
||||
// Utenza di un sorvegliante: si collega al proclamatore dopo che
|
||||
// i proclamatori sono stati importati (vedi i gruppi di servizio).
|
||||
$utentiDaCollegare[$user->id] = [
|
||||
'proclamatore' => (string) ($userNode->legacy_proclamatore_id ?? ''),
|
||||
'disattivato_at' => $this->normalizeDateTimeForDb((string) ($userNode->disattivato_at ?? '')),
|
||||
];
|
||||
$utentiDaCollegare[$user->id] = (string) ($userNode->legacy_proclamatore_id ?? '');
|
||||
|
||||
$stats['utenti_importati']++;
|
||||
if ($legacyId !== '') {
|
||||
@@ -681,10 +682,9 @@ class XmlExchange extends Component
|
||||
DB::table('proclamatori')->where('id', $idProclamatore)->update(['gruppo_servizio_id' => $gruppoMap[$legacyGruppo]]);
|
||||
}
|
||||
}
|
||||
foreach ($utentiDaCollegare as $idUtente => $collegamento) {
|
||||
foreach ($utentiDaCollegare as $idUtente => $legacyProclamatore) {
|
||||
DB::table('users')->where('id', $idUtente)->update([
|
||||
'proclamatore_id' => $proclamatoreMap[$collegamento['proclamatore']] ?? null,
|
||||
'disattivato_at' => $collegamento['disattivato_at'],
|
||||
'proclamatore_id' => $proclamatoreMap[$legacyProclamatore] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -869,15 +869,13 @@ class XmlExchange extends Component
|
||||
'legacy_id' => (string) $u->id,
|
||||
'name' => (string) $u->name,
|
||||
'email' => (string) $u->email,
|
||||
'username' => (string) $u->username,
|
||||
'password' => (string) $u->getAuthPassword(),
|
||||
'email_verified_at' => optional($u->email_verified_at)->toIso8601String() ?? '',
|
||||
// remember_token no: all'import si azzera comunque, e nel file
|
||||
// sarebbe solo una credenziale in più per chi lo tiene.
|
||||
'roles' => $u->roles->pluck('name')->all(),
|
||||
// Sorvegliante di gruppo: il proclamatore collegato e se l'utenza è disattivata.
|
||||
// Sorvegliante di gruppo: il proclamatore collegato.
|
||||
'proclamatore_id' => $u->proclamatore_id ? (string) $u->proclamatore_id : '',
|
||||
'disattivato_at' => optional($u->disattivato_at)->toIso8601String() ?? '',
|
||||
'created_at' => optional($u->created_at)->toIso8601String() ?? '',
|
||||
'updated_at' => optional($u->updated_at)->toIso8601String() ?? '',
|
||||
])->all(),
|
||||
@@ -1285,11 +1283,9 @@ class XmlExchange extends Component
|
||||
}
|
||||
$this->addXmlText($node, 'name', (string) ($user['name'] ?? ''));
|
||||
$this->addXmlText($node, 'email', (string) ($user['email'] ?? ''));
|
||||
$this->addXmlText($node, 'username', (string) ($user['username'] ?? ''));
|
||||
$this->addXmlText($node, 'password', (string) ($user['password'] ?? ''));
|
||||
$node->addChild('email_verified_at', (string) ($user['email_verified_at'] ?? ''));
|
||||
$node->addChild('legacy_proclamatore_id', (string) ($user['proclamatore_id'] ?? ''));
|
||||
$node->addChild('disattivato_at', (string) ($user['disattivato_at'] ?? ''));
|
||||
$node->addChild('created_at', (string) ($user['created_at'] ?? ''));
|
||||
$node->addChild('updated_at', (string) ($user['updated_at'] ?? ''));
|
||||
$rolesNode = $node->addChild('roles');
|
||||
|
||||
@@ -175,22 +175,9 @@ class Proclamatore extends Model
|
||||
$this->cognome = 'Proclamatore #' . $this->id;
|
||||
$this->attivo = false;
|
||||
$this->gruppo_servizio_id = null;
|
||||
// Se era sorvegliante, il salvataggio (non più attivo) scioglie il
|
||||
// gruppo ed elimina la sua utenza: booted().
|
||||
$this->save();
|
||||
$this->delete(); // soft delete
|
||||
|
||||
// L'utenza di un ex sorvegliante (già disattivata: il sorvegliante di
|
||||
// un gruppo attivo non si anonimizza) porta nome ed email in chiaro.
|
||||
$utente = $this->utente()->first();
|
||||
if ($utente) {
|
||||
$utente->syncRoles([]);
|
||||
$utente->forceFill([
|
||||
'name' => 'Anonimo',
|
||||
'email' => null,
|
||||
'username' => "anonimo.{$utente->id}",
|
||||
'password' => \Illuminate\Support\Str::random(40),
|
||||
'remember_token' => null,
|
||||
'disattivato_at' => $utente->disattivato_at ?? now(),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* MustVerifyEmail: l'account funziona solo con l'indirizzo confermato
|
||||
@@ -22,11 +24,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'username',
|
||||
'password',
|
||||
'password_change_required',
|
||||
'proclamatore_id',
|
||||
'disattivato_at',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -40,7 +40,6 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'password_change_required' => 'boolean',
|
||||
'disattivato_at' => 'datetime',
|
||||
'proclamatore_id' => 'integer',
|
||||
];
|
||||
}
|
||||
@@ -60,7 +59,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $this->hasRole('sorvegliante');
|
||||
}
|
||||
|
||||
/** Il gruppo di cui è sorvegliante, se c'è (sciolto, il gruppo non esiste più). */
|
||||
/** Il gruppo di cui è sorvegliante (sciolto il gruppo, l'utenza non esiste più). */
|
||||
public function gruppoSorvegliato(): ?GruppoServizio
|
||||
{
|
||||
return $this->proclamatore_id
|
||||
@@ -69,18 +68,37 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
}
|
||||
|
||||
/**
|
||||
* Con cosa entra: l'email, o il nome utente per il sorvegliante di
|
||||
* gruppo, che l'email non ce l'ha. Da mostrare dove prima c'era l'email.
|
||||
* Elimina l'utenza senza perdere lo storico: le assegnazioni che ha fatto
|
||||
* o fatto rientrare passano a $subentra (la colonna non può restare
|
||||
* vuota), e nel registro delle attività le sue voci si tengono, con
|
||||
* scritto chi era ($nome, $email) al posto del collegamento all'utenza.
|
||||
*/
|
||||
public function getIdentificativoAttribute(): string
|
||||
public function eliminaConservandoStorico(self $subentra, string $nome, ?string $email): void
|
||||
{
|
||||
return (string) ($this->email ?? $this->username ?? $this->name);
|
||||
}
|
||||
DB::transaction(function () use ($subentra, $nome, $email) {
|
||||
$eliminatoIl = now()->toDateTimeString();
|
||||
|
||||
/** Utenza disattivata (gruppo sciolto): non può più entrare. */
|
||||
public function isDisattivato(): bool
|
||||
{
|
||||
return $this->disattivato_at !== null;
|
||||
Activity::query()
|
||||
->where('causer_type', self::class)
|
||||
->where('causer_id', $this->id)
|
||||
->chunkById(200, function ($attivita) use ($nome, $email, $eliminatoIl) {
|
||||
foreach ($attivita as $voce) {
|
||||
$proprieta = $voce->properties?->toArray() ?? [];
|
||||
$proprieta['causer_name'] = $nome;
|
||||
$proprieta['causer_email'] = $email;
|
||||
$proprieta['causer_deleted_at'] = $eliminatoIl;
|
||||
|
||||
$voce->properties = $proprieta;
|
||||
$voce->save();
|
||||
}
|
||||
});
|
||||
|
||||
Assegnazione::query()->where('created_by', $this->id)->update(['created_by' => $subentra->id]);
|
||||
Assegnazione::query()->where('returned_by', $this->id)->update(['returned_by' => $subentra->id]);
|
||||
|
||||
$this->syncRoles([]);
|
||||
$this->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,12 +21,15 @@ use InvalidArgumentException;
|
||||
*/
|
||||
class GruppiServizio
|
||||
{
|
||||
/** Il dominio delle email fittizie dei sorveglianti (iniziale+cognome@…). */
|
||||
public const DOMINIO_EMAIL = 'termanager.it';
|
||||
|
||||
/**
|
||||
* Crea il gruppo e l'utenza del sorvegliante: nome utente ricavato dal
|
||||
* nome del gruppo (niente email), ruolo "sorvegliante", password
|
||||
* temporanea da cambiare al primo accesso. Restituisce il gruppo e la
|
||||
* password temporanea, da mostrare una volta sola all'amministratore
|
||||
* insieme al nome utente ($gruppo->sorvegliante->utente->username).
|
||||
* Crea il gruppo e l'utenza del sorvegliante: email fittizia ricavata dal
|
||||
* suo nome (emailFittizia), ruolo "sorvegliante", password temporanea da
|
||||
* cambiare al primo accesso, nessuna conferma dell'email. Restituisce il
|
||||
* gruppo e la password temporanea, da mostrare una volta sola
|
||||
* all'amministratore insieme all'email.
|
||||
*
|
||||
* @param int[] $membri id dei proclamatori, oltre al sorvegliante
|
||||
* @return array{0: GruppoServizio, 1: string}
|
||||
@@ -46,29 +49,25 @@ class GruppiServizio
|
||||
if ($sorvegliante->gruppo_servizio_id) {
|
||||
throw new InvalidArgumentException("{$sorvegliante->nome_completo} fa già parte di un altro gruppo.");
|
||||
}
|
||||
// Chi è stato sorvegliante di un gruppo sciolto ha già un'utenza,
|
||||
// disattivata: si riattiva quella (un proclamatore, un'utenza).
|
||||
$esistente = $sorvegliante->utente()->first();
|
||||
if ($esistente && ! $esistente->isDisattivato()) {
|
||||
if ($sorvegliante->utente()->exists()) {
|
||||
throw new InvalidArgumentException("{$sorvegliante->nome_completo} ha già un'utenza.");
|
||||
}
|
||||
|
||||
$password = Str::password(12, symbols: false);
|
||||
|
||||
$gruppo = DB::transaction(function () use ($nome, $sorvegliante, $membri, $password, $esistente) {
|
||||
$gruppo = DB::transaction(function () use ($nome, $sorvegliante, $membri, $password) {
|
||||
$gruppo = GruppoServizio::create(['nome' => $nome, 'sorvegliante_id' => $sorvegliante->id]);
|
||||
$sorvegliante->update(['gruppo_servizio_id' => $gruppo->id]);
|
||||
$this->aggiungiMembri($gruppo, $membri);
|
||||
|
||||
$utente = $esistente ?? new User(['proclamatore_id' => $sorvegliante->id]);
|
||||
$utente->forceFill([
|
||||
$utente = User::create([
|
||||
'name' => $sorvegliante->nome_completo,
|
||||
'username' => self::nomeUtente($nome),
|
||||
'email' => self::emailFittizia($sorvegliante),
|
||||
'password' => $password,
|
||||
'password_change_required' => true,
|
||||
'disattivato_at' => null,
|
||||
])->save();
|
||||
$utente->syncRoles(['sorvegliante']);
|
||||
'proclamatore_id' => $sorvegliante->id,
|
||||
]);
|
||||
$utente->assignRole('sorvegliante');
|
||||
|
||||
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
||||
->withProperties(['sorvegliante_id' => $sorvegliante->id, 'utente_id' => $utente->id])
|
||||
@@ -81,20 +80,27 @@ class GruppiServizio
|
||||
}
|
||||
|
||||
/**
|
||||
* Il nome utente dal nome del gruppo: minuscole, senza accenti, parole
|
||||
* unite da un punto ("Gruppo Noce" → "gruppo.noce"); se è già preso, un
|
||||
* numero in coda ("zisa2"). Mai una chiocciola: al login distingue il
|
||||
* nome utente dall'email.
|
||||
* L'email fittizia del sorvegliante, con cui entra: iniziale del nome e
|
||||
* cognome, minuscoli, senza accenti, spazi né apostrofi, sul dominio
|
||||
* dell'app (Francesco Fontana → ffontana@termanager.it, Francesco
|
||||
* D'Angelo → fdangelo@…). Se è già presa, un numero in coda
|
||||
* (ffontana2@…). Non riceve posta: il sorvegliante non conferma l'email.
|
||||
*/
|
||||
public static function nomeUtente(string $nomeGruppo): string
|
||||
public static function emailFittizia(Proclamatore $proclamatore): string
|
||||
{
|
||||
$base = Str::limit(Str::slug($nomeGruppo, '.') ?: 'gruppo', 50, '');
|
||||
$nome = $base;
|
||||
for ($n = 2; User::where('username', $nome)->exists(); $n++) {
|
||||
$nome = $base . $n;
|
||||
$pulisci = fn (string $testo) => preg_replace('/[^a-z0-9]/', '', Str::lower(Str::ascii($testo)));
|
||||
|
||||
$base = Str::limit(mb_substr($pulisci((string) $proclamatore->nome), 0, 1) . $pulisci((string) $proclamatore->cognome), 60, '');
|
||||
if ($base === '') {
|
||||
$base = "sorvegliante{$proclamatore->id}";
|
||||
}
|
||||
|
||||
return $nome;
|
||||
$email = "{$base}@" . self::DOMINIO_EMAIL;
|
||||
for ($n = 2; User::where('email', $email)->exists(); $n++) {
|
||||
$email = "{$base}{$n}@" . self::DOMINIO_EMAIL;
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
@@ -250,8 +256,10 @@ class GruppiServizio
|
||||
* I territori del pacchetto non ancora dati tornano in reparto; quelli
|
||||
* già dati restano ai membri come assegnazioni qualsiasi, che conservano
|
||||
* solo il nome del gruppo (gruppo_nome). I membri restano senza gruppo.
|
||||
* L'utenza del sorvegliante si disattiva e libera il nome utente; resta
|
||||
* perché è l'autrice delle assegnazioni che ha fatto.
|
||||
* L'utenza del sorvegliante si elimina: torna un proclamatore come gli
|
||||
* altri. Le assegnazioni che aveva fatto o fatto rientrare passano a chi
|
||||
* scioglie il gruppo; nel registro le sue voci restano, a nome
|
||||
* "Sorvegliante del gruppo …", senza dati personali.
|
||||
*/
|
||||
public function sciogli(GruppoServizio $gruppo, ?string $motivo = null): void
|
||||
{
|
||||
@@ -269,30 +277,39 @@ class GruppiServizio
|
||||
Assegnazione::where('gruppo_servizio_id', $gruppo->id)->update(['gruppo_servizio_id' => null]);
|
||||
|
||||
$utente = User::where('proclamatore_id', $gruppo->sorvegliante_id)->first();
|
||||
if ($utente) {
|
||||
$utente->removeRole('sorvegliante');
|
||||
// Il nome utente del gruppo si libera; al suo posto uno che
|
||||
// identifica l'utenza (backup XML) e con cui non si entra.
|
||||
$utente->forceFill([
|
||||
'disattivato_at' => now(),
|
||||
'remember_token' => null,
|
||||
'username' => "ex.sorvegliante.{$utente->id}",
|
||||
])->save();
|
||||
}
|
||||
|
||||
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
||||
->withProperties(array_filter([
|
||||
'gruppo' => $gruppo->nome,
|
||||
'territori_liberati' => $liberati->all(),
|
||||
'utente_disattivato' => $utente?->id,
|
||||
'utente_eliminato' => $utente?->id,
|
||||
'motivo' => $motivo,
|
||||
], fn ($v) => $v !== null))
|
||||
->log('gruppo_sciolto');
|
||||
|
||||
if ($utente) {
|
||||
$utente->eliminaConservandoStorico($this->subentrante($utente), "Sorvegliante del gruppo {$gruppo->nome}", null);
|
||||
}
|
||||
|
||||
$gruppo->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chi prende le assegnazioni fatte dal sorvegliante quando la sua utenza
|
||||
* si elimina: chi sta sciogliendo il gruppo, altrimenti il primo
|
||||
* amministratore.
|
||||
*/
|
||||
private function subentrante(User $sorvegliante): User
|
||||
{
|
||||
$chi = auth()->user();
|
||||
if ($chi instanceof User && $chi->id !== $sorvegliante->id) {
|
||||
return $chi;
|
||||
}
|
||||
|
||||
return User::role('amministratore')->orderBy('id')->firstOrFail();
|
||||
}
|
||||
|
||||
private function attivo(GruppoServizio $gruppo): void
|
||||
{
|
||||
if (! $gruppo->exists || ! GruppoServizio::whereKey($gruppo->id)->exists()) {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Proclamatore;
|
||||
use App\Services\GruppiServizio;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Gruppi di servizio, 20 ottobre 2026 (decisioni dell'utente, che
|
||||
* sostituiscono il nome utente introdotto poche ore prima):
|
||||
*
|
||||
* - il sorvegliante entra con un'email fittizia, iniziale del nome e cognome
|
||||
* (ffontana@termanager.it), come gli altri utenti: via users.username, e
|
||||
* users.email torna obbligatoria;
|
||||
* - sciolto il gruppo l'utenza del sorvegliante si elimina, non si disattiva:
|
||||
* via users.disattivato_at e le utenze già disattivate. Le loro
|
||||
* assegnazioni passano al primo amministratore; nel registro le loro voci
|
||||
* restano, a nome "Ex sorvegliante di gruppo".
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$amministratore = DB::table('model_has_roles')
|
||||
->join('roles', 'roles.id', '=', 'model_has_roles.role_id')
|
||||
->where('roles.name', 'amministratore')
|
||||
->where('model_has_roles.model_type', 'App\\Models\\User')
|
||||
->min('model_has_roles.model_id');
|
||||
|
||||
if (Schema::hasColumn('users', 'disattivato_at')) {
|
||||
foreach (DB::table('users')->whereNotNull('disattivato_at')->get() as $utente) {
|
||||
if (! $amministratore) {
|
||||
continue; // nessuno a cui passare le assegnazioni: resta
|
||||
}
|
||||
|
||||
DB::table('assegnazioni')->where('created_by', $utente->id)->update(['created_by' => $amministratore]);
|
||||
DB::table('assegnazioni')->where('returned_by', $utente->id)->update(['returned_by' => $amministratore]);
|
||||
|
||||
foreach (DB::table('activity_log')->where('causer_type', 'App\\Models\\User')->where('causer_id', $utente->id)->get() as $voce) {
|
||||
$proprieta = json_decode((string) $voce->properties, true) ?: [];
|
||||
$proprieta['causer_name'] = 'Ex sorvegliante di gruppo';
|
||||
$proprieta['causer_email'] = null;
|
||||
$proprieta['causer_deleted_at'] = now()->toDateTimeString();
|
||||
DB::table('activity_log')->where('id', $voce->id)->update(['properties' => json_encode($proprieta)]);
|
||||
}
|
||||
|
||||
DB::table('model_has_roles')->where('model_type', 'App\\Models\\User')->where('model_id', $utente->id)->delete();
|
||||
DB::table('model_has_permissions')->where('model_type', 'App\\Models\\User')->where('model_id', $utente->id)->delete();
|
||||
DB::table('users')->where('id', $utente->id)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Chi è rimasto senza email (sorveglianti col nome utente): la fittizia.
|
||||
foreach (DB::table('users')->whereNull('email')->get() as $utente) {
|
||||
$proclamatore = $utente->proclamatore_id ? Proclamatore::withTrashed()->find($utente->proclamatore_id) : null;
|
||||
$email = $proclamatore
|
||||
? GruppiServizio::emailFittizia($proclamatore)
|
||||
: "utente{$utente->id}@" . GruppiServizio::DOMINIO_EMAIL;
|
||||
DB::table('users')->where('id', $utente->id)->update(['email' => $email]);
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('users', 'username')) {
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropUnique(['username']);
|
||||
});
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('username');
|
||||
});
|
||||
}
|
||||
if (Schema::hasColumn('users', 'disattivato_at')) {
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('disattivato_at');
|
||||
});
|
||||
}
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('email')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('email')->nullable()->change();
|
||||
});
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('username', 60)->nullable()->unique()->after('email');
|
||||
$table->timestamp('disattivato_at')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -5,7 +5,7 @@
|
||||
{{ $forced ? 'Imposta una nuova password' : 'Cambia password' }}
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Account: <span class="font-medium text-gray-700 break-all">{{ $user->identificativo }}</span>
|
||||
Account: <span class="font-medium text-gray-700 break-all">{{ $user->email }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
{{-- L'email in un campo nascosto: senza, i gestori di password non
|
||||
sanno a quale account associare la credenziale che salvano. --}}
|
||||
<input type="hidden" name="email" autocomplete="username" value="{{ $user->identificativo }}">
|
||||
<input type="hidden" name="email" autocomplete="username" value="{{ $user->email }}">
|
||||
|
||||
@unless($forced)
|
||||
{{-- Dopo un ripristino non si chiede: la password attuale e'
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" style="width:20px;height:20px;opacity:.75"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
<span class="flex flex-col leading-tight text-left">
|
||||
<span class="text-sm font-medium">{{ auth()->user()->name }}</span>
|
||||
<span class="text-xs" style="opacity:.7">{{ auth()->user()->identificativo }} · {{ ucfirst(auth()->user()->roles->first()?->name ?? 'utente') }}</span>
|
||||
<span class="text-xs" style="opacity:.7">{{ auth()->user()->email }} · {{ ucfirst(auth()->user()->roles->first()?->name ?? 'utente') }}</span>
|
||||
</span>
|
||||
</x-btn>
|
||||
</div>
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
<form method="POST" action="{{ route('login.store') }}" class="space-y-5">
|
||||
@csrf
|
||||
<div>
|
||||
{{-- Email, o nome utente per il sorvegliante di un gruppo di servizio --}}
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email o nome utente</label>
|
||||
<input name="email" value="{{ old('email') }}" type="text" id="email" autocomplete="username"
|
||||
autocapitalize="none" autocorrect="off" spellcheck="false"
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input name="email" value="{{ old('email') }}" type="email" id="email" autocomplete="email"
|
||||
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm px-4 py-2.5">
|
||||
@error('email') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<p class="mt-1">Consegna queste credenziali a {{ $creato->sorvegliante?->nome_completo }}: la password si vede <strong>solo ora</strong> e al primo accesso va cambiata.</p>
|
||||
<dl class="mt-3 grid grid-cols-[auto_1fr] gap-x-3 gap-y-1">
|
||||
<dt class="text-green-700">Indirizzo</dt><dd><code>{{ route('login') }}</code></dd>
|
||||
<dt class="text-green-700">Nome utente</dt><dd><code class="select-all">{{ $creato->sorvegliante?->utente?->username }}</code></dd>
|
||||
<dt class="text-green-700">Email</dt><dd><code class="select-all">{{ $creato->sorvegliante?->utente?->email }}</code></dd>
|
||||
<dt class="text-green-700">Password</dt><dd><code class="select-all">{{ $passwordTemporanea }}</code></dd>
|
||||
</dl>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
@@ -29,13 +29,12 @@
|
||||
<label for="nome-gruppo" class="block text-sm font-medium text-gray-700 mb-1">Nome del gruppo *</label>
|
||||
<input wire:model="nome" id="nome-gruppo" type="text" placeholder="es. Zisa"
|
||||
class="block w-full rounded-lg border-gray-300 text-base sm:text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<p class="text-xs text-gray-500 mt-1">Dal nome nasce il nome utente del sorvegliante (es. «Gruppo Zisa» → <code>gruppo.zisa</code>).</p>
|
||||
@error('nome') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label for="sorvegliante" class="block text-sm font-medium text-gray-700 mb-1">Sorvegliante *</label>
|
||||
<x-scelta id="sorvegliante" wire:model="sorveglianteId" :opzioni="$opzioniSorveglianti" segnaposto="Cognome o nome" :vuoto="null" />
|
||||
<p class="text-xs text-gray-500 mt-1">Un proclamatore attivo che non fa già parte di un gruppo. Diventa anche membro del gruppo.</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Un proclamatore attivo che non fa già parte di un gruppo. Diventa anche membro del gruppo, e riceve un'utenza con un'email fittizia: iniziale del nome e cognome (es. Francesco Fontana → <code>ffontana@{{ \App\Services\GruppiServizio::DOMINIO_EMAIL }}</code>).</p>
|
||||
@error('sorveglianteId') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<x-btn variante="primary" type="submit">Crea il gruppo</x-btn>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
Sorvegliante: <strong>{{ $gruppo->sorvegliante?->nome_completo ?? '—' }}</strong>
|
||||
@if($gruppo->sorvegliante?->utente)
|
||||
· nome utente <code class="select-all">{{ $gruppo->sorvegliante->utente->identificativo }}</code>
|
||||
· accede con <code class="select-all">{{ $gruppo->sorvegliante->utente->email }}</code>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@
|
||||
<x-btn variante="secondary" wire:click="nuovaPassword"
|
||||
wire:confirm="Generare una nuova password temporanea per {{ $gruppo->sorvegliante?->nome_completo }}? Quella attuale smette di funzionare.">Nuova password temporanea</x-btn>
|
||||
<x-btn variante="danger" wire:click="sciogli"
|
||||
wire:confirm="Togliere il ruolo di sorvegliante a {{ $gruppo->sorvegliante?->nome_completo }}? Il gruppo si scioglie e viene eliminato: i territori non ancora dati tornano in reparto, i membri restano senza gruppo e l'utenza del sorvegliante viene disattivata. Le assegnazioni già fatte restano ai membri.">Togli il ruolo e sciogli il gruppo</x-btn>
|
||||
wire:confirm="Togliere il ruolo di sorvegliante a {{ $gruppo->sorvegliante?->nome_completo }}? Il gruppo si scioglie e viene eliminato: i territori non ancora dati tornano in reparto, i membri restano senza gruppo e l'utenza del sorvegliante viene eliminata. Le assegnazioni già fatte restano ai membri.">Togli il ruolo e sciogli il gruppo</x-btn>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<li><strong>Proclamatori</strong>: solo nome e cognome, cifrati nel database ({{ $cifratura }}), se sono attivi e l'eventuale gruppo di servizio di cui fanno parte. Nessun numero di telefono né indirizzo email.</li>
|
||||
<li><strong>Territori e piantine</strong>: numero, zona, tipologia, confini, mappe e PDF — nessun dato personale.</li>
|
||||
<li><strong>Assegnazioni</strong>: collegamento tra proclamatore e territorio, con date di assegnazione e rientro, conteggio nelle campagne, codice del link alla piantina, stato del suo invio (inviato o no, e con quale canale) e, se l'ha data il sorvegliante di un gruppo di servizio, il nome del gruppo.</li>
|
||||
<li><strong>Utenti dell'applicazione</strong>: nome, email (che va confermata), password conservata solo come impronta non reversibile, ruolo. Il sorvegliante di un gruppo di servizio non ha email: entra con un nome utente ricavato dal nome del gruppo. La sua utenza è collegata al suo proclamatore e viene disattivata quando il gruppo si scioglie; il gruppo, sciolto, viene eliminato.</li>
|
||||
<li><strong>Utenti dell'applicazione</strong>: nome, email (che va confermata), password conservata solo come impronta non reversibile, ruolo. Il sorvegliante di un gruppo di servizio entra con un'email fittizia creata dall'applicazione (iniziale del nome e cognome), che non riceve posta e non va confermata. La sua utenza è collegata al suo proclamatore e viene eliminata quando il gruppo si scioglie; anche il gruppo, sciolto, viene eliminato.</li>
|
||||
<li><strong>Registro delle attività</strong>: chi ha fatto cosa e quando. I proclamatori vi compaiono solo con un identificativo numerico, mai con il nome.</li>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -58,7 +58,7 @@
|
||||
<li><strong>{{ ucfirst($ruolo['nome']) }}</strong>: {{ $ruolo['permessi'] ? implode('; ', $ruolo['permessi']) : 'nessun permesso' }}.</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<p class="mt-2">Il proclamatore non ha un account: riceve solo il link alla piantina del territorio che gli è assegnato. Fa eccezione il sorvegliante di un gruppo di servizio, che ha un'utenza (nome utente e password) con cui vede solo il proprio gruppo: i territori del suo pacchetto e i nomi dei membri.</p>
|
||||
<p class="mt-2">Il proclamatore non ha un account: riceve solo il link alla piantina del territorio che gli è assegnato. Fa eccezione il sorvegliante di un gruppo di servizio, che ha un'utenza (email fittizia e password) con cui vede solo il proprio gruppo: i territori del suo pacchetto e i nomi dei membri.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -100,7 +100,7 @@
|
||||
<section>
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-2">Conservazione dei dati</h2>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li>I dati di un proclamatore possono essere anonimizzati su richiesta: nome e cognome vengono cancellati in modo irreversibile, insieme al nome dell'utenza se è stato sorvegliante di un gruppo.</li>
|
||||
<li>I dati di un proclamatore possono essere anonimizzati su richiesta: nome e cognome vengono cancellati in modo irreversibile; se era sorvegliante di un gruppo, il gruppo si scioglie e la sua utenza viene eliminata.</li>
|
||||
<li>Territori e proclamatori eliminati restano nel cestino finché non vengono eliminati definitivamente.</li>
|
||||
<li>Il registro delle attività è conservato per <strong>{{ $auditRetention }} giorni</strong> (modificabile nelle impostazioni), poi cancellato automaticamente.</li>
|
||||
<li>Lo storico delle assegnazioni è conservato per la durata necessaria alla gestione territoriale.</li>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
@if($gruppoSorvegliato ?? null)
|
||||
<p class="-mt-2 text-xs text-amber-800">
|
||||
È il sorvegliante del gruppo <strong>{{ $gruppoSorvegliato->nome }}</strong>: se non è più attivo il gruppo si scioglie,
|
||||
i territori non ancora dati tornano in reparto e la sua utenza viene disattivata.
|
||||
i territori non ancora dati tornano in reparto e la sua utenza viene eliminata.
|
||||
</p>
|
||||
@endif
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
{{-- Sorvegliante: disattivarlo scioglie il gruppo, si chiede conferma --}}
|
||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
||||
wire:click="toggleActive({{ $proclamatore->id }})"
|
||||
wire:confirm="{{ $proclamatore->nome_completo }} è il sorvegliante del gruppo {{ $gruppiSorvegliati[$proclamatore->id] }}. Se non è più attivo il gruppo si scioglie: i territori non ancora dati tornano in reparto, i membri restano senza gruppo e la sua utenza viene disattivata. Procedere?"
|
||||
wire:confirm="{{ $proclamatore->nome_completo }} è il sorvegliante del gruppo {{ $gruppiSorvegliati[$proclamatore->id] }}. Se non è più attivo il gruppo si scioglie: i territori non ancora dati tornano in reparto, i membri restano senza gruppo e la sua utenza viene eliminata. Procedere?"
|
||||
title="Disattiva" aria-label="Disattiva il proclamatore">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/></svg>
|
||||
</x-btn>
|
||||
@@ -101,7 +101,7 @@
|
||||
@endif
|
||||
<x-btn variante="danger" tono="tenue" dimensione="sm" icona
|
||||
wire:click="deleteProclamatore({{ $proclamatore->id }})"
|
||||
wire:confirm="{{ isset($gruppiSorvegliati[$proclamatore->id]) ? 'Spostare nel cestino ' . $proclamatore->nome_completo . '? È il sorvegliante del gruppo ' . $gruppiSorvegliati[$proclamatore->id] . ': il gruppo si scioglie, i territori non ancora dati tornano in reparto e la sua utenza viene disattivata.' : 'Spostare il proclamatore nel cestino?' }}"
|
||||
wire:confirm="{{ isset($gruppiSorvegliati[$proclamatore->id]) ? 'Spostare nel cestino ' . $proclamatore->nome_completo . '? È il sorvegliante del gruppo ' . $gruppiSorvegliati[$proclamatore->id] . ': il gruppo si scioglie, i territori non ancora dati tornano in reparto e la sua utenza viene eliminata.' : 'Spostare il proclamatore nel cestino?' }}"
|
||||
title="Elimina" aria-label="Sposta nel cestino il proclamatore">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
</x-btn>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
@if($utenza)
|
||||
<p class="mt-4 border-t border-gray-100 pt-3 text-sm text-gray-600">
|
||||
Utenza
|
||||
@can('settings.manage') <code>{{ $utenza->identificativo }}</code> @endcan
|
||||
@can('settings.manage') <code>{{ $utenza->email }}</code> @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' }}
|
||||
</p>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-xl font-bold text-gray-900 truncate">{{ $user->name }}</h2>
|
||||
<p class="text-sm text-gray-500 truncate mt-0.5">
|
||||
<span class="break-all">{{ $user->identificativo }}</span>
|
||||
<span class="break-all">{{ $user->email }}</span>
|
||||
<span class="ml-1 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-50 text-indigo-700 align-middle">
|
||||
{{ ucfirst($user->roles->first()?->name ?? 'utente') }}
|
||||
</span>
|
||||
@@ -74,11 +74,11 @@
|
||||
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
@if($user->isSorvegliante())
|
||||
{{-- Il sorvegliante entra con il nome utente del gruppo, senza email --}}
|
||||
{{-- Il sorvegliante entra con l'email fittizia creata col gruppo --}}
|
||||
<div>
|
||||
<p class="block text-sm font-medium text-gray-700">Nome utente</p>
|
||||
<p class="mt-1 text-sm"><code class="select-all">{{ $user->username }}</code></p>
|
||||
<p class="mt-1 text-xs text-gray-500">Viene dal nome del gruppo: lo usi per entrare, insieme alla password.</p>
|
||||
<p class="block text-sm font-medium text-gray-700">Email per entrare</p>
|
||||
<p class="mt-1 text-sm"><code class="select-all">{{ $user->email }}</code></p>
|
||||
<p class="mt-1 text-xs text-gray-500">L'ha creata l'applicazione insieme al gruppo: la usi per entrare, insieme alla password. Non riceve posta.</p>
|
||||
</div>
|
||||
@else
|
||||
<div>
|
||||
|
||||
@@ -111,14 +111,8 @@
|
||||
@else
|
||||
<td data-label="Nome" class="px-4 py-2 text-gray-900">{{ $user->name }}</td>
|
||||
<td data-label="Email" class="px-4 py-2 text-gray-700">
|
||||
@if(! $user->email)
|
||||
{{-- Sorvegliante di gruppo: entra con il nome utente, senza email --}}
|
||||
@if($user->username)<span class="text-gray-500">nome utente</span> <code>{{ $user->username }}</code>@else<span class="text-gray-400">—</span>@endif
|
||||
@else
|
||||
{{ $user->email }}
|
||||
@endif
|
||||
@if(! $user->email)
|
||||
@elseif($user->hasVerifiedEmail())
|
||||
@if($user->hasVerifiedEmail())
|
||||
<span class="ml-1 inline-flex items-center rounded-full bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700">confermata</span>
|
||||
@else
|
||||
<span class="ml-1 inline-flex items-center rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-800"
|
||||
@@ -138,7 +132,7 @@
|
||||
@if($user->proclamatore_id)
|
||||
{{-- Utenza di un sorvegliante di gruppo: si gestisce dal gruppo. --}}
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ $user->disattivato_at ? 'Disattivata (gruppo sciolto)' : 'Sorvegliante: si gestisce da Gruppi di servizio' }}
|
||||
Sorvegliante: si gestisce da Gruppi di servizio
|
||||
</span>
|
||||
@else
|
||||
<div class="flex gap-2">
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user