1387 lines
53 KiB
PHP
1387 lines
53 KiB
PHP
<?php
|
||
namespace App\Filament\Pages\Contabilita;
|
||
|
||
use App\Filament\Pages\Condomini\TabelleMillesimaliProspetto;
|
||
use App\Filament\Pages\Contabilita\VoceSpesaMastrino;
|
||
use App\Models\GestioneContabile;
|
||
use App\Models\RipartizioneSpeseInquilini;
|
||
use App\Models\TabellaMillesimale;
|
||
use App\Models\User;
|
||
use App\Models\VoceSpesa;
|
||
use App\Support\AnnoGestioneContext;
|
||
use App\Support\StabileContext;
|
||
use BackedEnum;
|
||
use Filament\Actions\Action;
|
||
use Filament\Actions\BulkAction;
|
||
use Filament\Forms\Components\Select;
|
||
use Filament\Forms\Components\Toggle;
|
||
use Filament\Notifications\Notification;
|
||
use Filament\Pages\Page;
|
||
use Filament\Tables\Columns\IconColumn;
|
||
use Filament\Tables\Columns\Summarizers\Sum;
|
||
use Filament\Tables\Columns\TextColumn;
|
||
use Filament\Tables\Concerns\InteractsWithTable;
|
||
use Filament\Tables\Contracts\HasTable;
|
||
use Filament\Tables\Filters\SelectFilter;
|
||
use Filament\Tables\Grouping\Group;
|
||
use Filament\Tables\Table;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Illuminate\Support\Str;
|
||
use UnitEnum;
|
||
|
||
class VociSpesaArchivio extends Page implements HasTable
|
||
{
|
||
use InteractsWithTable;
|
||
|
||
protected static ?string $navigationLabel = 'Voci di spesa';
|
||
|
||
protected static ?string $title = 'Voci di spesa';
|
||
|
||
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-clipboard-document-list';
|
||
|
||
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
|
||
|
||
protected static ?int $navigationSort = 40;
|
||
|
||
protected static ?string $slug = 'contabilita/voci-spesa';
|
||
|
||
protected string $view = 'filament.pages.contabilita.voci-spesa-prospetto';
|
||
|
||
public string $tipoGestioneTab = 'ordinaria';
|
||
|
||
public ?int $gestioneContabileId = null;
|
||
|
||
public string $presetRipartizione = 'manuale';
|
||
|
||
public ?int $tabellaFocusId = null;
|
||
|
||
public bool $hideFirstTwoTabelle = false;
|
||
|
||
/** @var array<string, TabellaMillesimale|null> */
|
||
protected array $tabellaAnnoCache = [];
|
||
|
||
public static function canAccess(): bool
|
||
{
|
||
$user = Auth::user();
|
||
|
||
if (! $user instanceof User) {
|
||
return false;
|
||
}
|
||
|
||
if ($user->hasAnyRole(['super-admin', 'admin'])) {
|
||
return true;
|
||
}
|
||
|
||
return $user->can('contabilita.voci-spesa');
|
||
}
|
||
|
||
public function mount(): void
|
||
{
|
||
$tab = (string) request()->query('tab', 'ordinaria');
|
||
$this->tipoGestioneTab = in_array($tab, ['ordinaria', 'acqua', 'riscaldamento', 'straordinaria'], true) ? $tab : 'ordinaria';
|
||
|
||
$this->gestioneContabileId = $this->resolveGestioneContabileId();
|
||
|
||
$preset = (string) request()->query('preset', 'manuale');
|
||
$this->presetRipartizione = in_array($preset, ['manuale', 'confedilizia'], true) ? $preset : 'manuale';
|
||
|
||
$focus = request()->query('tabella');
|
||
$this->tabellaFocusId = is_numeric($focus) ? (int) $focus : null;
|
||
if (! $this->tabellaFocusId || $this->tabellaFocusId <= 0) {
|
||
$this->tabellaFocusId = $this->getFirstTabellaIdForFocus();
|
||
}
|
||
|
||
$this->mountInteractsWithTable();
|
||
}
|
||
|
||
protected function getHeaderActions(): array
|
||
{
|
||
return [
|
||
Action::make('tabelle')
|
||
->label('Tabelle millesimali')
|
||
->icon('heroicon-o-rectangle-stack')
|
||
->url(TabelleMillesimaliProspetto::getUrl(panel: 'admin-filament')),
|
||
|
||
Action::make('importaVociDaAnno')
|
||
->label('Importa voci da anno')
|
||
->icon('heroicon-o-arrow-down-tray')
|
||
->modalHeading('Importa voci per la gestione attiva')
|
||
->form([
|
||
Select::make('anno_sorgente')
|
||
->label('Anno sorgente')
|
||
->options(function (): array {
|
||
$y = (int) now()->year;
|
||
$out = [];
|
||
for ($i = 0; $i < 8; $i++) {
|
||
$out[(string) ($y - $i)] = (string) ($y - $i);
|
||
}
|
||
return $out;
|
||
})
|
||
->default(function (): string {
|
||
$user = Auth::user();
|
||
$anno = $user instanceof User ? AnnoGestioneContext::resolveActiveAnno($user) : (int) now()->year;
|
||
return (string) max(2000, $anno - 1);
|
||
})
|
||
->required(),
|
||
|
||
Toggle::make('aggiorna_esistenti')
|
||
->label('Aggiorna anche le voci già presenti (ricollega tabella se serve)')
|
||
->default(true),
|
||
])
|
||
->action(function (array $data): void {
|
||
$annoSorgente = is_numeric($data['anno_sorgente'] ?? null) ? (int) $data['anno_sorgente'] : 0;
|
||
$aggiorna = (bool) ($data['aggiorna_esistenti'] ?? true);
|
||
$this->importaVociPerAnno($annoSorgente, $aggiorna);
|
||
}),
|
||
|
||
Action::make('ricollegaTabelleAnno')
|
||
->label('Ricollega tabelle (anno)')
|
||
->icon('heroicon-o-link')
|
||
->modalHeading('Ricollega tabelle all’anno attivo')
|
||
->requiresConfirmation()
|
||
->action(function (): void {
|
||
$this->ricollegaTabellePerAnnoAttivo();
|
||
}),
|
||
];
|
||
}
|
||
|
||
private function resolveGestioneIdForAnnoTipo(int $stabileId, int $anno, string $tipo): ?int
|
||
{
|
||
if (! Schema::hasTable('gestioni_contabili')) {
|
||
return null;
|
||
}
|
||
|
||
$q = GestioneContabile::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('anno_gestione', $anno)
|
||
->where('tipo_gestione', $tipo)
|
||
->orderByDesc('gestione_attiva')
|
||
->orderByDesc('id');
|
||
|
||
$id = $q->value('id');
|
||
return is_numeric($id) ? (int) $id : null;
|
||
}
|
||
|
||
private function mapTabellaToAnno(?int $sourceTabellaId, int $stabileId, int $targetAnno, string $tipoGestione): ?int
|
||
{
|
||
if (! $sourceTabellaId || $sourceTabellaId <= 0) {
|
||
return null;
|
||
}
|
||
|
||
/** @var TabellaMillesimale|null $source */
|
||
$source = TabellaMillesimale::query()->where('stabile_id', $stabileId)->whereKey($sourceTabellaId)->first();
|
||
if (! $source) {
|
||
return null;
|
||
}
|
||
|
||
$codice = trim((string) ($source->codice_tabella ?? ''));
|
||
if ($codice === '') {
|
||
return $sourceTabellaId;
|
||
}
|
||
|
||
$hasAnno = Schema::hasColumn('tabelle_millesimali', 'anno_gestione');
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
|
||
$q = TabellaMillesimale::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('codice_tabella', $codice);
|
||
|
||
if ($hasLegacyTipo) {
|
||
$q->where('tipo', $tipoGestione);
|
||
}
|
||
|
||
if ($hasAnno) {
|
||
$q->where('anno_gestione', $targetAnno);
|
||
}
|
||
|
||
$id = $q->orderBy('ordine_visualizzazione')
|
||
->orderBy('ordinamento')
|
||
->orderBy('id')
|
||
->value('id');
|
||
|
||
return is_numeric($id) ? (int) $id : $sourceTabellaId;
|
||
}
|
||
|
||
private function importaVociPerAnno(int $annoSorgente, bool $aggiornaEsistenti = true): void
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return;
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId) {
|
||
Notification::make()->title('Seleziona uno stabile')->warning()->send();
|
||
return;
|
||
}
|
||
|
||
$tipo = $this->normalizeTipoGestione($this->tipoGestioneTab ?: 'ordinaria');
|
||
$annoTarget = AnnoGestioneContext::resolveActiveAnno($user);
|
||
|
||
$gestioneTargetId = $this->gestioneContabileId ?: $this->resolveGestioneContabileId();
|
||
if (! $gestioneTargetId) {
|
||
Notification::make()->title('Gestione target non trovata')->danger()->send();
|
||
return;
|
||
}
|
||
|
||
$gestioneSourceId = $this->resolveGestioneIdForAnnoTipo($stabileId, $annoSorgente, $tipo);
|
||
if (! $gestioneSourceId) {
|
||
Notification::make()
|
||
->title('Gestione sorgente non trovata')
|
||
->body('Non esiste una gestione per ' . $annoSorgente . ' (' . strtoupper($tipo) . ').')
|
||
->warning()
|
||
->send();
|
||
return;
|
||
}
|
||
|
||
$sourceVoci = VoceSpesa::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('gestione_contabile_id', $gestioneSourceId)
|
||
->orderBy('ordinamento')
|
||
->orderBy('codice')
|
||
->get();
|
||
|
||
if ($sourceVoci->isEmpty()) {
|
||
Notification::make()->title('Nessuna voce da importare')->warning()->send();
|
||
return;
|
||
}
|
||
|
||
$existing = VoceSpesa::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('gestione_contabile_id', $gestioneTargetId)
|
||
->pluck('id', 'codice')
|
||
->all();
|
||
|
||
$created = 0;
|
||
$updated = 0;
|
||
$skipped = 0;
|
||
|
||
DB::transaction(function () use (
|
||
$sourceVoci,
|
||
$existing,
|
||
$stabileId,
|
||
$gestioneTargetId,
|
||
$annoTarget,
|
||
$tipo,
|
||
$aggiornaEsistenti,
|
||
&$created,
|
||
&$updated,
|
||
&$skipped,
|
||
): void {
|
||
foreach ($sourceVoci as $src) {
|
||
if (! $src instanceof VoceSpesa) {
|
||
continue;
|
||
}
|
||
|
||
$codice = trim((string) ($src->codice ?? ''));
|
||
if ($codice === '') {
|
||
$skipped++;
|
||
continue;
|
||
}
|
||
|
||
$targetTabellaId = $this->mapTabellaToAnno(
|
||
is_numeric($src->tabella_millesimale_default_id ?? null) ? (int) $src->tabella_millesimale_default_id : null,
|
||
$stabileId,
|
||
$annoTarget,
|
||
$tipo,
|
||
);
|
||
|
||
if (isset($existing[$codice])) {
|
||
if (! $aggiornaEsistenti) {
|
||
$skipped++;
|
||
continue;
|
||
}
|
||
|
||
$targetId = (int) $existing[$codice];
|
||
$target = VoceSpesa::query()->whereKey($targetId)->first();
|
||
if (! $target) {
|
||
$skipped++;
|
||
continue;
|
||
}
|
||
|
||
$patch = [];
|
||
if (Schema::hasColumn('voci_spesa', 'tabella_millesimale_default_id')) {
|
||
if ((int) ($target->tabella_millesimale_default_id ?? 0) !== (int) ($targetTabellaId ?? 0)) {
|
||
$patch['tabella_millesimale_default_id'] = $targetTabellaId;
|
||
}
|
||
}
|
||
if (Schema::hasColumn('voci_spesa', 'tipo_gestione')) {
|
||
if ((string) ($target->tipo_gestione ?? '') !== $tipo) {
|
||
$patch['tipo_gestione'] = $tipo;
|
||
}
|
||
}
|
||
|
||
if ($patch !== []) {
|
||
$target->update($patch);
|
||
$updated++;
|
||
} else {
|
||
$skipped++;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// Crea nuova voce per gestione target (preserva codice).
|
||
$data = $src->toArray();
|
||
unset($data['id'], $data['id_voce'], $data['created_at'], $data['updated_at']);
|
||
$data['stabile_id'] = $stabileId;
|
||
$data['gestione_contabile_id'] = $gestioneTargetId;
|
||
if (Schema::hasColumn('voci_spesa', 'tipo_gestione')) {
|
||
$data['tipo_gestione'] = $tipo;
|
||
}
|
||
if (Schema::hasColumn('voci_spesa', 'tabella_millesimale_default_id')) {
|
||
$data['tabella_millesimale_default_id'] = $targetTabellaId;
|
||
}
|
||
|
||
VoceSpesa::query()->create($data);
|
||
$created++;
|
||
}
|
||
});
|
||
|
||
Notification::make()
|
||
->title('Import voci completato')
|
||
->body('Create: ' . $created . ' · Aggiornate: ' . $updated . ' · Saltate: ' . $skipped)
|
||
->success()
|
||
->send();
|
||
|
||
$this->dispatch('$refresh');
|
||
}
|
||
|
||
private function ricollegaTabellePerAnnoAttivo(): void
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return;
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId) {
|
||
Notification::make()->title('Seleziona uno stabile')->warning()->send();
|
||
return;
|
||
}
|
||
|
||
$annoTarget = AnnoGestioneContext::resolveActiveAnno($user);
|
||
$tipo = $this->normalizeTipoGestione($this->tipoGestioneTab ?: 'ordinaria');
|
||
$gestioneTargetId = $this->gestioneContabileId ?: $this->resolveGestioneContabileId();
|
||
if (! $gestioneTargetId) {
|
||
Notification::make()->title('Gestione non trovata')->danger()->send();
|
||
return;
|
||
}
|
||
|
||
if (! Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
||
Notification::make()->title('Anno tabelle non attivo')->body('La colonna anno_gestione non è presente sulle tabelle.')->warning()->send();
|
||
return;
|
||
}
|
||
|
||
$voci = VoceSpesa::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('gestione_contabile_id', $gestioneTargetId)
|
||
->whereNotNull('tabella_millesimale_default_id')
|
||
->with('tabellaMillesimaleDefault')
|
||
->get();
|
||
|
||
$updated = 0;
|
||
DB::transaction(function () use ($voci, $stabileId, $annoTarget, $tipo, &$updated): void {
|
||
foreach ($voci as $voce) {
|
||
if (! $voce instanceof VoceSpesa) {
|
||
continue;
|
||
}
|
||
|
||
$t = $voce->tabellaMillesimaleDefault;
|
||
if (! $t) {
|
||
continue;
|
||
}
|
||
|
||
$tabAnno = is_numeric($t->anno_gestione ?? null) ? (int) $t->anno_gestione : null;
|
||
if ($tabAnno === null || $tabAnno === $annoTarget) {
|
||
continue;
|
||
}
|
||
|
||
$newId = $this->mapTabellaToAnno((int) $t->id, $stabileId, $annoTarget, $tipo);
|
||
if ($newId && $newId !== (int) $t->id) {
|
||
$voce->update(['tabella_millesimale_default_id' => $newId]);
|
||
$updated++;
|
||
}
|
||
}
|
||
});
|
||
|
||
Notification::make()
|
||
->title('Ricollegamento completato')
|
||
->body('Voci aggiornate: ' . $updated)
|
||
->success()
|
||
->send();
|
||
|
||
$this->dispatch('$refresh');
|
||
}
|
||
|
||
private function getFirstTabellaIdForFocus(): ?int
|
||
{
|
||
$opts = $this->getTabelleMillesimaliOptions();
|
||
if (empty($opts)) {
|
||
return null;
|
||
}
|
||
|
||
$firstKey = array_key_first($opts);
|
||
return is_numeric($firstKey) ? (int) $firstKey : null;
|
||
}
|
||
|
||
/** @return array<int, string> */
|
||
public function getTabelleMillesimaliOptions(): array
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return [];
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId) {
|
||
return [];
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab ?: 'ordinaria';
|
||
$tipo = $this->normalizeTipoGestione($tipoTab);
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
$hasAnno = Schema::hasColumn('tabelle_millesimali', 'anno_gestione');
|
||
$anno = AnnoGestioneContext::resolveActiveAnno($user);
|
||
|
||
$q = TabellaMillesimale::query()
|
||
->where('stabile_id', $stabileId)
|
||
->orderBy('ordine_visualizzazione')
|
||
->orderBy('ordinamento')
|
||
->orderBy('codice_tabella')
|
||
->orderBy('nome_tabella');
|
||
|
||
if ($hasAnno) {
|
||
$q->where('anno_gestione', $anno);
|
||
}
|
||
|
||
if ($hasLegacyTipo) {
|
||
$q->whereIn('tipo', $this->getLegacyTipoValues($tipo));
|
||
}
|
||
|
||
return $q
|
||
->get(['id', 'codice_tabella', 'denominazione', 'nome_tabella'])
|
||
->mapWithKeys(function (TabellaMillesimale $t): array {
|
||
$code = trim((string) ($t->codice_tabella ?? ''));
|
||
$name = trim((string) ($t->denominazione ?? ''));
|
||
if ($name === '') {
|
||
$name = trim((string) ($t->nome_tabella ?? ''));
|
||
}
|
||
if ($name === '') {
|
||
$name = trim((string) ($t->nome_tabella_millesimale ?? ''));
|
||
}
|
||
$label = $this->formatTabellaLabel($code, $name, $t->id);
|
||
return [(int) $t->id => $label];
|
||
})
|
||
->all();
|
||
}
|
||
|
||
public function updatedTabellaFocusId($value): void
|
||
{
|
||
$id = is_numeric($value) ? (int) $value : null;
|
||
$this->tabellaFocusId = $id && $id > 0 ? $id : null;
|
||
}
|
||
|
||
/** @return array{label: string, count: int, totale_prev: float, totale_cons: float, voci: array<int, array{codice: string, descrizione: string, prev: float, cons: float, prop: float, inq: float, attiva: bool}>} */
|
||
public function getDettaglioTabellaFocus(): array
|
||
{
|
||
$id = (int) ($this->tabellaFocusId ?? 0);
|
||
$opts = $this->getTabelleMillesimaliOptions();
|
||
$label = $id > 0 && array_key_exists($id, $opts) ? (string) $opts[$id] : '—';
|
||
|
||
if ($id <= 0) {
|
||
return ['label' => $label, 'count' => 0, 'totale_prev' => 0.0, 'totale_cons' => 0.0, 'voci' => []];
|
||
}
|
||
|
||
$q = $this->getTableQuery()
|
||
->where('voci_spesa.tabella_millesimale_default_id', $id);
|
||
|
||
$records = $q->get([
|
||
'voci_spesa.codice',
|
||
'voci_spesa.descrizione',
|
||
'voci_spesa.importo_default',
|
||
'voci_spesa.importo_consuntivo',
|
||
'voci_spesa.percentuale_condomino',
|
||
'voci_spesa.percentuale_inquilino',
|
||
'voci_spesa.attiva',
|
||
]);
|
||
|
||
$voci = $records->map(function ($r): array {
|
||
return [
|
||
'codice' => (string) ($r->codice ?? ''),
|
||
'descrizione' => (string) ($r->descrizione ?? ''),
|
||
'prev' => (float) ($r->importo_default ?? 0),
|
||
'cons' => (float) ($r->importo_consuntivo ?? 0),
|
||
'prop' => (float) ($r->percentuale_condomino ?? 0),
|
||
'inq' => (float) ($r->percentuale_inquilino ?? 0),
|
||
'attiva' => (bool) ($r->attiva ?? false),
|
||
];
|
||
})->all();
|
||
|
||
$totPrev = (float) $records->sum(fn($r) => (float) ($r->importo_default ?? 0));
|
||
$totCons = (float) $records->sum(fn($r) => (float) ($r->importo_consuntivo ?? 0));
|
||
|
||
return [
|
||
'label' => $label,
|
||
'count' => count($voci),
|
||
'totale_prev' => $totPrev,
|
||
'totale_cons' => $totCons,
|
||
'voci' => $voci,
|
||
];
|
||
}
|
||
|
||
public function getNoTabellaCount(): int
|
||
{
|
||
return (int) $this->getTableQuery()
|
||
->whereNull('voci_spesa.tabella_millesimale_default_id')
|
||
->count();
|
||
}
|
||
|
||
/**
|
||
* Quando cambia il preset dalla UI, applica eventuali regole.
|
||
*/
|
||
public function updatedPresetRipartizione(string $value): void
|
||
{
|
||
$this->presetRipartizione = in_array($value, ['manuale', 'confedilizia'], true) ? $value : 'manuale';
|
||
$this->applyPresetRipartizione();
|
||
}
|
||
|
||
public function updatedGestioneContabileId($value): void
|
||
{
|
||
$id = is_numeric($value) ? (int) $value : null;
|
||
$this->gestioneContabileId = $id && $id > 0 ? $id : null;
|
||
$this->resetPage();
|
||
}
|
||
|
||
public function getGestioniOptions(): array
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return [];
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId || ! Schema::hasTable('gestioni_contabili')) {
|
||
return [];
|
||
}
|
||
|
||
$tipo = $this->normalizeTipoGestione($this->tipoGestioneTab ?: 'ordinaria');
|
||
|
||
$gestioni = GestioneContabile::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('tipo_gestione', $tipo)
|
||
->orderByDesc('gestione_attiva')
|
||
->orderByDesc('stato')
|
||
->orderByDesc('anno_gestione')
|
||
->orderByDesc('id')
|
||
->get(['id', 'anno_gestione', 'tipo_gestione', 'stato'])
|
||
->all();
|
||
|
||
$options = [];
|
||
$seenYears = [];
|
||
foreach ($gestioni as $g) {
|
||
$anno = (string) ($g->anno_gestione ?? '');
|
||
if ($anno === '' || isset($seenYears[$anno])) {
|
||
continue;
|
||
}
|
||
$seenYears[$anno] = true;
|
||
$options[(int) $g->id] = $anno;
|
||
}
|
||
|
||
return $options;
|
||
}
|
||
|
||
private function normalizeTipoGestione(string $tipo): string
|
||
{
|
||
return $tipo === 'acqua' ? 'ordinaria' : $tipo;
|
||
}
|
||
|
||
/** @return array<int, string> */
|
||
private function getLegacyTipoValues(string $tipo): array
|
||
{
|
||
return match ($tipo) {
|
||
'ordinaria' => ['ordinaria', 'O', 'o'],
|
||
'riscaldamento' => ['riscaldamento', 'R', 'r'],
|
||
'straordinaria' => ['straordinaria', 'S', 's'],
|
||
default => [$tipo],
|
||
};
|
||
}
|
||
|
||
private function applyTipoGestioneFilter(Builder $q, string $tipoTab, bool $hasLegacyTipo): Builder
|
||
{
|
||
$tipo = $this->normalizeTipoGestione($tipoTab);
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'tipo_gestione')) {
|
||
$q->where('voci_spesa.tipo_gestione', $tipo);
|
||
}
|
||
|
||
if ($tipoTab === 'acqua') {
|
||
$q->where(function (Builder $qq) {
|
||
$qq->whereRaw('UPPER(TRIM(tm.codice_tabella)) = ?', ['ACQUA'])
|
||
->orWhere('voci_spesa.categoria', 'acqua');
|
||
});
|
||
} elseif ($tipoTab === 'ordinaria') {
|
||
$q->whereRaw('(tm.codice_tabella IS NULL OR UPPER(TRIM(tm.codice_tabella)) != ?)', ['ACQUA']);
|
||
$q->where(function (Builder $qq) {
|
||
$qq->whereNull('voci_spesa.categoria')->orWhere('voci_spesa.categoria', '!=', 'acqua');
|
||
});
|
||
}
|
||
|
||
return $q;
|
||
}
|
||
|
||
private function getTabellaGroupTitle(VoceSpesa $record): string
|
||
{
|
||
$tabella = $this->resolveTabellaForAnno($record->tabellaMillesimaleDefault, (int) $record->stabile_id);
|
||
$code = trim((string) ($tabella?->codice_tabella ?? ''));
|
||
$name = trim((string) ($tabella?->denominazione ?? ''));
|
||
if ($name === '') {
|
||
$name = trim((string) ($tabella?->nome_tabella ?? ''));
|
||
}
|
||
if ($name === '') {
|
||
$name = trim((string) ($tabella?->nome_tabella_millesimale ?? ''));
|
||
}
|
||
|
||
return $this->formatTabellaLabel($code, $name, $tabella?->id);
|
||
}
|
||
|
||
private function formatTabellaLabel(?string $code, ?string $name, ?int $fallbackId = null): string
|
||
{
|
||
$code = trim((string) ($code ?? ''));
|
||
$name = trim((string) ($name ?? ''));
|
||
if ($code === '' && $fallbackId) {
|
||
$code = 'TAB #' . (int) $fallbackId;
|
||
}
|
||
|
||
if ($code === '' && $name === '') {
|
||
return 'NO TABELLA';
|
||
}
|
||
|
||
$label = $code !== '' && $name !== ''
|
||
? ($code . ' — ' . $name)
|
||
: ($name !== '' ? $name : $code);
|
||
|
||
return mb_strtoupper($label, 'UTF-8');
|
||
}
|
||
|
||
private function getNordOrderExpression(string $alias = 'tm'): string
|
||
{
|
||
$parts = [];
|
||
if (Schema::hasColumn('tabelle_millesimali', 'nord')) {
|
||
$parts[] = 'CAST(' . $alias . '.nord AS UNSIGNED)';
|
||
}
|
||
if (Schema::hasColumn('tabelle_millesimali', 'ordine_visualizzazione')) {
|
||
$parts[] = 'CAST(' . $alias . '.ordine_visualizzazione AS UNSIGNED)';
|
||
}
|
||
if (Schema::hasColumn('tabelle_millesimali', 'ordinamento')) {
|
||
$parts[] = 'CAST(' . $alias . '.ordinamento AS UNSIGNED)';
|
||
}
|
||
if ($parts === []) {
|
||
return '9999';
|
||
}
|
||
return 'COALESCE(' . implode(', ', $parts) . ', 9999)';
|
||
}
|
||
|
||
private function getNordValueForTabella(?TabellaMillesimale $tabella): ?int
|
||
{
|
||
if (! $tabella) {
|
||
return null;
|
||
}
|
||
foreach (['nord', 'ordine_visualizzazione', 'ordinamento'] as $field) {
|
||
if (isset($tabella->{$field}) && is_numeric($tabella->{$field})) {
|
||
return (int) $tabella->{$field};
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** @return array<int, string> */
|
||
private function getNordOptions(): array
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return [];
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId) {
|
||
return [];
|
||
}
|
||
|
||
$expr = $this->getNordOrderExpression('tabelle_millesimali');
|
||
if ($expr === '9999') {
|
||
return [];
|
||
}
|
||
|
||
$q = TabellaMillesimale::query()->where('stabile_id', $stabileId);
|
||
if (Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
||
$anno = AnnoGestioneContext::resolveActiveAnno($user);
|
||
$q->where('anno_gestione', $anno);
|
||
}
|
||
|
||
$values = $q->selectRaw('DISTINCT ' . $expr . ' as nord_val')
|
||
->pluck('nord_val')
|
||
->filter(fn($v) => $v !== null && $v !== '')
|
||
->unique()
|
||
->sort()
|
||
->values();
|
||
|
||
return $values->mapWithKeys(fn($v) => [(string) $v => (string) $v])->all();
|
||
}
|
||
|
||
/** @return array<int, string> */
|
||
private function getFirstTabellaCodesToHide(int $stabileId): array
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return [];
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab ?: 'ordinaria';
|
||
$tipo = $this->normalizeTipoGestione($tipoTab);
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
$hasAnno = Schema::hasColumn('tabelle_millesimali', 'anno_gestione');
|
||
$anno = AnnoGestioneContext::resolveActiveAnno($user);
|
||
|
||
$q = TabellaMillesimale::query()->where('stabile_id', $stabileId);
|
||
|
||
if ($hasAnno) {
|
||
$q->where('anno_gestione', $anno);
|
||
}
|
||
|
||
if ($hasLegacyTipo) {
|
||
$q->whereIn('tipo', $this->getLegacyTipoValues($tipo));
|
||
}
|
||
|
||
$q->orderByRaw($this->getNordOrderExpression('tabelle_millesimali'))
|
||
->orderBy('codice_tabella');
|
||
|
||
return $q->limit(2)->pluck('codice_tabella')->filter()->values()->all();
|
||
}
|
||
|
||
private function resolveTabellaForAnno(?TabellaMillesimale $tabella, int $stabileId): ?TabellaMillesimale
|
||
{
|
||
if (! $tabella) {
|
||
return null;
|
||
}
|
||
|
||
if (! Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
||
return $tabella;
|
||
}
|
||
|
||
$user = Auth::user();
|
||
$anno = $user instanceof User ? AnnoGestioneContext::resolveActiveAnno($user) : null;
|
||
if (! $anno) {
|
||
return $tabella;
|
||
}
|
||
|
||
$codice = trim((string) ($tabella->codice_tabella ?? ''));
|
||
if ($codice === '') {
|
||
return $tabella;
|
||
}
|
||
|
||
$cacheKey = $stabileId . '|' . $anno . '|' . $codice;
|
||
if (array_key_exists($cacheKey, $this->tabellaAnnoCache)) {
|
||
return $this->tabellaAnnoCache[$cacheKey];
|
||
}
|
||
|
||
$match = TabellaMillesimale::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('codice_tabella', $codice)
|
||
->where('anno_gestione', $anno)
|
||
->orderBy('ordine_visualizzazione')
|
||
->orderBy('ordinamento')
|
||
->orderBy('id')
|
||
->first();
|
||
|
||
$this->tabellaAnnoCache[$cacheKey] = $match ?: $tabella;
|
||
|
||
return $this->tabellaAnnoCache[$cacheKey];
|
||
}
|
||
|
||
/**
|
||
* Applica il preset selezionato alle percentuali %Prop/%Inq delle voci attualmente in lista.
|
||
*/
|
||
public function applyPresetRipartizione(): void
|
||
{
|
||
if ($this->presetRipartizione === 'manuale') {
|
||
return;
|
||
}
|
||
|
||
if ($this->presetRipartizione !== 'confedilizia') {
|
||
return;
|
||
}
|
||
|
||
$records = $this->getTableQuery()
|
||
->get(['id', 'categoria']);
|
||
|
||
foreach ($records as $record) {
|
||
if (! $record instanceof VoceSpesa) {
|
||
continue;
|
||
}
|
||
|
||
$categoria = $this->mapVoceCategoriaToConfedilizia((string) $record->categoria);
|
||
$default = RipartizioneSpeseInquilini::getDefaultConfedilizia($categoria);
|
||
|
||
$record->update([
|
||
'percentuale_condomino' => (float) ($default['proprietario'] ?? 100),
|
||
'percentuale_inquilino' => (float) ($default['inquilino'] ?? 0),
|
||
]);
|
||
}
|
||
|
||
$this->dispatch('$refresh');
|
||
}
|
||
|
||
private function mapVoceCategoriaToConfedilizia(string $categoriaVoce): string
|
||
{
|
||
return match ($categoriaVoce) {
|
||
'riscaldamento' => 'B',
|
||
'ascensore' => 'C',
|
||
'illuminazione' => 'D',
|
||
'pulizia' => 'E',
|
||
'acqua' => 'G',
|
||
'energia_elettrica' => 'L',
|
||
'gas' => 'B',
|
||
'giardino_verde' => 'I',
|
||
'sicurezza' => 'N',
|
||
'amministrazione' => 'O',
|
||
'assicurazioni' => 'A',
|
||
'tasse_tributi' => 'O',
|
||
'spese_legali' => 'O',
|
||
'manutenzione_ordinaria' => 'A',
|
||
'manutenzione_straordinaria' => 'A',
|
||
'lavori_miglioramento' => 'A',
|
||
default => 'A',
|
||
};
|
||
}
|
||
|
||
public function setTipoGestioneTab(string $tab): void
|
||
{
|
||
$tab = trim($tab);
|
||
if (! in_array($tab, ['ordinaria', 'acqua', 'riscaldamento', 'straordinaria'], true)) {
|
||
return;
|
||
}
|
||
|
||
$selectedYear = null;
|
||
if ($this->gestioneContabileId && Schema::hasTable('gestioni_contabili')) {
|
||
$year = GestioneContabile::query()->whereKey($this->gestioneContabileId)->value('anno_gestione');
|
||
$selectedYear = is_numeric($year) ? (int) $year : null;
|
||
}
|
||
|
||
$this->tipoGestioneTab = $tab;
|
||
|
||
$user = Auth::user();
|
||
$stabileId = $user instanceof User ? StabileContext::resolveActiveStabileId($user) : null;
|
||
if ($stabileId && $selectedYear) {
|
||
$this->gestioneContabileId = $this->resolveGestioneIdForAnnoTipo(
|
||
(int) $stabileId,
|
||
$selectedYear,
|
||
$this->normalizeTipoGestione($tab)
|
||
);
|
||
} else {
|
||
$this->gestioneContabileId = $this->resolveGestioneContabileId();
|
||
}
|
||
|
||
// Filament v4: reset della paginazione tabella
|
||
$this->resetPage();
|
||
}
|
||
|
||
protected function resolveGestioneContabileId(): ?int
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return null;
|
||
}
|
||
|
||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $stabileId) {
|
||
return null;
|
||
}
|
||
|
||
if (! Schema::hasTable('gestioni_contabili')) {
|
||
return null;
|
||
}
|
||
|
||
$anno = AnnoGestioneContext::resolveActiveAnno($user);
|
||
// Per questa pagina la gestione è determinata dalla tab selezionata.
|
||
$tipo = $this->normalizeTipoGestione($this->tipoGestioneTab ?: 'ordinaria');
|
||
|
||
$match = GestioneContabile::query()
|
||
->where('stabile_id', $stabileId)
|
||
->where('anno_gestione', $anno)
|
||
->where('tipo_gestione', $tipo)
|
||
->where('stato', 'aperta')
|
||
->orderByDesc('gestione_attiva')
|
||
->orderByDesc('id')
|
||
->first();
|
||
|
||
return $match?->id ? (int) $match->id : null;
|
||
}
|
||
|
||
protected function getTableQuery(): Builder
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return VoceSpesa::query()->whereRaw('1 = 0');
|
||
}
|
||
|
||
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $activeStabileId) {
|
||
return VoceSpesa::query()->whereRaw('1 = 0');
|
||
}
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && ! $this->gestioneContabileId) {
|
||
return VoceSpesa::query()->whereRaw('1 = 0');
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab;
|
||
|
||
// Requisito (legacy): tabs filtrano per `tabelle.tipo` e ordinano per `tabelle.nord`.
|
||
// Nel dominio corrente i nomi colonna possono variare: gestiamo fallback in modo robusto.
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
|
||
$query = VoceSpesa::query()
|
||
->select('voci_spesa.*')
|
||
->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'voci_spesa.tabella_millesimale_default_id')
|
||
->where('voci_spesa.stabile_id', $activeStabileId)
|
||
->when(Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && $this->gestioneContabileId, function (Builder $q) {
|
||
$q->where('voci_spesa.gestione_contabile_id', $this->gestioneContabileId);
|
||
})
|
||
->with('tabellaMillesimaleDefault');
|
||
|
||
if ($this->hideFirstTwoTabelle) {
|
||
$hidden = $this->getFirstTabellaCodesToHide($activeStabileId);
|
||
if (! empty($hidden)) {
|
||
$query->where(function (Builder $q) use ($hidden) {
|
||
$q->whereNull('tm.codice_tabella')->orWhereNotIn('tm.codice_tabella', $hidden);
|
||
});
|
||
}
|
||
}
|
||
|
||
$query->orderByRaw($this->getNordOrderExpression('tm'));
|
||
$query->orderBy('tm.codice_tabella');
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'nord')) {
|
||
$query->orderByRaw('COALESCE(voci_spesa.nord, voci_spesa.ordinamento, 9999)');
|
||
} else {
|
||
$query->orderByRaw('COALESCE(voci_spesa.ordinamento, 9999)');
|
||
}
|
||
$query->orderBy('voci_spesa.codice');
|
||
$query->orderBy('voci_spesa.descrizione');
|
||
$query->orderBy('voci_spesa.id');
|
||
|
||
$this->applyTipoGestioneFilter($query, $tipoTab, $hasLegacyTipo);
|
||
|
||
return $query;
|
||
}
|
||
|
||
public function getTotalePreventivo(): float
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return 0.0;
|
||
}
|
||
|
||
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $activeStabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && ! $this->gestioneContabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab;
|
||
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
|
||
$q = VoceSpesa::query()->where('voci_spesa.stabile_id', $activeStabileId);
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && $this->gestioneContabileId) {
|
||
$q->where('gestione_contabile_id', $this->gestioneContabileId);
|
||
}
|
||
|
||
$q->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'voci_spesa.tabella_millesimale_default_id');
|
||
$this->applyTipoGestioneFilter($q, $tipoTab, $hasLegacyTipo);
|
||
|
||
return (float) $q->sum('importo_default');
|
||
}
|
||
|
||
public function getTotalePreventivoAcqua(): float
|
||
{
|
||
return $this->getTotaleByTipoTab('acqua', 'importo_default');
|
||
}
|
||
|
||
public function getTotalePreventivoGenerale(): float
|
||
{
|
||
if (($this->tipoGestioneTab ?? 'ordinaria') === 'acqua') {
|
||
return $this->getTotalePreventivo();
|
||
}
|
||
|
||
return $this->getTotalePreventivo() + $this->getTotalePreventivoAcqua();
|
||
}
|
||
|
||
public function getTotaleConsuntivo(): float
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return 0.0;
|
||
}
|
||
|
||
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $activeStabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && ! $this->gestioneContabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
if (! Schema::hasColumn('voci_spesa', 'importo_consuntivo')) {
|
||
return 0.0;
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab;
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
|
||
$q = VoceSpesa::query()->where('voci_spesa.stabile_id', $activeStabileId);
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && $this->gestioneContabileId) {
|
||
$q->where('gestione_contabile_id', $this->gestioneContabileId);
|
||
}
|
||
|
||
$q->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'voci_spesa.tabella_millesimale_default_id');
|
||
$this->applyTipoGestioneFilter($q, $tipoTab, $hasLegacyTipo);
|
||
|
||
return (float) $q->sum('importo_consuntivo');
|
||
}
|
||
|
||
public function getTotaleConsuntivoAcqua(): float
|
||
{
|
||
return $this->getTotaleByTipoTab('acqua', 'importo_consuntivo');
|
||
}
|
||
|
||
public function getTotaleConsuntivoGenerale(): float
|
||
{
|
||
if (($this->tipoGestioneTab ?? 'ordinaria') === 'acqua') {
|
||
return $this->getTotaleConsuntivo();
|
||
}
|
||
|
||
return $this->getTotaleConsuntivo() + $this->getTotaleConsuntivoAcqua();
|
||
}
|
||
|
||
private function getTotaleByTipoTab(string $tipoTab, string $column): float
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return 0.0;
|
||
}
|
||
|
||
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $activeStabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && ! $this->gestioneContabileId) {
|
||
return 0.0;
|
||
}
|
||
|
||
if (! Schema::hasColumn('voci_spesa', $column)) {
|
||
return 0.0;
|
||
}
|
||
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
|
||
$q = VoceSpesa::query()->where('voci_spesa.stabile_id', $activeStabileId);
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && $this->gestioneContabileId) {
|
||
$q->where('gestione_contabile_id', $this->gestioneContabileId);
|
||
}
|
||
|
||
$q->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'voci_spesa.tabella_millesimale_default_id');
|
||
$this->applyTipoGestioneFilter($q, $tipoTab, $hasLegacyTipo);
|
||
|
||
return (float) $q->sum($column);
|
||
}
|
||
|
||
/**
|
||
* Totali per tabella (codice_tabella) e per Prev/Cons.
|
||
* @return array<int, array{tabella: string, totale_prev: float, totale_cons: float}>
|
||
*/
|
||
public function getTotaliPerTabella(): array
|
||
{
|
||
$user = Auth::user();
|
||
if (! $user instanceof User) {
|
||
return [];
|
||
}
|
||
|
||
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
||
if (! $activeStabileId) {
|
||
return [];
|
||
}
|
||
|
||
if (Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && ! $this->gestioneContabileId) {
|
||
return [];
|
||
}
|
||
|
||
$tipoTab = $this->tipoGestioneTab;
|
||
$hasLegacyTipo = Schema::hasColumn('tabelle_millesimali', 'tipo');
|
||
$q = VoceSpesa::query()
|
||
->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'voci_spesa.tabella_millesimale_default_id')
|
||
->where('voci_spesa.stabile_id', $activeStabileId)
|
||
->when(Schema::hasColumn('voci_spesa', 'gestione_contabile_id') && $this->gestioneContabileId, function (Builder $qq) {
|
||
$qq->where('voci_spesa.gestione_contabile_id', $this->gestioneContabileId);
|
||
});
|
||
|
||
$q->selectRaw('COALESCE(tm.codice_tabella, "—") as tabella, COALESCE(tm.nord, tm.ordine_visualizzazione, tm.ordinamento, 9999) as tabella_nord, SUM(voci_spesa.importo_default) as totale_prev, SUM(COALESCE(voci_spesa.importo_consuntivo, 0)) as totale_cons');
|
||
|
||
$this->applyTipoGestioneFilter($q, $tipoTab, $hasLegacyTipo);
|
||
|
||
return $q
|
||
->groupBy('tabella', 'tabella_nord')
|
||
->orderBy('tabella_nord')
|
||
->orderByRaw('tabella = "—"')
|
||
->orderBy('tabella')
|
||
->get()
|
||
->map(fn($r) => [
|
||
'tabella' => (string) $r->tabella,
|
||
'totale_prev' => (float) $r->totale_prev,
|
||
'totale_cons' => (float) $r->totale_cons,
|
||
])
|
||
->all();
|
||
}
|
||
|
||
public function table(Table $table): Table
|
||
{
|
||
return $table
|
||
->striped()
|
||
->groups([
|
||
Group::make('tabellaMillesimaleDefault.codice_tabella')
|
||
->label('Tabella')
|
||
->titlePrefixedWithLabel(false)
|
||
->getTitleFromRecordUsing(fn(VoceSpesa $record): string => $this->getTabellaGroupTitle($record)),
|
||
])
|
||
->defaultGroup('tabellaMillesimaleDefault.codice_tabella')
|
||
->groupingSettingsHidden()
|
||
->filters([
|
||
SelectFilter::make('tabella')
|
||
->label('Tabella')
|
||
->options(fn(): array=> $this->getTabelleMillesimaliOptions())
|
||
->searchable()
|
||
->query(function (Builder $query, array $data): Builder {
|
||
$value = $data['value'] ?? null;
|
||
if (! is_numeric($value)) {
|
||
return $query;
|
||
}
|
||
return $query->where('voci_spesa.tabella_millesimale_default_id', (int) $value);
|
||
}),
|
||
SelectFilter::make('nord')
|
||
->label('NORD')
|
||
->options(fn(): array=> $this->getNordOptions())
|
||
->query(function (Builder $query, array $data): Builder {
|
||
$value = $data['value'] ?? null;
|
||
if ($value === null || $value === '') {
|
||
return $query;
|
||
}
|
||
return $query->whereRaw($this->getNordOrderExpression('tm') . ' = ?', [$value]);
|
||
}),
|
||
])
|
||
->columns([
|
||
TextColumn::make('descrizione')
|
||
->label('Descrizione')
|
||
->searchable()
|
||
->wrap()
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
|
||
TextColumn::make('importo_default')
|
||
->label('Prev.')
|
||
->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.'))
|
||
->alignRight()
|
||
->summarize(Sum::make()->label('Subtotale')->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.')))
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
|
||
TextColumn::make('importo_consuntivo')
|
||
->label('Cons.')
|
||
->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.'))
|
||
->alignRight()
|
||
->summarize(Sum::make()->label('Subtotale')->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.')))
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
|
||
TextColumn::make('percentuale_condomino')
|
||
->label('% Prop.')
|
||
->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.'))
|
||
->alignRight()
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
|
||
TextColumn::make('percentuale_inquilino')
|
||
->label('% Inq.')
|
||
->formatStateUsing(fn($state) => number_format((float) ($state ?? 0), 2, ',', '.'))
|
||
->alignRight()
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
|
||
IconColumn::make('attiva')
|
||
->label('Attiva')
|
||
->boolean()
|
||
->extraAttributes(['class' => 'text-[13px] leading-tight']),
|
||
])
|
||
->actions([
|
||
Action::make('mastrino')
|
||
->hiddenLabel()
|
||
->icon('heroicon-o-document-chart-bar')
|
||
->tooltip('Mastrino')
|
||
->url(fn(VoceSpesa $record) => VoceSpesaMastrino::getUrl([
|
||
'record' => (int) $record->id,
|
||
'gestione' => $this->gestioneContabileId,
|
||
], panel: 'admin-filament')),
|
||
Action::make('modifica')
|
||
->hiddenLabel()
|
||
->icon('heroicon-o-pencil-square')
|
||
->tooltip('Modifica')
|
||
->modalHeading('Modifica voce di spesa')
|
||
->form([
|
||
Select::make('tabella_millesimale_default_id')
|
||
->label('Tabella')
|
||
->options(fn() => $this->getTabelleMillesimaliOptions())
|
||
->searchable()
|
||
->placeholder('— Seleziona —')
|
||
->native(false),
|
||
Select::make('attiva')
|
||
->label('Attiva')
|
||
->options([1 => 'Sì', 0 => 'No'])
|
||
->default(1)
|
||
->native(false),
|
||
Select::make('categoria')
|
||
->label('Categoria')
|
||
->options(VoceSpesa::getCategorieStandard())
|
||
->searchable()
|
||
->native(false),
|
||
Select::make('tipo_gestione')
|
||
->label('Tipo gestione')
|
||
->options(VoceSpesa::getTipiGestione())
|
||
->native(false),
|
||
\Filament\Forms\Components\TextInput::make('importo_default')
|
||
->label('Importo preventivo')
|
||
->numeric()
|
||
->step('0.01'),
|
||
\Filament\Forms\Components\TextInput::make('importo_consuntivo')
|
||
->label('Importo consuntivo')
|
||
->numeric()
|
||
->step('0.01'),
|
||
\Filament\Forms\Components\TextInput::make('percentuale_condomino')
|
||
->label('% Proprietario')
|
||
->numeric()
|
||
->step('0.01')
|
||
->minValue(0)
|
||
->maxValue(100),
|
||
\Filament\Forms\Components\TextInput::make('percentuale_inquilino')
|
||
->label('% Inquilino')
|
||
->numeric()
|
||
->step('0.01')
|
||
->minValue(0)
|
||
->maxValue(100),
|
||
])
|
||
->fillForm(function (VoceSpesa $record): array {
|
||
return [
|
||
'tabella_millesimale_default_id' => $record->tabella_millesimale_default_id,
|
||
'attiva' => $record->attiva ? 1 : 0,
|
||
'categoria' => $record->categoria,
|
||
'tipo_gestione' => $record->tipo_gestione,
|
||
'importo_default' => $record->importo_default,
|
||
'importo_consuntivo' => $record->importo_consuntivo,
|
||
'percentuale_condomino' => $record->percentuale_condomino,
|
||
'percentuale_inquilino' => $record->percentuale_inquilino,
|
||
];
|
||
})
|
||
->action(function (array $data, VoceSpesa $record): void {
|
||
$cond = isset($data['percentuale_condomino']) ? (float) $data['percentuale_condomino'] : null;
|
||
$inq = isset($data['percentuale_inquilino']) ? (float) $data['percentuale_inquilino'] : null;
|
||
if ($cond !== null && $inq === null) {
|
||
$inq = max(0.0, 100.0 - $cond);
|
||
}
|
||
$record->update([
|
||
'tabella_millesimale_default_id' => is_numeric($data['tabella_millesimale_default_id'] ?? null)
|
||
? (int) $data['tabella_millesimale_default_id']
|
||
: null,
|
||
'attiva' => (bool) ($data['attiva'] ?? true),
|
||
'categoria' => $data['categoria'] ?? $record->categoria,
|
||
'tipo_gestione' => $data['tipo_gestione'] ?? $record->tipo_gestione,
|
||
'importo_default' => $data['importo_default'] ?? $record->importo_default,
|
||
'importo_consuntivo' => $data['importo_consuntivo'] ?? $record->importo_consuntivo,
|
||
'percentuale_condomino' => $cond ?? $record->percentuale_condomino,
|
||
'percentuale_inquilino' => $inq ?? $record->percentuale_inquilino,
|
||
]);
|
||
$this->dispatch('$refresh');
|
||
}),
|
||
Action::make('assegnaTabella')
|
||
->hiddenLabel()
|
||
->icon('heroicon-o-rectangle-stack')
|
||
->tooltip('Tabella')
|
||
->modalHeading('Imposta tabella di riferimento')
|
||
->form([
|
||
Select::make('tabella_millesimale_default_id')
|
||
->label('Tabella')
|
||
->options(fn() => $this->getTabelleMillesimaliOptions())
|
||
->searchable()
|
||
->placeholder('— Seleziona —')
|
||
->native(false),
|
||
])
|
||
->fillForm(function (VoceSpesa $record): array {
|
||
return [
|
||
'tabella_millesimale_default_id' => $record->tabella_millesimale_default_id,
|
||
];
|
||
})
|
||
->action(function (array $data, VoceSpesa $record): void {
|
||
$id = is_numeric($data['tabella_millesimale_default_id'] ?? null)
|
||
? (int) $data['tabella_millesimale_default_id']
|
||
: null;
|
||
$record->update(['tabella_millesimale_default_id' => $id && $id > 0 ? $id : null]);
|
||
$this->dispatch('$refresh');
|
||
}),
|
||
])
|
||
->paginationPageOptions([24, 48, 96, 150])
|
||
->defaultPaginationPageOption(48)
|
||
->bulkActions([
|
||
BulkAction::make('bulkAssegnaTabella')
|
||
->label('Assegna tabella')
|
||
->icon('heroicon-o-wrench-screwdriver')
|
||
->modalHeading('Assegna tabella alle voci selezionate')
|
||
->form([
|
||
Select::make('tabella_millesimale_default_id')
|
||
->label('Tabella')
|
||
->options(fn() => $this->getTabelleMillesimaliOptions())
|
||
->searchable()
|
||
->required()
|
||
->native(false),
|
||
])
|
||
->action(function (array $data, $records): void {
|
||
$id = is_numeric($data['tabella_millesimale_default_id'] ?? null)
|
||
? (int) $data['tabella_millesimale_default_id']
|
||
: null;
|
||
if (! $id || $id <= 0) {
|
||
return;
|
||
}
|
||
foreach ($records as $record) {
|
||
if ($record instanceof VoceSpesa) {
|
||
$record->update(['tabella_millesimale_default_id' => $id]);
|
||
}
|
||
}
|
||
$this->tabellaFocusId = $this->tabellaFocusId ?: $id;
|
||
$this->dispatch('$refresh');
|
||
}),
|
||
|
||
BulkAction::make('bulkRimuoviTabella')
|
||
->label('Rimuovi tabella')
|
||
->icon('heroicon-o-x-mark')
|
||
->requiresConfirmation()
|
||
->action(function ($records): void {
|
||
foreach ($records as $record) {
|
||
if ($record instanceof VoceSpesa) {
|
||
$record->update(['tabella_millesimale_default_id' => null]);
|
||
}
|
||
}
|
||
$this->dispatch('$refresh');
|
||
}),
|
||
]);
|
||
}
|
||
}
|