preselectedTerritorioId = $territorioId; $this->territorio_id = $territorioId; $this->assigned_at = now()->format('Y-m-d'); } 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; } $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(), ]); }); 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'); session()->flash('success', "Territorio {$territorio->numero} assegnato a {$proclamatore->nome_completo}."); return $this->redirect(route('territori.show', $territorio), navigate: true); } public function toggleLinkSent(int $assegnazioneId): void { $this->authorize('territori.assign'); $assegnazione = Assegnazione::findOrFail($assegnazioneId); $assegnazione->forceFill(['link_sent' => ! $assegnazione->link_sent])->saveQuietly(); } #[Computed] public function selectedThumbnailUrl(): ?string { if (!$this->territorio_id) { return null; } $t = Territorio::find($this->territorio_id); return $t?->miniaturaUrl(); } public function render() { $territoriQuery = Territorio::where('attivo', true) ->whereDoesntHave('assegnazioni', fn($q) => $q->aperte()) ->with(['zona', 'tipologia']); if (trim($this->territorioSearch) !== '') { $term = trim($this->territorioSearch); $territoriQuery->where(function ($q) use ($term) { $q->where('numero', 'like', "%{$term}%") ->orWhereHas('zona', fn($z) => $z->where('nome', 'like', "%{$term}%")) ->orWhereHas('tipologia', fn($t) => $t->where('nome', 'like', "%{$term}%")); }); } $territoriDisponibili = $territoriQuery ->orderBy('numero') ->get(); $proclamatoriAttivi = Proclamatore::attivi() ->get() ->sortBy(fn($p) => mb_strtolower($p->cognome . ' ' . $p->nome)); // All currently assigned territories with links $assegnazioniAperte = Assegnazione::aperte() ->with(['territorio.zona', 'proclamatore']) ->get() ->sortBy(fn($a) => (int) $a->territorio?->numero); // Ricerca rapida nella lista dei link: numero territorio, zona, // proclamatore in un solo campo. In memoria perché nome/cognome del // proclamatore sono cifrati e non filtrabili via SQL. $assegnazioniAperteVisibili = $assegnazioniAperte; if (trim($this->linkSearch) !== '') { $term = mb_strtolower(trim($this->linkSearch)); $assegnazioniAperteVisibili = $assegnazioniAperte->filter(function ($a) use ($term) { $haystack = mb_strtolower( ($a->territorio?->numero ?? '') . ' ' . ($a->territorio?->zona?->nome ?? '') . ' ' . ($a->proclamatore?->nome_completo ?? '') ); return str_contains($haystack, $term); })->values(); } // Il link scade alla soglia rientro (giorni_per_smarrito). $sogliaRientro = max(1, (int) \App\Models\Setting::getValue('giorni_per_smarrito', 120)); return view('livewire.assegnazioni.assegna', [ 'territoriDisponibili' => $territoriDisponibili, 'proclamatoriAttivi' => $proclamatoriAttivi, 'assegnazioniAperte' => $assegnazioniAperte, 'assegnazioniAperteVisibili' => $assegnazioniAperteVisibili, 'sogliaRientro' => $sogliaRientro, ]); } }