> */ public array $palazzine = []; /** * @var array>> [scala => [piano => units[]]] */ public array $unitaPerScalaPiano = []; /** * @var array> */ public array $millesimiPerTabella = []; /** * @var array */ public array $selectedUnitaIds = []; public ?string $bulkScala = null; public ?int $bulkPiano = null; public ?int $bulkTipologiaId = null; public ?string $bulkCategoriaCatastale = null; protected static ?string $navigationLabel = 'Palazzine · Interni'; protected static ?string $title = 'Palazzine · Interni'; protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-office-2'; protected static UnitEnum|string|null $navigationGroup = 'Stabile'; protected static ?int $navigationSort = 31; protected static ?string $slug = 'condomini/palazzine/interni'; protected string $view = 'filament.pages.condomini.palazzine-interni'; 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); } $requestedStabileId = request()->integer('stabile_id'); if ($requestedStabileId > 0) { StabileContext::setActiveStabileId($user, (int) $requestedStabileId); } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make() ->title('Nessuno stabile disponibile') ->body('Seleziona uno stabile per vedere palazzine e unità.') ->warning() ->send(); return; } $this->stabileId = $stabileId; $this->loadPalazzine(); $requestedPalazzinaId = request()->integer('palazzina_id') ?: null; $this->palazzinaId = $this->pickPalazzinaId($requestedPalazzinaId); $requestedUnitaId = request()->integer('unita_id') ?: null; $this->unitaId = $requestedUnitaId; $this->loadUnitaGrid(); $this->loadMillesimiUnita(); } public function hydrate(): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if ($stabileId) { $this->stabileId = (int) $stabileId; } } protected function loadPalazzine(): void { $this->palazzine = Palazzina::query() ->where('stabile_id', $this->stabileId) ->orderBy('codice_palazzina') ->orderBy('denominazione') ->get(['id', 'codice_palazzina', 'denominazione', 'numero_scale', 'numero_piani_fuori_terra', 'numero_piani_interrati', 'ha_piano_terra']) ->map(function (Palazzina $p) { $codice = $p->codice_palazzina ?: ('PAL ' . $p->id); $nome = $p->denominazione ?: 'Palazzina'; return [ 'id' => (int) $p->id, 'codice' => $codice, 'nome' => $nome, 'scale' => (int) ($p->numero_scale ?? 0), 'piani_fuori_terra' => (int) ($p->numero_piani_fuori_terra ?? 0), 'piani_interrati' => (int) ($p->numero_piani_interrati ?? 0), 'ha_piano_terra' => (bool) ($p->ha_piano_terra ?? false), ]; }) ->values() ->all(); } protected function pickPalazzinaId(?int $candidate): ?int { if ($candidate && collect($this->palazzine)->contains(fn($p) => (int) $p['id'] === $candidate)) { return $candidate; } $first = $this->palazzine[0]['id'] ?? null; return is_numeric($first) ? (int) $first : null; } protected function loadUnitaGrid(): void { $this->unitaPerScalaPiano = []; if (! $this->palazzinaId) { return; } $unita = UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->where('palazzina_id', $this->palazzinaId) ->orderBy('scala') ->orderBy('piano') ->orderBy('id') ->get(['id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno']); $ownersByUnita = []; $tenantsByUnita = []; // Preferisci Rubrica (nuovo archivio) se disponibile. try { $ids = $unita->pluck('id')->map(fn($v) => (int) $v)->values()->all(); if (! empty($ids) && DB::getSchemaBuilder()->hasTable('rubrica_ruoli')) { $today = now()->toDateString(); $ruoli = RubricaRuolo::query() ->with(['contatto']) ->where('stabile_id', $this->stabileId) ->whereIn('unita_immobiliare_id', $ids) ->where('is_attivo', true) ->where(function (Builder $q) use ($today) { $q->whereNull('data_inizio')->orWhere('data_inizio', '<=', $today); }) ->where(function (Builder $q) use ($today) { $q->whereNull('data_fine')->orWhere('data_fine', '>=', $today); }) ->orderByDesc('is_preferito') ->orderByDesc('data_inizio') ->orderByDesc('id') ->limit(5000) ->get(); foreach ($ruoli as $ruolo) { $unitaId = (int) ($ruolo->unita_immobiliare_id ?? 0); if (! $unitaId) { continue; } $label = (string) ($ruolo->contatto?->nome_completo ?? ''); $label = trim($label); if ($label === '') { continue; } $roleKey = strtolower(trim((string) ($ruolo->ruolo_standard ?? ''))); if ($roleKey === 'inquilino') { $tenantsByUnita[$unitaId] ??= $label; } else { $ownersByUnita[$unitaId] ??= $label; } } } } catch (\Throwable $e) { } // Fallback legacy (se presente): proprieta+soggetti try { $ids = $unita->pluck('id')->map(fn($v) => (int) $v)->values()->all(); if (! empty($ids) && DB::getSchemaBuilder()->hasTable('proprieta')) { $rows = DB::table('proprieta') ->join('soggetti', 'soggetti.id', '=', 'proprieta.soggetto_id') ->whereIn('proprieta.unita_immobiliare_id', $ids) ->select([ 'proprieta.unita_immobiliare_id as unita_id', 'proprieta.tipo_diritto as tipo_diritto', 'soggetti.ragione_sociale as ragione_sociale', 'soggetti.cognome as cognome', 'soggetti.nome as nome', ]) ->get(); foreach ($rows as $row) { $unitaId = (int) ($row->unita_id ?? 0); if (! $unitaId) { continue; } $label = trim((string) ($row->ragione_sociale ?? '')); if ($label === '') { $label = trim(((string) ($row->cognome ?? '')) . ' ' . ((string) ($row->nome ?? ''))); } if ($label === '') { continue; } $tipo = (string) ($row->tipo_diritto ?? ''); if ($tipo === 'locazione') { $tenantsByUnita[$unitaId] ??= $label; } else { $ownersByUnita[$unitaId] ??= $label; } } } } catch (\Throwable $e) { } $grouped = []; foreach ($unita as $u) { $scala = (string) ($u->scala ?? '-'); $piano = is_null($u->piano) ? 0 : (int) $u->piano; $grouped[$scala] ??= []; $grouped[$scala][$piano] ??= []; $grouped[$scala][$piano][] = [ 'id' => (int) $u->id, 'codice' => $u->codice_unita ?: ('ID ' . $u->id), 'nome' => $u->denominazione ?: 'Unità', 'scala' => $scala, 'piano' => $piano, 'interno' => $u->interno !== null ? (string) $u->interno : '—', 'proprietario' => $ownersByUnita[(int) $u->id] ?? null, 'inquilino' => $tenantsByUnita[(int) $u->id] ?? null, ]; } // Ordina gli interni in modo naturale (1,2,10) per ogni scala/piano foreach ($grouped as $scala => $piani) { foreach ($piani as $piano => $units) { usort($units, function (array $a, array $b): int { $ka = $this->internoSortKey($a['interno'] ?? null); $kb = $this->internoSortKey($b['interno'] ?? null); if ($ka[0] !== $kb[0]) { return strcmp($ka[0], $kb[0]); } if ($ka[1] !== $kb[1]) { return $ka[1] <=> $kb[1]; } if ($ka[2] !== $kb[2]) { return strnatcasecmp($ka[2], $kb[2]); } return ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0)); }); $grouped[$scala][$piano] = $units; } } // Ordina piani dall'alto al basso per ogni scala foreach ($grouped as $scala => $piani) { krsort($piani); $grouped[$scala] = $piani; } ksort($grouped); $this->unitaPerScalaPiano = $grouped; // Se unità selezionata non appartiene alla palazzina corrente, reset if ($this->unitaId) { $exists = $unita->contains(fn(UnitaImmobiliare $u) => (int) $u->id === (int) $this->unitaId); if (! $exists) { $this->unitaId = null; } } } /** * @return array */ public function getTipologieOptions(): array { try { if (! DB::getSchemaBuilder()->hasTable('unita_tipologie')) { return []; } return UnitaTipologia::query() ->orderBy('categoria') ->orderBy('nome') ->get(['id', 'codice', 'nome']) ->mapWithKeys(function (UnitaTipologia $t): array { $cod = trim((string) ($t->codice ?? '')); $name = trim((string) ($t->nome ?? '')); $label = trim(($cod !== '' ? ($cod . ' - ') : '') . ($name !== '' ? $name : ('Tipologia #' . (int) $t->id))); return [(string) $t->id => $label]; }) ->all(); } catch (\Throwable) { return []; } } public function bulkSetTipologiaCatasto(): void { $ids = is_array($this->selectedUnitaIds ?? null) ? array_values(array_filter(array_map('intval', $this->selectedUnitaIds), fn(int $v) => $v > 0)) : []; if ($ids === []) { Notification::make()->title('Nessuna unità selezionata')->warning()->send(); return; } $tipologiaId = $this->bulkTipologiaId ? (int) $this->bulkTipologiaId : null; $cat = is_string($this->bulkCategoriaCatastale) ? trim($this->bulkCategoriaCatastale) : ''; $cat = $cat !== '' ? $cat : null; if (! $tipologiaId && ! $cat) { Notification::make()->title('Nessun valore da applicare')->warning()->send(); return; } $payload = []; if ($tipologiaId) { $payload['tipologia_id'] = $tipologiaId; } if ($cat) { $payload['categoria_catastale'] = $cat; } UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->whereIn('id', $ids) ->update($payload); Notification::make()->title('Unità aggiornate')->success()->send(); $this->loadUnitaGrid(); $this->loadMillesimiUnita(); } /** * @return array{0:string,1:int,2:string} */ protected function internoSortKey(?string $interno): array { $s = trim((string)($interno ?? '')); if ($s === '' || $s === '—') { return ['~', PHP_INT_MAX, '']; } $u = strtoupper($s); $m = null; if (preg_match('/^([A-Z]+)\s*[-\/]\s*(\d+)(.*)$/', $u, $m)) { return [$m[1], (int)$m[2], trim((string)($m[3] ?? ''))]; } if (preg_match('/^(\d+)(.*)$/', $u, $m)) { return ['', (int)$m[1], trim((string)($m[2] ?? ''))]; } return [$u, PHP_INT_MAX, $u]; } public function bulkSetPiano(): void { if (! $this->stabileId || ! $this->palazzinaId) { Notification::make()->title('Seleziona una palazzina')->warning()->send(); return; } $ids = array_values(array_unique(array_filter(array_map('intval', $this->selectedUnitaIds)))); if (empty($ids)) { Notification::make()->title('Nessuna unità selezionata')->warning()->send(); return; } if ($this->bulkPiano === null) { Notification::make()->title('Imposta un piano')->warning()->send(); return; } $updated = UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->where('palazzina_id', $this->palazzinaId) ->whereIn('id', $ids) ->update([ 'piano' => $this->bulkPiano, 'updated_at' => now(), ]); $this->selectedUnitaIds = []; $this->loadUnitaGrid(); Notification::make() ->title('Piano associato') ->body("Aggiornate {$updated} unità.") ->success() ->send(); } public function bulkSetScala(): void { if (! $this->stabileId || ! $this->palazzinaId) { Notification::make()->title('Seleziona una palazzina')->warning()->send(); return; } $ids = array_values(array_unique(array_filter(array_map('intval', $this->selectedUnitaIds)))); if (empty($ids)) { Notification::make()->title('Nessuna unità selezionata')->warning()->send(); return; } $scala = is_string($this->bulkScala) ? trim($this->bulkScala) : ''; if ($scala === '') { Notification::make()->title('Imposta una scala')->warning()->send(); return; } $updated = UnitaImmobiliare::query() ->where('stabile_id', $this->stabileId) ->where('palazzina_id', $this->palazzinaId) ->whereIn('id', $ids) ->update([ 'scala' => $scala, 'updated_at' => now(), ]); $this->selectedUnitaIds = []; $this->loadUnitaGrid(); Notification::make() ->title('Scala associata') ->body("Aggiornate {$updated} unità.") ->success() ->send(); } protected function loadMillesimiUnita(): void { $this->millesimiPerTabella = []; if (! $this->unitaId) { return; } $unita = UnitaImmobiliare::with(['dettagliMillesimi.tabellaMillesimale']) ->where('stabile_id', $this->stabileId) ->whereKey($this->unitaId) ->first(); if (! $unita) { return; } $rows = $unita->dettagliMillesimi ->filter(fn($d) => $d->tabellaMillesimale !== null) ->sortBy(function ($d) { $tabella = $d->tabellaMillesimale; $tipo = (string) ($tabella?->tipo_tabella ?? ($tabella?->tipo ?? '')); return sprintf('%s|%09d', $tipo, (int) ($tabella?->ordinamento ?? 999999)); }) ->map(function ($d) { $tabella = $d->tabellaMillesimale; $tipo = (string) ($tabella?->tipo_tabella ?? ($tabella?->tipo ?? '')); if ($tipo === '') { $tipo = 'altro'; } return [ 'tipo' => $tipo, 'tabella_id' => (int) ($tabella?->id ?? 0), 'codice' => $tabella?->codice_tabella ?? $tabella?->nome_tabella ?? 'TAB', 'nome' => $tabella?->denominazione ?? $tabella?->nome_tabella_millesimale ?? 'Tabella', 'millesimi' => (float) ($d->millesimi ?? 0), ]; }) ->values() ->all(); $this->millesimiPerTabella = $rows; } public function getUnitaPageUrl(): ?string { if (! $this->unitaId) { return null; } $base = UnitaImmobiliarePage::getUrl(panel: 'admin-filament'); $back = urlencode((string) request()->getRequestUri()); return $base . '?unita_id=' . (int) $this->unitaId . '&back=' . $back; } }