HANDOFF: trappola di Blade sul @php in linea, test sul server prima del push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
273 lines
11 KiB
PHP
273 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\AnnoTeocratico;
|
|
use App\Models\Assegnazione;
|
|
use App\Models\GruppoServizio;
|
|
use App\Models\Proclamatore;
|
|
use App\Models\Territorio;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Le regole dei gruppi di servizio, in un posto solo: le usano le pagine
|
|
* dell'amministratore (Gruppi di servizio) e quella del sorvegliante (Il mio
|
|
* gruppo). Ogni metodo lancia InvalidArgumentException con un messaggio da
|
|
* mostrare così com'è quando una regola non è rispettata.
|
|
*/
|
|
class GruppiServizio
|
|
{
|
|
/**
|
|
* Crea il gruppo e l'utenza del sorvegliante: 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.
|
|
*
|
|
* @param int[] $membri id dei proclamatori, oltre al sorvegliante
|
|
* @return array{0: GruppoServizio, 1: string}
|
|
*/
|
|
public function crea(string $nome, Proclamatore $sorvegliante, string $email, array $membri = []): array
|
|
{
|
|
$nome = trim($nome);
|
|
if ($nome === '') {
|
|
throw new InvalidArgumentException('Il nome del gruppo è obbligatorio.');
|
|
}
|
|
if (! $sorvegliante->attivo || $sorvegliante->trashed()) {
|
|
throw new InvalidArgumentException('Il sorvegliante deve essere un proclamatore attivo.');
|
|
}
|
|
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()) {
|
|
throw new InvalidArgumentException("{$sorvegliante->nome_completo} ha già un'utenza.");
|
|
}
|
|
if (User::where('email', $email)->when($esistente, fn ($q) => $q->whereKeyNot($esistente->id))->exists()) {
|
|
throw new InvalidArgumentException("L'email {$email} è già usata da un altro utente.");
|
|
}
|
|
|
|
$password = Str::password(12, symbols: false);
|
|
|
|
$gruppo = DB::transaction(function () use ($nome, $sorvegliante, $email, $membri, $password, $esistente) {
|
|
$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([
|
|
'name' => $sorvegliante->nome_completo,
|
|
'email' => $email,
|
|
'password' => $password,
|
|
'password_change_required' => true,
|
|
'disattivato_at' => null,
|
|
])->save();
|
|
$utente->syncRoles(['sorvegliante']);
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
|
->withProperties(['sorvegliante_id' => $sorvegliante->id, 'utente_id' => $utente->id])
|
|
->log('gruppo_creato');
|
|
|
|
return $gruppo;
|
|
});
|
|
|
|
return [$gruppo, $password];
|
|
}
|
|
|
|
/** @param int[] $ids */
|
|
public function aggiungiMembri(GruppoServizio $gruppo, array $ids): void
|
|
{
|
|
$this->attivo($gruppo);
|
|
|
|
foreach (Proclamatore::whereIn('id', $ids)->get() as $p) {
|
|
if ($p->gruppo_servizio_id && $p->gruppo_servizio_id !== $gruppo->id) {
|
|
throw new InvalidArgumentException("{$p->nome_completo} fa già parte di un altro gruppo.");
|
|
}
|
|
if (! $p->attivo) {
|
|
throw new InvalidArgumentException("{$p->nome_completo} non è attivo.");
|
|
}
|
|
$p->update(['gruppo_servizio_id' => $gruppo->id]);
|
|
}
|
|
}
|
|
|
|
public function togliMembro(GruppoServizio $gruppo, Proclamatore $membro): void
|
|
{
|
|
$this->attivo($gruppo);
|
|
|
|
if ($membro->id === $gruppo->sorvegliante_id) {
|
|
throw new InvalidArgumentException('Il sorvegliante non si toglie: per cambiarlo si scioglie il gruppo.');
|
|
}
|
|
if ($membro->gruppo_servizio_id === $gruppo->id) {
|
|
$membro->update(['gruppo_servizio_id' => null]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mette nel pacchetto del gruppo dei territori in reparto (attivi, non
|
|
* assegnati, non già in un altro pacchetto). Restituisce quanti.
|
|
*
|
|
* @param int[] $ids
|
|
*/
|
|
public function aggiungiAlPacchetto(GruppoServizio $gruppo, array $ids): int
|
|
{
|
|
$this->attivo($gruppo);
|
|
|
|
$territori = Territorio::whereIn('id', $ids)->get();
|
|
foreach ($territori as $t) {
|
|
if (! $t->attivo) {
|
|
throw new InvalidArgumentException("Il territorio {$t->numero} non è attivo.");
|
|
}
|
|
if ($t->gruppo_servizio_id && $t->gruppo_servizio_id !== $gruppo->id) {
|
|
throw new InvalidArgumentException("Il territorio {$t->numero} è già nel pacchetto di un altro gruppo.");
|
|
}
|
|
if ($t->assegnazioneCorrente) {
|
|
throw new InvalidArgumentException("Il territorio {$t->numero} è assegnato: va prima fatto rientrare.");
|
|
}
|
|
}
|
|
|
|
Territorio::whereKey($territori->pluck('id'))->update(['gruppo_servizio_id' => $gruppo->id]);
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
|
->withProperties(['territori' => $territori->pluck('numero')->all()])
|
|
->log('pacchetto_aggiunti');
|
|
|
|
return $territori->count();
|
|
}
|
|
|
|
/**
|
|
* Rimanda in reparto un territorio del pacchetto non ancora dato a
|
|
* nessuno (lo fa l'amministratore o il sorvegliante).
|
|
*/
|
|
public function rimandaInReparto(GruppoServizio $gruppo, Territorio $territorio): void
|
|
{
|
|
if ($territorio->gruppo_servizio_id !== $gruppo->id) {
|
|
throw new InvalidArgumentException("Il territorio {$territorio->numero} non è nel pacchetto del gruppo.");
|
|
}
|
|
if ($territorio->assegnazioneCorrente) {
|
|
throw new InvalidArgumentException("Il territorio {$territorio->numero} è assegnato: prima va registrato il rientro.");
|
|
}
|
|
|
|
$territorio->update(['gruppo_servizio_id' => null]);
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
|
->withProperties(['territorio' => $territorio->numero])
|
|
->log('pacchetto_rimandato_in_reparto');
|
|
}
|
|
|
|
/**
|
|
* Il sorvegliante dà un territorio del pacchetto a un membro (o a sé):
|
|
* nasce un'assegnazione normale, segnata come proveniente dal gruppo. Il
|
|
* territorio resta nel pacchetto finché non rientra.
|
|
*/
|
|
public function assegna(GruppoServizio $gruppo, Territorio $territorio, Proclamatore $membro, Carbon $data): Assegnazione
|
|
{
|
|
$this->attivo($gruppo);
|
|
|
|
// Si decide sui dati di adesso, non su quelli con cui è partita la
|
|
// pagina: nel frattempo l'amministratore può aver cambiato pacchetto
|
|
// o membri.
|
|
$territorio->refresh();
|
|
$membro->refresh();
|
|
|
|
if ($territorio->gruppo_servizio_id !== $gruppo->id) {
|
|
throw new InvalidArgumentException("Il territorio {$territorio->numero} non è nel pacchetto del gruppo.");
|
|
}
|
|
if ($membro->gruppo_servizio_id !== $gruppo->id) {
|
|
throw new InvalidArgumentException("{$membro->nome_completo} non fa parte del gruppo.");
|
|
}
|
|
if (! $membro->attivo) {
|
|
throw new InvalidArgumentException("{$membro->nome_completo} non è attivo.");
|
|
}
|
|
if ($data->isFuture()) {
|
|
throw new InvalidArgumentException('La data di assegnazione non può essere nel futuro.');
|
|
}
|
|
|
|
$esito = DB::transaction(function () use ($gruppo, $territorio, $membro, $data) {
|
|
$bloccato = Territorio::whereKey($territorio->id)->lockForUpdate()->first();
|
|
if (! $bloccato || $bloccato->gruppo_servizio_id !== $gruppo->id) {
|
|
return "Il territorio {$territorio->numero} non è più nel pacchetto del gruppo.";
|
|
}
|
|
|
|
$conflitto = Assegnazione::sovrappostaA($territorio->id, $data);
|
|
if ($conflitto) {
|
|
return "Il territorio {$territorio->numero} risulta assegnato {$conflitto->descrizionePeriodo()}.";
|
|
}
|
|
|
|
return Assegnazione::create([
|
|
'territorio_id' => $territorio->id,
|
|
'proclamatore_id' => $membro->id,
|
|
'anno_teocratico_id' => AnnoTeocratico::perData($data)->id,
|
|
'assigned_at' => $data,
|
|
'created_by' => auth()->id(),
|
|
'gruppo_servizio_id' => $gruppo->id,
|
|
'link_da_inviare' => (bool) $territorio->pdf_path,
|
|
]);
|
|
});
|
|
|
|
if (is_string($esito)) {
|
|
throw new InvalidArgumentException($esito);
|
|
}
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($esito)
|
|
->withProperties([
|
|
'territorio' => $territorio->numero,
|
|
'proclamatore_id' => $membro->id,
|
|
'gruppo_servizio_id' => $gruppo->id,
|
|
])
|
|
->log('assigned');
|
|
|
|
return $esito;
|
|
}
|
|
|
|
/**
|
|
* Scioglie il gruppo (si toglie il ruolo di sorvegliante): i territori del
|
|
* pacchetto non ancora dati tornano in reparto, i membri restano senza
|
|
* gruppo, l'utenza del sorvegliante si disattiva. Le assegnazioni già
|
|
* fatte restano com'erano, con il loro riferimento al gruppo, che resta
|
|
* nello storico come sciolto.
|
|
*/
|
|
public function sciogli(GruppoServizio $gruppo): void
|
|
{
|
|
if ($gruppo->sciolto_at) {
|
|
return;
|
|
}
|
|
|
|
DB::transaction(function () use ($gruppo) {
|
|
// Esce dal pacchetto solo quello non ancora dato: un territorio
|
|
// assegnato resta del suo gruppo finché non rientra (Assegnazione
|
|
// lo libera al rientro), così non può essere ridato due volte.
|
|
$nonDati = Territorio::withTrashed()
|
|
->where('gruppo_servizio_id', $gruppo->id)
|
|
->whereDoesntHave('assegnazioni', fn ($q) => $q->whereNull('returned_at'));
|
|
$liberati = (clone $nonDati)->pluck('numero');
|
|
$nonDati->update(['gruppo_servizio_id' => null]);
|
|
|
|
Proclamatore::withTrashed()->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');
|
|
$utente->forceFill(['disattivato_at' => now(), 'remember_token' => null])->save();
|
|
}
|
|
|
|
$gruppo->update(['sciolto_at' => now()]);
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($gruppo)
|
|
->withProperties(['territori_liberati' => $liberati->all(), 'utente_disattivato' => $utente?->id])
|
|
->log('gruppo_sciolto');
|
|
});
|
|
}
|
|
|
|
private function attivo(GruppoServizio $gruppo): void
|
|
{
|
|
if ($gruppo->sciolto_at) {
|
|
throw new InvalidArgumentException('Il gruppo è stato sciolto: creane uno nuovo.');
|
|
}
|
|
}
|
|
}
|