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

501 lines
17 KiB
PHP
Executable File

<?php
namespace App\Filament\Pages\Condomini;
use App\Models\DettaglioMillesimi;
use App\Models\TabellaMillesimale;
use App\Models\UnitaImmobiliare;
use App\Models\User;
use App\Support\AnnoGestioneContext;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
use UnitEnum;
class TabelleMillesimaliProspetto extends Page
{
protected int $stabileId = 0;
protected int $annoGestione = 0;
public ?int $tabellaId = null;
/**
* @var array<int, array<string, mixed>>
*/
public array $tabelle = [];
/**
* @var array<int, array<string, mixed>>
*/
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<int, array<string, mixed>> $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);
}
}