++ gruppi di servizio: sorvegliante con utenza limitata, pacchetto di territori, assegnazione ai membri
Il gruppo ha un sorvegliante (proclamatore con utenza: email e password temporanea, senza conferma dell'email) e dei membri; l'amministratore mette in blocco dei territori nel pacchetto del gruppo. Il pacchetto non e' un'assegnazione e non tocca le statistiche; quando il sorvegliante da' un territorio a un membro nasce un'assegnazione normale con gruppo_servizio_id. Al rientro il territorio torna in reparto. Sciogliere il gruppo libera pacchetto e membri e disattiva l'utenza. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,8 +25,15 @@ class Assegnazione extends Model
|
||||
'note',
|
||||
'created_by',
|
||||
'returned_by',
|
||||
'gruppo_servizio_id',
|
||||
];
|
||||
|
||||
/** Il gruppo di servizio da cui è nata (data dal sorvegliante a un membro). */
|
||||
public function gruppo()
|
||||
{
|
||||
return $this->belongsTo(GruppoServizio::class, 'gruppo_servizio_id');
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@@ -35,6 +42,7 @@ class Assegnazione extends Model
|
||||
'counted_in_campaign' => 'boolean',
|
||||
'link_sent' => 'boolean',
|
||||
'link_da_inviare' => 'boolean',
|
||||
'gruppo_servizio_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -56,6 +64,20 @@ class Assegnazione extends Model
|
||||
$territorio->update(['prioritario' => false]);
|
||||
}
|
||||
});
|
||||
|
||||
// Al rientro un territorio del pacchetto di un gruppo torna in reparto
|
||||
// ed esce dal pacchetto. Qui e non nella pagina Rientra: vale per ogni
|
||||
// via (rientro del sorvegliante, dell'amministratore, Registro).
|
||||
static::updated(function (self $assegnazione): void {
|
||||
if (! $assegnazione->wasChanged('returned_at') || $assegnazione->returned_at === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Territorio::withTrashed()
|
||||
->whereKey($assegnazione->territorio_id)
|
||||
->whereNotNull('gruppo_servizio_id')
|
||||
->update(['gruppo_servizio_id' => null]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
75
app/Models/GruppoServizio.php
Normal file
75
app/Models/GruppoServizio.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
|
||||
/**
|
||||
* Gruppo di servizio: un sorvegliante (proclamatore con un'utenza limitata
|
||||
* al gruppo), i membri (proclamatori, un solo gruppo a testa) e un pacchetto
|
||||
* di territori che il sorvegliante distribuisce ai membri.
|
||||
*
|
||||
* Il pacchetto non è un'assegnazione: un territorio del pacchetto non ancora
|
||||
* dato a nessuno è "in carico al gruppo" e non entra nelle statistiche. Quando
|
||||
* il sorvegliante lo dà a un membro nasce un'assegnazione normale, con
|
||||
* gruppo_servizio_id per dire da dove viene. Al rientro il territorio torna in
|
||||
* reparto ed esce dal pacchetto.
|
||||
*
|
||||
* Un gruppo sciolto (sciolto_at) non ha più membri né pacchetto, ma resta: le
|
||||
* assegnazioni nate da lui continuano a citarlo. Logica in
|
||||
* App\Services\GruppiServizio.
|
||||
*/
|
||||
class GruppoServizio extends Model
|
||||
{
|
||||
use LogsActivity;
|
||||
|
||||
protected $table = 'gruppi_servizio';
|
||||
|
||||
protected $fillable = ['nome', 'sorvegliante_id', 'sciolto_at'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return ['sciolto_at' => 'datetime', 'sorvegliante_id' => 'integer'];
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logOnly(['nome', 'sorvegliante_id', 'sciolto_at'])
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
}
|
||||
|
||||
public function sorvegliante()
|
||||
{
|
||||
return $this->belongsTo(Proclamatore::class, 'sorvegliante_id')->withTrashed();
|
||||
}
|
||||
|
||||
public function membri()
|
||||
{
|
||||
return $this->hasMany(Proclamatore::class, 'gruppo_servizio_id');
|
||||
}
|
||||
|
||||
/** Il pacchetto: i territori in carico al gruppo, dati o non ancora dati. */
|
||||
public function territori()
|
||||
{
|
||||
return $this->hasMany(Territorio::class, 'gruppo_servizio_id');
|
||||
}
|
||||
|
||||
public function assegnazioni()
|
||||
{
|
||||
return $this->hasMany(Assegnazione::class, 'gruppo_servizio_id');
|
||||
}
|
||||
|
||||
public function getScioltoAttribute(): bool
|
||||
{
|
||||
return $this->sciolto_at !== null;
|
||||
}
|
||||
|
||||
public function scopeAttivi($query)
|
||||
{
|
||||
return $query->whereNull('sciolto_at');
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,19 @@ class Proclamatore extends Model
|
||||
// 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'];
|
||||
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');
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
@@ -24,6 +36,7 @@ class Proclamatore extends Model
|
||||
'nome' => 'encrypted',
|
||||
'cognome' => 'encrypted',
|
||||
'attivo' => 'boolean',
|
||||
'gruppo_servizio_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,20 @@ class Territorio extends Model
|
||||
'thumbnail_path',
|
||||
'attivo',
|
||||
'prioritario',
|
||||
'gruppo_servizio_id',
|
||||
];
|
||||
|
||||
/** Il gruppo di servizio nel cui pacchetto si trova, se c'è. */
|
||||
public function gruppo()
|
||||
{
|
||||
return $this->belongsTo(GruppoServizio::class, 'gruppo_servizio_id');
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'attivo' => 'boolean',
|
||||
'gruppo_servizio_id' => 'integer',
|
||||
'prioritario' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -95,9 +103,25 @@ class Territorio extends Model
|
||||
return 'assegnato';
|
||||
}
|
||||
|
||||
// Nel pacchetto di un gruppo e non ancora dato a nessuno: non è in
|
||||
// reparto (non si assegna dalla pagina normale), è del gruppo.
|
||||
if ($this->gruppo_servizio_id) {
|
||||
return 'in_gruppo';
|
||||
}
|
||||
|
||||
return 'in_reparto';
|
||||
}
|
||||
|
||||
/** Lo stato da mostrare: "In gruppo Zisa" invece di "In gruppo". */
|
||||
public function getStatoEtichettaAttribute(): string
|
||||
{
|
||||
$stato = $this->stato;
|
||||
|
||||
return $stato === 'in_gruppo'
|
||||
? 'In gruppo ' . ($this->gruppo?->nome ?? '')
|
||||
: str_replace('_', ' ', ucfirst($stato));
|
||||
}
|
||||
|
||||
public function getAssegnatarioAttribute(): ?Proclamatore
|
||||
{
|
||||
return $this->assegnazioneCorrente?->proclamatore;
|
||||
@@ -172,12 +196,23 @@ class Territorio extends Model
|
||||
|
||||
public function scopeInReparto($query)
|
||||
{
|
||||
// Quelli nel pacchetto di un gruppo non sono in reparto: stanno al
|
||||
// gruppo finché il sorvegliante non li dà o non li rimanda indietro.
|
||||
return $query->attivi()
|
||||
->whereNull('gruppo_servizio_id')
|
||||
->whereDoesntHave('assegnazioni', function ($q) {
|
||||
$q->whereNull('returned_at');
|
||||
});
|
||||
}
|
||||
|
||||
/** Nel pacchetto di un gruppo e non ancora dati a nessuno. */
|
||||
public function scopeInGruppo($query)
|
||||
{
|
||||
return $query->attivi()
|
||||
->whereNotNull('gruppo_servizio_id')
|
||||
->whereDoesntHave('assegnazioni', fn ($q) => $q->whereNull('returned_at'));
|
||||
}
|
||||
|
||||
public function scopeAssegnato($query)
|
||||
{
|
||||
return $query->attivi()
|
||||
|
||||
@@ -24,6 +24,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'email',
|
||||
'password',
|
||||
'password_change_required',
|
||||
'proclamatore_id',
|
||||
'disattivato_at',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -37,9 +39,50 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'password_change_required' => 'boolean',
|
||||
'disattivato_at' => 'datetime',
|
||||
'proclamatore_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** Il proclamatore di cui è l'utenza (sorvegliante di gruppo). */
|
||||
public function proclamatore()
|
||||
{
|
||||
return $this->belongsTo(Proclamatore::class, 'proclamatore_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorvegliante di gruppo: vede solo il proprio gruppo e il proprio
|
||||
* profilo (middleware LimitaSorvegliante).
|
||||
*/
|
||||
public function isSorvegliante(): bool
|
||||
{
|
||||
return $this->hasRole('sorvegliante');
|
||||
}
|
||||
|
||||
/** Il gruppo (attivo) di cui è sorvegliante, se c'è. */
|
||||
public function gruppoSorvegliato(): ?GruppoServizio
|
||||
{
|
||||
return $this->proclamatore_id
|
||||
? GruppoServizio::attivi()->where('sorvegliante_id', $this->proclamatore_id)->first()
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Utenza disattivata (gruppo sciolto): non può più entrare. */
|
||||
public function isDisattivato(): bool
|
||||
{
|
||||
return $this->disattivato_at !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Il sorvegliante non conferma l'email (scelta dell'utente, 19 ottobre
|
||||
* 2026): l'utenza la crea l'amministratore con una password temporanea.
|
||||
* Per tutti gli altri resta obbligatoria.
|
||||
*/
|
||||
public function hasVerifiedEmail(): bool
|
||||
{
|
||||
return ! is_null($this->email_verified_at) || $this->isSorvegliante();
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
@@ -65,6 +108,10 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
*/
|
||||
public function inviaConfermaEmail(): bool
|
||||
{
|
||||
if ($this->isSorvegliante()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->sendEmailVerificationNotification();
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
Reference in New Issue
Block a user