hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(int $record): void { $this->record = $record; $this->assemblea = Assemblea::with('stabile')->find($this->record); if (!$this->assemblea) { abort(404); } $this->stabileAttivo = $this->assemblea->stabile; $this->editTipo = $this->assemblea->tipo ?: 'ordinaria'; $this->editData1 = $this->assemblea->data_prima_convocazione ? $this->assemblea->data_prima_convocazione->format('Y-m-d\TH:i') : ''; $this->editData2 = $this->assemblea->data_seconda_convocazione ? $this->assemblea->data_seconda_convocazione->format('Y-m-d\TH:i') : ''; $this->editLuogo = $this->assemblea->luogo ?: ''; $this->editNote = $this->assemblea->note ?: ''; $this->resetOdgForm(); } public function changeTab(string $tab): void { $this->activeSubTab = $tab; } // --- AZIONI GESTIONE ODG --- public function resetOdgForm(): void { $this->odgNumeroPunto = (OrdineGiorno::where('assemblea_id', $this->record)->max('numero_punto') ?? 0) + 1; $this->odgTitolo = ''; $this->odgDescrizione = ''; $this->odgArticoloLegge = ''; $this->odgTabellaMillesimaleId = null; $this->odgMaggioranza = 'semplice'; $this->odgRiferimentoLegge = ''; $this->odgAllegati = []; $this->audioUpload = null; } public function aggiungiPuntoOdG(): void { $this->validate([ 'odgNumeroPunto' => 'required|integer', 'odgTitolo' => 'required|string|max:255', 'odgDescrizione' => 'nullable|string', ]); $allegatiPaths = []; if ($this->odgAllegati) { foreach ($this->odgAllegati as $file) { $path = $file->store('assemblee/' . $this->record . '/allegati', 'public'); $allegatiPaths[] = [ 'nome' => $file->getClientOriginalName(), 'path' => $path, 'url' => asset('storage/' . $path) ]; } } $maxOrder = OrdineGiorno::where('assemblea_id', $this->record)->max('ordinamento') ?? 0; OrdineGiorno::create([ 'assemblea_id' => $this->record, 'numero_punto' => $this->odgNumeroPunto, 'ordinamento' => $maxOrder + 1, 'titolo' => $this->odgTitolo, 'descrizione' => $this->odgDescrizione ?: '', 'allegati' => $allegatiPaths, 'articolo_legge' => $this->odgArticoloLegge, 'tabella_millesimale_id' => $this->odgTabellaMillesimaleId, 'maggioranza_richiesta' => $this->odgMaggioranza, 'riferimento_legge' => $this->odgRiferimentoLegge, ]); $this->resetOdgForm(); $this->notification('Punto all\'ordine del giorno aggiunto!'); } public function importaSospeso(int $odgId): void { $sospeso = OrdineGiorno::find($odgId); if ($sospeso) { $maxOrder = OrdineGiorno::where('assemblea_id', $this->record)->max('ordinamento') ?? 0; $maxNumeroPunto = OrdineGiorno::where('assemblea_id', $this->record)->max('numero_punto') ?? 0; $nuovo = OrdineGiorno::create([ 'assemblea_id' => $this->record, 'numero_punto' => $maxNumeroPunto + 1, 'ordinamento' => $maxOrder + 1, 'titolo' => $sospeso->titolo, 'descrizione' => $sospeso->descrizione, 'allegati' => $sospeso->allegati, 'articolo_legge' => $sospeso->articolo_legge, 'tabella_millesimale_id' => $sospeso->tabella_millesimale_id, 'maggioranza_richiesta' => $sospeso->maggioranza_richiesta, 'riferimento_legge' => $sospeso->riferimento_legge, ]); $sospeso->update([ 'imported_to_id' => $nuovo->id ]); $this->notification('Punto sospeso importato con successo!'); } } public function getDisponibiliSospesiProperty(): Collection { if (!$this->stabileAttivo) return collect(); $stabileId = $this->stabileAttivo->id; return OrdineGiorno::query() ->whereHas('assemblea', function ($query) use ($stabileId) { $query->where('stabile_id', $stabileId) ->where('stato', 'svolta') ->where('id', '!=', $this->record); }) ->whereIn('esito_votazione', ['non_deliberato', 'rimandato', 'sospeso']) ->whereNull('imported_to_id') ->orderBy('id') ->get(); } public function spostaPuntoOdG(int $id, string $direction): void { $punto = OrdineGiorno::find($id); if (!$punto) return; $punti = OrdineGiorno::where('assemblea_id', $this->record) ->orderBy('ordinamento') ->orderBy('id') ->get(); foreach ($punti as $index => $p) { if ($p->ordinamento !== ($index + 1)) { $p->update(['ordinamento' => $index + 1]); } } $punto->refresh(); $currentOrder = $punto->ordinamento; if ($direction === 'up' && $currentOrder > 1) { $prevPunto = OrdineGiorno::where('assemblea_id', $this->record) ->where('ordinamento', $currentOrder - 1) ->first(); if ($prevPunto) { $prevPunto->update(['ordinamento' => $currentOrder]); $punto->update(['ordinamento' => $currentOrder - 1]); } } elseif ($direction === 'down' && $currentOrder < $punti->count()) { $nextPunto = OrdineGiorno::where('assemblea_id', $this->record) ->where('ordinamento', $currentOrder + 1) ->first(); if ($nextPunto) { $nextPunto->update(['ordinamento' => $currentOrder]); $punto->update(['ordinamento' => $currentOrder + 1]); } } elseif ($direction === 'top' && $currentOrder > 1) { OrdineGiorno::where('assemblea_id', $this->record) ->where('ordinamento', '<', $currentOrder) ->increment('ordinamento'); $punto->update(['ordinamento' => 1]); } $puntiAggiornati = OrdineGiorno::where('assemblea_id', $this->record) ->orderBy('ordinamento') ->get(); foreach ($puntiAggiornati as $idx => $p) { $p->update([ 'ordinamento' => $idx + 1, 'numero_punto' => $idx + 1 ]); } $this->resetOdgForm(); $this->notification('Ordinamento punti aggiornato!'); } public function caricaAudioLog(int $odgId): void { $this->validate([ 'audioUpload' => 'required|file|max:20480', // 20MB max ]); $punto = OrdineGiorno::find($odgId); if ($punto) { $path = $this->audioUpload->store('assemblee/' . $this->record . '/audio', 'public'); $trascrizioneMock = "Trascrizione AI (" . now()->format('d/m/Y H:i') . "): Discussione sul punto \"" . $punto->titolo . "\". L'assemblea delibera in conformità con quanto proposto. Vengono citati i riferimenti normativi relativi per la maggioranza qualificata."; $punto->update([ 'audio_log_path' => $path, 'audio_log_trascrizione' => $trascrizioneMock, ]); $this->audioUpload = null; $this->notification('Audio caricato e trascritto tramite AI con successo!'); } } public function eliminaPuntoOdG(int $id): void { $punto = OrdineGiorno::find($id); if ($punto) { $punto->delete(); $this->notification('Punto OdG eliminato.'); } } public function aggiornaEsitoPunto(int $odgId, string $esito, string $note = ''): void { $punto = OrdineGiorno::find($odgId); if ($punto) { $punto->update([ 'esito_votazione' => $esito ? trim($esito) : null, 'note_delibera' => $note ? trim($note) : null, ]); $this->notification('Esito punto aggiornato!'); } } // --- AZIONI GESTIONE CONVOCAZIONI --- public function generaConvocazioniMassive(): void { $this->sincronizzaNominativi(); } public function sincronizzaNominativi(): void { if (!$this->stabileAttivo) return; $unitaList = UnitaImmobiliare::where('stabile_id', $this->stabileAttivo->id) ->with(['rubricaRuoliAttivi.contatto', 'soggetti']) ->get(); $activePairs = []; $createdCount = 0; $restoredCount = 0; foreach ($unitaList as $unita) { $ruoli = $unita->rubricaRuoliAttivi ?? collect(); $soggettiForUnita = collect(); foreach ($ruoli as $ruolo) { $soggetto = $ruolo->contatto; if ($soggetto) { $soggettiForUnita->push([ 'soggetto' => $soggetto, 'ruolo' => strtolower(trim((string)$ruolo->ruolo_standard)) ]); } } if ($soggettiForUnita->isEmpty()) { $soggetti = $unita->soggetti ?? collect(); foreach ($soggetti as $s) { $soggettiForUnita->push([ 'soggetto' => $s, 'ruolo' => 'C' ]); } } foreach ($soggettiForUnita as $item) { $soggetto = $item['soggetto']; $roleLabel = $item['ruolo']; $ruoloAbbr = 'C'; if (in_array($roleLabel, ['inquilino', 'locatario', 'conduttore', 'i'], true)) { $ruoloAbbr = 'I'; } // Escludi inquilini ('I') dalle convocazioni assembleari if ($ruoloAbbr === 'I') { continue; } $activePairs[] = $soggetto->id . '|' . $unita->id; $conv = Convocazione::where('assemblea_id', $this->record) ->where('soggetto_id', $soggetto->id) ->where('unita_immobiliare_id', $unita->id) ->first(); if (!$conv) { Convocazione::create([ 'assemblea_id' => $this->record, 'unita_immobiliare_id' => $unita->id, 'soggetto_id' => $soggetto->id, 'ruolo' => $ruoloAbbr, 'consegnato_canale' => $soggetto->pec ? 'pec' : ($soggetto->email ? 'email' : ($soggetto->telefono ? 'whatsapp' : 'posta')), 'token_accesso' => Str::random(40), 'archiviata' => false, ]); $createdCount++; } elseif ($conv->archiviata) { $conv->update(['archiviata' => false]); $restoredCount++; } } } // Archivia convocazioni obsolete (non più presenti come soggetti/unità attivi nello stabile) $obsoleteConvs = Convocazione::where('assemblea_id', $this->record) ->where('archiviata', false) ->get(); $archivedCount = 0; foreach ($obsoleteConvs as $c) { $pairKey = $c->soggetto_id . '|' . $c->unita_immobiliare_id; if (!in_array($pairKey, $activePairs, true)) { $c->update(['archiviata' => true]); $archivedCount++; } } $msg = "Sincronizzazione completata! Nuove: {$createdCount}, Ripristinate: {$restoredCount}"; if ($archivedCount > 0) { $msg .= ", Archiviate: {$archivedCount}"; } $this->notification($msg); } public function cancellaTutteConvocazioni(): void { Convocazione::where('assemblea_id', $this->record)->delete(); $this->notification('Tutte le convocazioni sono state eliminate.'); } public function inviaConvocazioneSingola(int $id): void { $conv = Convocazione::find($id); if ($conv) { $conv->update(['consegnato_at' => now()]); $this->notification('Notifica di convocazione simulata inviata con successo!'); } } public function downloadFoglioFirme(): \Symfony\Component\HttpFoundation\StreamedResponse { $this->assemblea->load(['stabile']); $unitaList = UnitaImmobiliare::where('stabile_id', $this->stabileAttivo->id) ->orderBy('palazzina') ->orderBy('scala') ->orderBy('interno') ->with(['rubricaRuoliAttivi.contatto', 'soggetti']) ->get(); $rows = []; foreach ($unitaList as $unita) { $soggettiForUnita = collect(); $ruoli = $unita->rubricaRuoliAttivi ?? collect(); foreach ($ruoli as $ruolo) { $soggetto = $ruolo->contatto; if ($soggetto) { $soggettiForUnita->push([ 'nome' => $soggetto->nome_completo, 'ruolo' => strtolower(trim((string)$ruolo->ruolo_standard)) === 'inquilino' ? 'Inquilino' : 'Proprietario', 'token' => Convocazione::where('assemblea_id', $this->record) ->where('soggetto_id', $soggetto->id) ->where('unita_immobiliare_id', $unita->id) ->value('token_accesso') ]); } } if ($soggettiForUnita->isEmpty()) { $soggetti = $unita->soggetti ?? collect(); foreach ($soggetti as $s) { $soggettiForUnita->push([ 'nome' => $s->nome_completo, 'ruolo' => 'Proprietario', 'token' => Convocazione::where('assemblea_id', $this->record) ->where('soggetto_id', $s->id) ->where('unita_immobiliare_id', $unita->id) ->value('token_accesso') ]); } } foreach ($soggettiForUnita as $item) { $millesimi = DB::table('unita_immobiliari') ->where('id', $unita->id) ->value('millesimi_generali') ?: 0.000; $rows[] = [ 'unita' => 'Pal. ' . ($unita->palazzina ?: 'A') . ' - Int. ' . $unita->interno, 'soggetto' => $item['nome'], 'ruolo' => $item['ruolo'], 'millesimi' => number_format($millesimi, 3, ',', '.'), 'qr_url' => url('/public/assemblea/' . ($item['token'] ?: 'unknown')) ]; } } $html = view('pdf.foglio-firme', [ 'stabile' => $this->stabileAttivo, 'assemblea' => $this->assemblea, 'rows' => $rows ])->render(); $options = new \Dompdf\Options(); $options->set('isRemoteEnabled', true); $dompdf = new \Dompdf\Dompdf($options); $dompdf->setPaper('A4', 'portrait'); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->render(); return response()->streamDownload( fn() => print($dompdf->output()), 'foglio_firme_assemblea_' . $this->record . '.pdf' ); } public function downloadConvocazioniPdf(): \Symfony\Component\HttpFoundation\StreamedResponse { $this->assemblea->load(['stabile']); $convocazioni = Convocazione::where('assemblea_id', $this->record) ->where('archiviata', false) ->with(['soggetto', 'unitaImmobiliare']) ->get(); $odg = OrdineGiorno::where('assemblea_id', $this->record) ->orderBy('ordinamento') ->orderBy('numero_punto') ->get(); $html = view('pdf.convocazione', [ 'stabile' => $this->stabileAttivo, 'assemblea' => $this->assemblea, 'convocazioni' => $convocazioni, 'odg' => $odg ])->render(); $options = new \Dompdf\Options(); $options->set('isRemoteEnabled', true); $dompdf = new \Dompdf\Dompdf($options); $dompdf->setPaper('A4', 'portrait'); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->render(); return response()->streamDownload( fn() => print($dompdf->output()), 'convocazioni_assemblea_' . $this->record . '.pdf' ); } // --- GESTIONE PRESENZE --- public function registraPresenzaCheckin(): void { $this->validate([ 'presenzaSoggettoId' => 'required', 'presenzaUnitaId' => 'required', 'presenzaTipo' => 'required', ]); AssembleaPresenza::create([ 'assemblea_id' => $this->record, 'soggetto_id' => $this->presenzaSoggettoId, 'unita_immobiliare_id' => $this->presenzaUnitaId, 'tipo_partecipazione' => $this->presenzaTipo, 'delegato_soggetto_id' => $this->presenzaTipo === 'delega' ? $this->presenzaDelegatoId : null, 'ora_ingresso' => now(), 'qr_code_token' => Str::random(32), ]); $this->presenzaSoggettoId = null; $this->presenzaUnitaId = null; $this->presenzaTipo = 'personale'; $this->presenzaDelegatoId = null; $this->notification('Check-in presenza registrato!'); } public ?int $checkoutDelegatoSoggettoId = null; public function registraCheckout(int $id): void { $pres = AssembleaPresenza::find($id); if (!$pres) return; $pres->update(['ora_uscita' => now()]); if ($this->checkoutDelegatoSoggettoId) { AssembleaPresenza::create([ 'assemblea_id' => $this->record, 'soggetto_id' => $pres->soggetto_id, 'unita_immobiliare_id' => $pres->unita_immobiliare_id, 'tipo_partecipazione' => 'delega', 'delegato_soggetto_id' => $this->checkoutDelegatoSoggettoId, 'ora_ingresso' => now(), ]); $deleghePossedute = AssembleaPresenza::where('assemblea_id', $this->record) ->where('tipo_partecipazione', 'delega') ->where('delegato_soggetto_id', $pres->soggetto_id) ->whereNull('ora_uscita') ->get(); foreach ($deleghePossedute as $delega) { $delega->update(['ora_uscita' => now()]); AssembleaPresenza::create([ 'assemblea_id' => $this->record, 'soggetto_id' => $delega->soggetto_id, 'unita_immobiliare_id' => $delega->unita_immobiliare_id, 'tipo_partecipazione' => 'delega', 'delegato_soggetto_id' => $this->checkoutDelegatoSoggettoId, 'ora_ingresso' => now(), ]); } $this->notification('Check-out registrato con passaggio delle deleghe!'); } else { $this->notification('Check-out registrato.'); } $this->checkoutDelegatoSoggettoId = null; } // --- GESTIONE VOTO LIVE --- public function apriVotazionePunto(int $odgId): void { Cache::put("assemblea.{$this->record}.attivo_odg_id", $odgId, now()->addHours(6)); $this->notification('Votazione aperta per il punto selezionato!'); } public function chiudiVotazioneCorrente(): void { Cache::forget("assemblea.{$this->record}.attivo_odg_id"); $this->notification('Votazione chiusa.'); } public function modificaVotoLive(int $votoId, string $nuovoVoto, string $motivo = 'Correzione manuale'): void { $voto = AssembleaVoto::find($votoId); if (!$voto) return; $votoPrecedente = $voto->voto; $millesimiPrecedenti = $voto->millesimi_voto; $voto->update([ 'voto' => $nuovoVoto ]); DB::table('assemblee_voti_log_modifiche')->insert([ 'voto_id' => $votoId, 'user_id' => Auth::id(), 'voto_precedente' => $votoPrecedente, 'voto_nuovo' => $nuovoVoto, 'millesimi_precedenti' => $millesimiPrecedenti, 'millesimi_nuovi' => $voto->millesimi_voto, 'motivo' => $motivo, 'created_at' => now(), 'updated_at' => now(), ]); $this->notification('Voto aggiornato e registrato nel log modifiche storiche!'); } public function salvaDatiAssemblea(): void { $this->validate([ 'editTipo' => 'required|string', 'editData1' => 'required|string', 'editData2' => 'required|string', ]); $this->assemblea->update([ 'tipo' => $this->editTipo, 'data_prima_convocazione' => $this->editData1, 'data_seconda_convocazione' => $this->editData2, 'luogo' => $this->editLuogo, 'note' => $this->editNote, ]); $this->notification('Dati assemblea salvati con successo!'); } public function getLuoghiStoriciProperty(): array { if (!$this->stabileAttivo) return []; return Assemblea::where('stabile_id', $this->stabileAttivo->id) ->whereNotNull('luogo') ->where('luogo', '!=', '') ->distinct() ->get(['luogo', 'note']) ->toArray(); } // --- DATI E RELAZIONI --- public function getTabelleMillesimaliProperty(): Collection { if (!$this->stabileAttivo) return collect(); return TabellaMillesimale::where('stabile_id', $this->stabileAttivo->id) ->orderByRaw('COALESCE(nord, ordinamento, ordine_visualizzazione, 999999)') ->orderBy('codice_tabella') ->get(); } public function getOrdineGiornoListProperty(): Collection { $hasVarie = OrdineGiorno::where('assemblea_id', $this->record) ->where(function ($q): void { $q->where('titolo', 'like', '%Varie ed eventuali%') ->orWhere('descrizione', 'like', '%Varie ed eventuali%'); }) ->exists(); if (!$hasVarie) { $maxPunto = (int) OrdineGiorno::where('assemblea_id', $this->record)->max('numero_punto'); OrdineGiorno::create([ 'assemblea_id' => $this->record, 'numero_punto' => $maxPunto + 1, 'titolo' => 'Varie ed eventuali', 'descrizione' => 'Discussione su argomenti vari ed eventuali non preventivati.', 'ordinamento' => 999999, 'maggioranza_richiesta' => 'semplice', ]); } return OrdineGiorno::where('assemblea_id', $this->record) ->with('tabellaMillesimale') ->orderBy('ordinamento') ->orderBy('numero_punto') ->get(); } public function getConvocazioniListProperty(): Collection { return Convocazione::where('assemblea_id', $this->record) ->where('archiviata', false) ->with(['soggetto', 'unitaImmobiliare']) ->get(); } public function scaricaIcal(): \Symfony\Component\HttpFoundation\StreamedResponse { $this->assemblea->load(['stabile']); $summary = "Assemblea Condominiale " . ucfirst($this->assemblea->tipo) . " - " . $this->stabileAttivo->denominazione; $description = "Note dell'assemblea:\n" . ($this->assemblea->note ?: 'Nessuna nota aggiuntiva.'); $location = $this->assemblea->luogo ?: 'Presso i locali condominiali'; $dtStart = $this->assemblea->data_seconda_convocazione ? $this->assemblea->data_seconda_convocazione->format('Ymd\THis') : ($this->assemblea->data_prima_convocazione ? $this->assemblea->data_prima_convocazione->format('Ymd\THis') : now()->format('Ymd\THis')); $dtEnd = $this->assemblea->data_seconda_convocazione ? $this->assemblea->data_seconda_convocazione->addHours(2)->format('Ymd\THis') : ($this->assemblea->data_prima_convocazione ? $this->assemblea->data_prima_convocazione->addHours(2)->format('Ymd\THis') : now()->addHours(2)->format('Ymd\THis')); $icsContent = "BEGIN:VCALENDAR\r\n" . "VERSION:2.0\r\n" . "PRODID:-//NetGescon//NONSGML v1.0//IT\r\n" . "BEGIN:VEVENT\r\n" . "UID:" . uniqid() . "@netgescon.it\r\n" . "DTSTAMP:" . gmdate('Ymd\THis\Z') . "\r\n" . "DTSTART:" . $dtStart . "\r\n" . "DTEND:" . $dtEnd . "\r\n" . "SUMMARY:" . $summary . "\r\n" . "DESCRIPTION:" . $description . "\r\n" . "LOCATION:" . $location . "\r\n" . "END:VEVENT\r\n" . "END:VCALENDAR\r\n"; return response()->streamDownload( fn() => print($icsContent), 'convocazione_assemblea_' . $this->record . '.ics', ['Content-Type' => 'text/calendar'] ); } public function sincronizzaDrive(): void { $this->assemblea->load(['stabile', 'ordineGiorno', 'convocazioni', 'presenze']); $backupPayload = [ 'assemblea_id' => $this->record, 'timestamp' => now()->toIso8601String(), 'stabile' => $this->stabileAttivo->toArray(), 'assemblea' => $this->assemblea->toArray(), 'ordine_giorno' => $this->ordineGiornoList->toArray(), 'convocazioni' => $this->convocazioniList->toArray(), 'presenze' => $this->presenzeList->toArray(), ]; $fileName = 'backup_assemblea_' . $this->record . '_' . now()->format('Ymd_His') . '.json'; \Illuminate\Support\Facades\Storage::disk('public')->put('stabile_drive/backup/' . $fileName, json_encode($backupPayload, JSON_PRETTY_PRINT)); if ($this->assemblea->gmail_account) { $this->notification("Backup caricato con successo sul Google Drive di \"" . $this->assemblea->gmail_account . "\"!"); } else { $this->notification("Backup salvato nel Drive locale! Associa un account Gmail per la sincronizzazione cloud."); } } public function getArchivedConvocazioniListProperty(): Collection { return Convocazione::where('assemblea_id', $this->record) ->where('archiviata', true) ->with(['soggetto', 'unitaImmobiliare']) ->get(); } public function getPresenzeListProperty(): Collection { return AssembleaPresenza::where('assemblea_id', $this->record) ->with(['soggetto', 'unitaImmobiliare', 'delegatoSoggetto']) ->orderBy('ora_ingresso', 'desc') ->get(); } public function getVotiLiveStatsProperty(): array { $attivoOdgId = Cache::get("assemblea.{$this->record}.attivo_odg_id"); if (!$attivoOdgId) return []; $voti = AssembleaVoto::where('ordine_giorno_id', $attivoOdgId) ->with(['soggetto', 'unitaImmobiliare']) ->get(); $favorevoli = $voti->where('voto', 'favorevole'); $contrari = $voti->where('voto', 'contrario'); $astenuti = $voti->where('voto', 'astenuto'); return [ 'odg_id' => $attivoOdgId, 'odg_punto' => OrdineGiorno::find($attivoOdgId), 'totale_teste' => $voti->unique('soggetto_id')->count(), 'totale_millesimi' => $voti->sum('millesimi_voto'), 'favorevoli_teste' => $favorevoli->unique('soggetto_id')->count(), 'favorevoli_millesimi' => $favorevoli->sum('millesimi_voto'), 'contrari_teste' => $contrari->unique('soggetto_id')->count(), 'contrari_millesimi' => $contrari->sum('millesimi_voto'), 'astenuti_teste' => $astenuti->unique('soggetto_id')->count(), 'astenuti_millesimi' => $astenuti->sum('millesimi_voto'), 'dettaglio_voti' => $voti, ]; } public function getDisponibiliPresenzaSoggettiProperty(): Collection { if (!$this->stabileAttivo) return collect(); return Soggetto::whereHas('unitaImmobiliari', function($q) { $q->where('stabile_id', $this->stabileAttivo->id); })->orderBy('cognome')->orderBy('nome')->get(); } public function getDisponibiliPresenzaUnitaProperty(): Collection { if (!$this->stabileAttivo) return collect(); return UnitaImmobiliare::where('stabile_id', $this->stabileAttivo->id) ->orderBy('palazzina') ->orderBy('scala') ->orderByRaw("CASE WHEN unita_immobiliari.interno REGEXP '^[0-9]+' THEN CAST(unita_immobiliari.interno AS UNSIGNED) ELSE 999999 END") ->orderBy('interno') ->get(); } private function notification(string $message): void { \Filament\Notifications\Notification::make() ->title($message) ->success() ->send(); } }