577 lines
19 KiB
PHP
Executable File
577 lines
19 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Filament\Pages\Condomini;
|
|
|
|
use App\Filament\Pages\UnitaImmobiliarePage;
|
|
use App\Models\Palazzina;
|
|
use App\Models\RubricaRuolo;
|
|
use App\Models\UnitaImmobiliare;
|
|
use App\Models\UnitaTipologia;
|
|
use App\Models\User;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use UnitEnum;
|
|
|
|
class PalazzineInterni extends Page
|
|
{
|
|
protected int $stabileId = 0;
|
|
|
|
public ?int $palazzinaId = null;
|
|
|
|
public ?int $unitaId = null;
|
|
|
|
/**
|
|
* @var array<int, array<string, mixed>>
|
|
*/
|
|
public array $palazzine = [];
|
|
|
|
/**
|
|
* @var array<string, array<int, array<string, mixed>>> [scala => [piano => units[]]]
|
|
*/
|
|
public array $unitaPerScalaPiano = [];
|
|
|
|
/**
|
|
* @var array<int, array<string, mixed>>
|
|
*/
|
|
public array $millesimiPerTabella = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
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<string, string>
|
|
*/
|
|
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;
|
|
}
|
|
}
|