netgescon-day0/app/Filament/Pages/Condomini/GestioneAssemblea.php

852 lines
31 KiB
PHP

<?php
namespace App\Filament\Pages\Condomini;
use App\Models\Assemblea;
use App\Models\Convocazione;
use App\Models\OrdineGiorno;
use App\Models\AssembleaVoto;
use App\Models\AssembleaPresenza;
use App\Models\UnitaImmobiliare;
use App\Models\Soggetto;
use App\Models\TabellaMillesimale;
use App\Models\Stabile;
use App\Models\User;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use UnitEnum;
use Livewire\WithFileUploads;
class GestioneAssemblea extends Page
{
use WithFileUploads;
protected static ?string $navigationLabel = 'Gestione Assemblea';
protected static ?string $title = 'Gestione Assemblea';
protected static ?string $slug = 'condomini/assemblee/{record}';
protected string $view = 'filament.pages.condomini.gestione-assemblea';
protected static bool $shouldRegisterNavigation = false;
public ?int $record = null; // ID dell'assemblea
public ?Assemblea $assemblea = null;
public ?Stabile $stabileAttivo = null;
// Gestione stato UI
public string $activeSubTab = 'odg'; // 'odg', 'convocazioni', 'presenze', 'voto_live'
// Form nuovo punto OdG
public ?int $odgNumeroPunto = null;
public ?string $odgTitolo = '';
public ?string $odgDescrizione = '';
public ?string $odgArticoloLegge = '';
public ?int $odgTabellaMillesimaleId = null;
public ?string $odgMaggioranza = 'semplice';
public ?string $odgRiferimentoLegge = '';
public $odgAllegati = []; // Upload file allegati
public $audioUpload; // Upload file audio della delibera
// Form nuova presenza
public ?int $presenzaSoggettoId = null;
public ?int $presenzaUnitaId = null;
public string $presenzaTipo = 'personale';
public ?int $presenzaDelegatoId = null;
// Modifica dati assemblea
public ?string $editTipo = 'ordinaria';
public ?string $editData1 = '';
public ?string $editData2 = '';
public ?string $editLuogo = '';
public ?string $editNote = '';
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& $user->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();
}
}