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.'); } } }