501 lines
17 KiB
PHP
Executable File
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);
|
|
}
|
|
}
|