> */ public array $tabelle = []; /** * @var array> */ public array $righe = []; public ?array $tabellaInfo = null; protected static ?string $navigationLabel = 'Tabelle millesimali · Prospetto'; protected static ?string $title = 'Tabelle millesimali · Prospetto'; protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-chart-pie'; protected static UnitEnum|string|null $navigationGroup = 'Stabile'; protected static ?int $navigationSort = 41; protected static ?string $slug = 'condomini/tabelle-millesimali/prospetto'; protected string $view = 'filament.pages.condomini.tabelle-millesimali-prospetto'; public function inizializzaRighe(): void { if (! $this->stabileId || ! $this->tabellaId) { return; } if (! Schema::hasTable('dettaglio_millesimi')) { Notification::make() ->title('Tabella dettagli non disponibile') ->body('La tabella dettaglio_millesimi non esiste nel database.') ->danger() ->send(); return; } $unitaIds = UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->orderBy('id') ->pluck('id') ->map(fn($v) => (int) $v) ->all(); if ($unitaIds === []) { Notification::make() ->title('Nessuna unità') ->body('Non ci sono unità immobiliari nello stabile attivo.') ->warning() ->send(); return; } DB::transaction(function () use ($unitaIds): void { $existing = DettaglioMillesimi::query() ->where('tabella_millesimale_id', $this->tabellaId) ->whereIn('unita_immobiliare_id', $unitaIds) ->pluck('unita_immobiliare_id') ->map(fn($v) => (int) $v) ->all(); $missing = array_values(array_diff($unitaIds, $existing)); if ($missing === []) { return; } $userId = Auth::id(); $rows = array_map(function (int $unitaId) use ($userId): array { return [ 'tabella_millesimale_id' => (int) $this->tabellaId, 'unita_immobiliare_id' => $unitaId, 'millesimi' => 0, 'partecipa' => 1, 'created_by' => $userId, 'created_at' => now(), 'updated_at' => now(), ]; }, $missing); DB::table('dettaglio_millesimi')->insert($rows); }); $this->loadDettaglioTabella(); Notification::make() ->title('Righe inizializzate') ->success() ->send(); } public function saveRighe(): void { if (! $this->stabileId || ! $this->tabellaId) { return; } $validated = Validator::make( ['righe' => $this->righe], [ 'righe' => ['array'], 'righe.*.unita_id' => ['required', 'integer', 'min:1'], 'righe.*.millesimi' => ['required', 'numeric', 'min:0'], 'righe.*.partecipa' => ['nullable', 'boolean'], ] )->validate(); /** @var array> $rows */ $rows = (array) ($validated['righe'] ?? []); $unitaIds = array_values(array_unique(array_map(fn($r) => (int) ($r['unita_id'] ?? 0), $rows))); $unitaIds = array_values(array_filter($unitaIds, fn(int $v) => $v > 0)); if ($unitaIds === []) { return; } $allowedUnita = UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->whereIn('id', $unitaIds) ->pluck('id') ->map(fn($v) => (int) $v) ->all(); $allowedSet = array_fill_keys($allowedUnita, true); $now = now(); $userId = Auth::id(); DB::transaction(function () use ($rows, $allowedSet, $now, $userId): void { foreach ($rows as $r) { $unitaId = (int) ($r['unita_id'] ?? 0); if ($unitaId <= 0 || ! isset($allowedSet[$unitaId])) { continue; } $millesimi = (float) ($r['millesimi'] ?? 0); $partecipa = ! empty($r['partecipa']) ? 1 : 0; $exists = DettaglioMillesimi::query() ->where('tabella_millesimale_id', $this->tabellaId) ->where('unita_immobiliare_id', $unitaId) ->first(); if ($exists) { $exists->update([ 'millesimi' => $millesimi, 'partecipa' => $partecipa, ]); continue; } DettaglioMillesimi::query()->create([ 'tabella_millesimale_id' => (int) $this->tabellaId, 'unita_immobiliare_id' => $unitaId, 'millesimi' => $millesimi, 'partecipa' => $partecipa, 'created_by' => $userId, 'created_at' => $now, 'updated_at' => $now, ]); } }); $this->loadDettaglioTabella(); Notification::make() ->title('Millesimi salvati') ->success() ->send(); } public function getBackUrl(): ?string { $candidate = request()->query('back'); if (! is_string($candidate) || trim($candidate) === '') { return null; } $candidate = trim($candidate); // Allow relative URLs only. if (str_starts_with($candidate, '/')) { return $candidate; } $parts = parse_url($candidate); if (! is_array($parts)) { return null; } $host = $parts['host'] ?? null; if ($host && $host !== request()->getHost()) { return null; } $path = $parts['path'] ?? null; if (! is_string($path) || $path === '') { return null; } $query = isset($parts['query']) ? ('?' . $parts['query']) : ''; return $path . $query; } public static function canAccess(): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(): void { $user = Auth::user(); if (! $user instanceof User) { abort(403); } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make() ->title('Nessuno stabile disponibile') ->body('Seleziona uno stabile per vedere le tabelle millesimali.') ->warning() ->send(); return; } $this->stabileId = $stabileId; $this->annoGestione = AnnoGestioneContext::resolveActiveAnno($user); $this->loadTabelle(); $requested = request()->integer('tabella_id') ?: null; $this->tabellaId = $this->pickTabellaId($requested); $this->loadDettaglioTabella(); } protected function loadTabelle(): void { $q = TabellaMillesimale::query() ->perStabile($this->stabileId) ->when(Schema::hasColumn('tabelle_millesimali', 'anno_gestione') && $this->annoGestione, function ($qq) { $qq->where(function ($q2) { $q2->where('anno_gestione', $this->annoGestione)->orWhereNull('anno_gestione'); })->orderByRaw('CASE WHEN anno_gestione IS NULL THEN 1 ELSE 0 END'); }) ->ordinato(); $this->tabelle = $q ->get(['id', 'codice_tabella', 'nome_tabella', 'denominazione', 'tipo_tabella', 'tipo_calcolo', 'totale_millesimi', 'ordinamento', 'meta_legacy']) ->map(function (TabellaMillesimale $t) { $codice = $t->codice_tabella ?: ($t->nome_tabella ?: ('TAB ' . $t->id)); $nome = $t->denominazione ?: ($t->nome_tabella_millesimale ?: ($t->nome_tabella ?: 'Tabella')); $calcoloRaw = $this->resolveCalcoloRaw($t); $isConsumo = $this->isConsumoCalcolo($calcoloRaw); $calcoloLabel = $this->formatCalcoloLabel($calcoloRaw); $tipoLabel = $this->resolveTipoLabel($t, $calcoloRaw); $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); $meta = is_array($meta) ? $meta : []; $prev = $meta['tot_prev_euro'] ?? $meta['tot_prev'] ?? null; $cons = $meta['tot_cons_euro'] ?? $meta['tot_cons'] ?? null; return [ 'id' => (int) $t->id, 'codice' => $codice, 'nome' => $nome, 'tipo' => $tipoLabel, 'calcolo' => $calcoloLabel, 'is_consumo' => $isConsumo, 'totale' => is_numeric($t->totale_millesimi) ? (float) $t->totale_millesimi : null, 'ordinamento' => (int) ($t->ordinamento ?? 999999), 'is_bilanciata' => (bool) ($t->is_bilanciata ?? false), 'preventivo' => is_numeric($prev) ? (float) $prev : null, 'consuntivo' => is_numeric($cons) ? (float) $cons : null, ]; }) ->values() ->all(); } protected function pickTabellaId(?int $candidate): ?int { if ($candidate && collect($this->tabelle)->contains(fn($t) => (int) $t['id'] === $candidate)) { return $candidate; } $first = $this->tabelle[0]['id'] ?? null; return is_numeric($first) ? (int) $first : null; } protected function loadDettaglioTabella(): void { $this->righe = []; $this->tabellaInfo = null; if (! $this->tabellaId) { return; } $tabella = TabellaMillesimale::query() ->perStabile($this->stabileId) ->with(['dettagliMillesimali.unitaImmobiliare', 'dettagliMillesimali.unitaImmobiliare.palazzinaObj']) ->when(Schema::hasColumn('tabelle_millesimali', 'anno_gestione') && $this->annoGestione, function ($qq) { $qq->where(function ($q2) { $q2->where('anno_gestione', $this->annoGestione)->orWhereNull('anno_gestione'); }); }) ->whereKey($this->tabellaId) ->first(); if (! $tabella) { return; } $totale = (float) ($tabella->calcolaTotaleMillesimi() ?? 0); $codice = $tabella->codice_tabella ?: ($tabella->nome_tabella ?: ('TAB ' . $tabella->id)); $nome = $tabella->denominazione ?: ($tabella->nome_tabella_millesimale ?: ($tabella->nome_tabella ?: 'Tabella')); $this->tabellaInfo = [ 'id' => (int) $tabella->id, 'codice' => $codice, 'nome' => $nome, 'tipo' => $this->resolveTipoLabel($tabella, $this->resolveCalcoloRaw($tabella)), 'calcolo' => $this->formatCalcoloLabel($this->resolveCalcoloRaw($tabella)), 'is_consumo' => $this->isConsumoCalcolo($this->resolveCalcoloRaw($tabella)), 'totale' => $totale, 'is_bilanciata' => (bool) $tabella->isBilanciata(), 'preventivo' => $this->resolveLegacyImporto($tabella, 'tot_prev_euro', 'tot_prev'), 'consuntivo' => $this->resolveLegacyImporto($tabella, 'tot_cons_euro', 'tot_cons'), ]; $this->righe = $tabella->dettagliMillesimali ->filter(fn($d) => $d->unitaImmobiliare !== null) ->sortBy(function ($d) { $u = $d->unitaImmobiliare; return sprintf( '%s|%s|%s|%s|%010d', $u?->palazzina ?? '', $u?->scala ?? '', str_pad((string) ($u?->piano ?? 0), 6, '0', STR_PAD_LEFT), $u?->interno ?? '', (int) ($u?->id ?? 0) ); }) ->map(function ($d) use ($totale) { $u = $d->unitaImmobiliare; $millesimi = (float) ($d->millesimi ?? 0); $percentuale = $totale > 0 ? ($millesimi / $totale) * 100 : 0; return [ 'unita_id' => (int) ($u?->id ?? 0), 'codice_unita' => $u?->codice_unita ?? ($u?->codice_completo ?? null), 'denominazione' => $u?->denominazione, 'palazzina' => $u?->palazzina, 'scala' => $u?->scala, 'piano' => $u?->piano, 'interno' => $u?->interno, 'millesimi' => $millesimi, 'percentuale' => round($percentuale, 4), 'partecipa' => (bool) ($d->partecipa ?? true), ]; }) ->values() ->all(); } private function resolveCalcoloRaw(TabellaMillesimale $t): ?string { $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); $v = is_array($meta) ? ($meta['calcolo'] ?? null) : null; if (! $v) { $v = $t->tipo_calcolo; } $v = strtolower(trim((string) ($v ?? ''))); if ($v === '') { return null; } return match ($v) { 'm' => 'millesimi', 'a', 'c' => 'a_consumo', 'x' => 'conguagli', 'p' => 'personali', default => $v, }; } private function formatCalcoloLabel(?string $calcolo): ?string { $c = strtolower(trim((string) ($calcolo ?? ''))); if ($c === '') { return null; } return match ($c) { 'acqua', 'a', 'consumo', 'c', 'a_consumo' => 'A CONSUMO', 'millesimi', 'm' => 'MILLESIMI', 'conguagli', 'x' => 'CONGUAGLI', 'personali', 'p' => 'PERSONALI', 'parti' => 'PARTI', 'fisso' => 'FISSO', default => strtoupper($c), }; } private function resolveTipoLabel(TabellaMillesimale $t, ?string $calcoloRaw): ?string { $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); $meta = is_array($meta) ? $meta : []; $codice = strtoupper((string) ($t->codice_tabella ?? '')); if ($codice === 'ACQUA' || $this->isConsumoCalcolo($calcoloRaw)) { return 'A (Acqua)'; } $ors = isset($meta['tipo']) ? strtoupper((string) $meta['tipo']) : null; if ($ors) { return match ($ors) { 'O' => 'O (Ordinaria)', 'R' => 'R (Riscaldamento)', 'S' => 'S (Straordinaria)', default => $ors, }; } $tipologia = strtoupper((string) ($t->tipologia ?? '')); if (in_array($tipologia, ['O', 'R', 'S'], true)) { return match ($tipologia) { 'R' => 'R (Riscaldamento)', 'S' => 'S (Straordinaria)', default => 'O (Ordinaria)', }; } $tipo = strtolower((string) ($t->tipo_tabella ?? '')); if ($tipo === 'riscaldamento') return 'R (Riscaldamento)'; if ($tipo === 'straordinaria') return 'S (Straordinaria)'; if ($tipo === 'ordinaria') return 'O (Ordinaria)'; return $tipo !== '' ? strtoupper($tipo) : null; } private function resolveLegacyImporto(TabellaMillesimale $t, string $primary, string $fallback): ?float { $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); $meta = is_array($meta) ? $meta : []; $value = $meta[$primary] ?? $meta[$fallback] ?? null; return is_numeric($value) ? (float) $value : null; } private function isConsumoCalcolo(?string $calcolo): bool { $c = strtolower(trim((string) ($calcolo ?? ''))); return in_array($c, ['acqua', 'a', 'consumo', 'c', 'a_consumo'], true); } }