Files
termanager2/app/Models/Proclamatore.php
Francesco Picone ec2e7bd7e9 ++ 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>
2026-10-20 12:09:15 +02:00

184 lines
5.9 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Proclamatore extends Model
{
use SoftDeletes, LogsActivity;
protected $table = 'proclamatori';
// Niente email né telefono (tolti il 19 ottobre 2026): il link della
// piantina lo manda chi assegna, copiandolo o con "Condividi", e il
// destinatario lo sceglie dai propri contatti.
protected $fillable = ['nome', 'cognome', 'attivo', 'gruppo_servizio_id'];
/** Il gruppo di servizio di cui è membro (al massimo uno). */
public function gruppo()
{
return $this->belongsTo(GruppoServizio::class, 'gruppo_servizio_id');
}
/** L'utenza, se è sorvegliante di un gruppo. */
public function utente()
{
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::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 [
'nome' => 'encrypted',
'cognome' => 'encrypted',
'attivo' => 'boolean',
'gruppo_servizio_id' => 'integer',
];
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['attivo']) // Do NOT log nome/cognome in audit (encrypted, GDPR)
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
/**
* Full name (decrypted, only visible in PHP/UI).
*/
public function getNomeCompletoAttribute(): string
{
return trim($this->cognome . ' ' . $this->nome);
}
/**
* L'opzione per il campo di scelta con ricerca (<x-scelta>): si cerca per
* cognome e nome. Nome e cognome sono cifrati nel database: la ricerca
* avviene sui valori già decifrati, nella pagina, mai in SQL.
*/
public function opzioneScelta(?string $motivo = null): array
{
// Il gruppo di servizio come nota, solo se già caricato (opzioniScelta).
$gruppo = $this->relationLoaded('gruppo') ? $this->gruppo_etichetta : null;
return array_filter([
'valore' => (string) $this->id,
'etichetta' => $this->nome_completo,
'dettaglio' => $gruppo !== null ? lcfirst($gruppo) : null,
'chiavi' => [(string) $this->cognome, (string) $this->nome],
'motivo' => $motivo,
], fn ($v) => $v !== null);
}
/**
* Le opzioni di un elenco di proclamatori, in ordine di cognome e nome.
* L'ordine si fa qui: in SQL ordinerebbe i testi cifrati. Con $conGruppo
* chi fa parte di un gruppo di servizio lo dice accanto al nome (non serve
* dove sono tutti dello stesso gruppo, come in "Il mio gruppo").
*/
public static function opzioniScelta(iterable $proclamatori, bool $conGruppo = true): array
{
if ($conGruppo && $proclamatori instanceof \Illuminate\Database\Eloquent\Collection) {
$proclamatori->loadMissing('gruppo');
}
return collect($proclamatori)
->sortBy(fn (self $p) => mb_strtolower($p->cognome . ' ' . $p->nome))
->map(fn (self $p) => $p->opzioneScelta($p->attivo ? null : 'non attivo'))
->values()
->all();
}
/**
* "Sorvegliante del gruppo Zisa" o "Gruppo Zisa"; null fuori dai gruppi.
* Senza gruppo_servizio_id non fa query.
*/
public function getGruppoEtichettaAttribute(): ?string
{
$gruppo = $this->gruppo;
if (! $gruppo) {
return null;
}
return $gruppo->sorvegliante_id === $this->id
? "Sorvegliante del gruppo {$gruppo->nome}"
: "Gruppo {$gruppo->nome}";
}
public function assegnazioni()
{
return $this->hasMany(Assegnazione::class, 'proclamatore_id');
}
public function assegnazioniAperte()
{
return $this->hasMany(Assegnazione::class, 'proclamatore_id')
->whereNull('returned_at');
}
public function scopeAttivi($query)
{
return $query->where('attivo', true);
}
/**
* Anonymize this proclamatore (GDPR right to be forgotten).
*/
public function anonimizza(): void
{
$this->nome = 'Anonimo';
$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
}
}