852 lines
31 KiB
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();
|
|
}
|
|
}
|