preselectedTerritorioId = $territorioId; $this->territorio_id = $territorioId; $this->assigned_at = now()->format('Y-m-d'); $this->elencoNascosto = request()->boolean('dalla_home'); // Dalla Home, da una richiesta: il proclamatore scelto fra i nomi // simili arriva già impostato (?proclamatore=), il resto del modulo è // quello di sempre. if (($id = request()->integer('proclamatore')) && Proclamatore::attivi()->whereKey($id)->exists()) { $this->proclamatore_id = $id; } if (($id = request()->integer('richiesta')) && auth()->user()->can('richieste.gestisci') && Richiesta::aperte()->whereKey($id)->exists()) { $this->richiestaId = $id; } // ?invia=: riprende l'invio di un link rimasto in sospeso (dalla // scheda del territorio). if ($invia = request()->integer('invia')) { $this->apriInvio($invia); } } protected function rules(): array { return [ 'territorio_id' => 'required|exists:territori,id', 'proclamatore_id' => 'required|exists:proclamatori,id', 'assigned_at' => 'required|date|before_or_equal:today', ]; } public function save() { $this->validate(); $territorio = Territorio::findOrFail($this->territorio_id); // Gli errori vanno sul campo e non in sessione: il messaggio flash // sta nel telaio della pagina, che Livewire non ridisegna, e il // rifiuto passerebbe inosservato. if ($territorio->assegnazioneCorrente) { $this->addError('territorio_id', "Il territorio {$territorio->numero} è già assegnato a {$territorio->assegnazioneCorrente->proclamatore->nome_completo}."); return; } if (!$territorio->attivo) { $this->addError('territorio_id', "Il territorio {$territorio->numero} è inattivo."); return; } // Nel pacchetto di un gruppo di servizio: lo assegna il sorvegliante. if ($territorio->gruppo_servizio_id) { $this->addError('territorio_id', "Il territorio {$territorio->numero} è nel pacchetto del gruppo {$territorio->gruppo?->nome}: lo assegna il sorvegliante, oppure va prima tolto dal pacchetto."); return; } $proclamatore = Proclamatore::findOrFail($this->proclamatore_id); if (!$proclamatore->attivo) { $this->addError('proclamatore_id', "Il proclamatore {$proclamatore->nome_completo} è inattivo."); return; } $assignedDate = \Carbon\Carbon::parse($this->assigned_at); $annoTeocratico = AnnoTeocratico::perData($assignedDate); // Controllo e inserimento sotto lo stesso lock sulla riga del // territorio: un doppio clic o due schede aperte non possono più // superare entrambi il controllo e creare due assegnazioni aperte. $esito = DB::transaction(function () use ($territorio, $assignedDate, $annoTeocratico) { Territorio::whereKey($territorio->id)->lockForUpdate()->first(); $conflitto = Assegnazione::sovrappostaA($territorio->id, $assignedDate); if ($conflitto) { return "Il territorio {$territorio->numero} risulta assegnato {$conflitto->descrizionePeriodo()}: " . ($conflitto->returned_at ? 'la data di assegnazione non può essere precedente al suo rientro.' : 'registrane prima il rientro.'); } return Assegnazione::create([ 'territorio_id' => $this->territorio_id, 'proclamatore_id' => $this->proclamatore_id, 'anno_teocratico_id' => $annoTeocratico->id, 'assigned_at' => $assignedDate, 'created_by' => auth()->id(), // C'è un link da mandare solo se il territorio ha il PDF. 'link_da_inviare' => (bool) $territorio->pdf_path, 'cartaceo' => $this->cartaceo, ]); }); if (is_string($esito)) { $this->addError('assigned_at', $esito); return; } $assegnazione = $esito; activity()->causedBy(auth()->user()) ->performedOn($assegnazione) // L'id, non il nome: nome e cognome sono cifrati nel database, e // copiati qui in chiaro restavano leggibili anche dopo // l'anonimizzazione. Il registro mostra il nome di oggi. ->withProperties([ 'territorio' => $territorio->numero, 'proclamatore_id' => $proclamatore->id, ]) ->log('assigned'); // Partita da una richiesta del modulo pubblico: ora è evasa. if ($this->richiestaId) { Richiesta::find($this->richiestaId)?->evadi($assegnazione, auth()->user()); $this->richiestaId = null; } $this->cartaceo = false; // Non si va avanti: si apre l'invio del link (vedi in cima). $this->apriInvio($assegnazione->id); } // ─── Invio del link (trait InviaLink) ────────────────────── protected function puoInviare(Assegnazione $assegnazione): bool { return auth()->user()->can('territori.assign'); } protected function paginaDopoInvio(): string { return $this->destinazione(route('assegnazioni.assegna')); } public function render() { // Le opzioni dei campi di scelta con ricerca (). Ci sono anche // i territori già assegnati, che non si possono scegliere: cercandone // uno si legge a chi è, invece di un "nessun risultato". La miniatura // serve all'anteprima, che si aggiorna nel browser. // Quelli nel pacchetto di un gruppo di servizio li assegna il // sorvegliante: qui si trovano ma non si scelgono. $territori = Territorio::attivi() ->with(['zona', 'tipologia', 'assegnazioneCorrente.proclamatore', 'gruppo']) ->get() ->sortBy('numero', SORT_NATURAL); $opzioniTerritori = $territori ->map(fn (Territorio $t) => $t->opzioneScelta(match (true) { (bool) $t->assegnazioneCorrente => 'già assegnato a ' . ($t->assegnazioneCorrente->proclamatore?->nome_completo ?? '—'), (bool) $t->gruppo_servizio_id => 'nel pacchetto del gruppo ' . ($t->gruppo?->nome ?? ''), default => null, }) + array_filter(['miniatura' => $t->miniaturaUrl()])) ->values() ->all(); $opzioniProclamatori = Proclamatore::opzioniScelta(Proclamatore::attivi()->get()); // L'elenco dei territori assegnati sta in ElencoAssegnati. return view('livewire.assegnazioni.assegna', [ 'invio' => $this->invioCorrente(), 'opzioniTerritori' => $opzioniTerritori, 'opzioniProclamatori' => $opzioniProclamatori, 'anteprimaIniziale' => $this->territorio_id ? $territori->firstWhere('id', $this->territorio_id)?->miniaturaUrl() : null, 'richiesta' => $this->richiestaId ? Richiesta::find($this->richiestaId) : null, ]); } }