'$refresh', ]; public string $scope = 'aperte'; private bool $legacySyncAttempted = false; public ?string $tipo = null; public function mount(): void { $this->mountInteractsWithTable(); } /** * Query principale tabella gestioni. * Applica ambito (aperte/archivio), tipo gestione e filtro riscaldamento. */ protected function getTableQuery(): Builder { $user = Auth::user(); if (! $user instanceof User) { return GestioneContabile::query()->whereRaw('1 = 0'); } $activeStabileId = StabileContext::resolveActiveStabileId($user); if (! $activeStabileId) { return GestioneContabile::query()->whereRaw('1 = 0'); } $stabile = \App\Models\Stabile::query() ->select('id', 'configurazione_avanzata') ->find($activeStabileId); $this->syncLegacyOrdinarieForStabile($stabile); $mostraRiscaldamento = (bool) data_get($stabile?->configurazione_avanzata, 'mostra_riscaldamento', false); $query = GestioneContabile::query() ->where('stabile_id', $activeStabileId); $tenantId = trim((string) ($user->tenant_id ?? '')); if ($tenantId !== '') { $query->where(function (Builder $builder) use ($tenantId): void { $builder->where('tenant_id', $tenantId) ->orWhere('tenant_id', '00000000'); }); } if ($this->scope === 'archivio') { $query->whereIn('stato', ['chiusa', 'consolidata']); } elseif ($this->scope === 'aperte') { $query->aperte(); } if ($this->tipo) { $query->where('tipo_gestione', $this->tipo); } if (! $mostraRiscaldamento) { $query->where('tipo_gestione', '!=', 'riscaldamento'); } $query->whereRaw("gestioni_contabili.id = ( select gc2.id from gestioni_contabili gc2 where gc2.tenant_id = gestioni_contabili.tenant_id and gc2.stabile_id = gestioni_contabili.stabile_id and gc2.anno_gestione = gestioni_contabili.anno_gestione and gc2.tipo_gestione = gestioni_contabili.tipo_gestione and ( (gc2.numero_straordinaria is null and gestioni_contabili.numero_straordinaria is null) or gc2.numero_straordinaria = gestioni_contabili.numero_straordinaria ) order by case when gc2.denominazione like '%/%' then 1 else 0 end desc, gc2.data_fine desc, gc2.id desc limit 1 )"); return $query; } private function syncLegacyOrdinarieForStabile( ? \App\Models\Stabile $stabile) : void { if ($this->legacySyncAttempted) { return; } $this->legacySyncAttempted = true; if (! $stabile) { return; } if (! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) { return; } $legacyCode = trim((string) ($stabile->codice_stabile ?? '')); if ($legacyCode === '') { return; } $legacyRows = DB::connection('gescon_import') ->table('gestioni_annuali') ->where('cod_stabile', $legacyCode) ->orderByDesc('anno_ordinario') ->get(['anno_ordinario', 'anno_riscaldamento', 'descrizione', 'ordinaria_dal', 'ordinaria_al', 'riscaldamento_dal', 'riscaldamento_al', 'cartella']); if ($legacyRows->isEmpty()) { return; } $tenantId = GestioneContabile::query() ->where('stabile_id', (int) $stabile->id) ->value('tenant_id'); if (! is_string($tenantId) || trim($tenantId) === '') { $tenantId = (string) (Auth::user()?->tenant_id ?? 'default'); } $mostraRiscaldamento = (bool) data_get($stabile->configurazione_avanzata, 'mostra_riscaldamento', false); $latestYear = (int) $legacyRows ->map(fn($row) => is_numeric($row->anno_ordinario ?? null) ? (int) $row->anno_ordinario : null) ->filter() ->max(); foreach ($legacyRows as $row) { $descrizione = trim((string) ($row->descrizione ?? $row->anno_ordinario ?? '')); $labelOrd = $descrizione !== '' ? preg_replace('/\s+/', '', $descrizione) : trim((string) ($row->anno_ordinario ?? '')); $labelRisc = trim((string) ($row->anno_riscaldamento ?? '')) ?: $labelOrd; $year = is_numeric($row->anno_ordinario ?? null) ? (int) $row->anno_ordinario : null; if (! $year) { continue; } $records = [ [ 'tipo' => 'ordinaria', 'inizio' => $row->ordinaria_dal ?: sprintf('%04d-01-01', $year), 'fine' => $row->ordinaria_al ?: sprintf('%04d-12-31', $year), 'prefix' => 'O' . $year, 'label' => $labelOrd, ], ]; if ($mostraRiscaldamento) { $records[] = [ 'tipo' => 'riscaldamento', 'inizio' => $row->riscaldamento_dal ?: ($row->ordinaria_dal ?: sprintf('%04d-01-01', $year)), 'fine' => $row->riscaldamento_al ?: ($row->ordinaria_al ?: sprintf('%04d-12-31', $year)), 'prefix' => 'R' . $year, 'label' => $labelRisc, ]; } foreach ($records as $recordMeta) { $lbl = $recordMeta['label']; GestioneContabile::query()->updateOrCreate( [ 'tenant_id' => $tenantId, 'stabile_id' => (int) $stabile->id, 'anno_gestione' => $year, 'tipo_gestione' => $recordMeta['tipo'], 'numero_straordinaria' => null, ], [ 'denominazione' => Str::headline($recordMeta['tipo']) . ' ' . ($lbl !== '' ? $lbl : $year), 'data_inizio' => $recordMeta['inizio'], 'data_fine' => $recordMeta['fine'], 'stato' => $year === $latestYear ? 'aperta' : 'chiusa', 'protocollo_prefix' => $recordMeta['prefix'], 'gestione_attiva' => $year === $latestYear, 'codice_archivio_legacy' => 'legacy:gestioni_annuali:' . (trim((string) ($row->cartella ?? '')) ?: $year), ] ); } } } public function table(Table $table): Table { return $table ->striped() ->defaultSort('anno_gestione', 'desc') ->recordUrl(fn(GestioneContabile $record): string => \App\Filament\Pages\Condomini\StabilePage::getUrl(panel: 'admin-filament') . '?tab=gestioni&gestioni_tab=periodi&gestione_id=' . $record->id) ->headerActions([ Action::make('nuova_gestione') ->label('Nuova gestione') ->icon('heroicon-o-plus') ->modalHeading('Nuova gestione') ->form([ \Filament\Forms\Components\Select::make('tipo_gestione') ->label('Tipo') ->options([ 'ordinaria' => 'Ordinaria', 'riscaldamento' => 'Riscaldamento', 'straordinaria' => 'Straordinaria', ]) ->required(), \Filament\Forms\Components\TextInput::make('anno_gestione') ->label('Anno') ->numeric() ->required(), \Filament\Forms\Components\TextInput::make('denominazione') ->label('Denominazione') ->maxLength(255), \Filament\Forms\Components\DatePicker::make('data_inizio') ->label('Data inizio'), \Filament\Forms\Components\DatePicker::make('data_fine') ->label('Data fine'), CheckboxList::make('mesi') ->label('Mesi scadenza rate') ->options([ 1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu', 7 => 'Lug', 8 => 'Ago', 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic', ]) ->columns(6), ]) ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return; } $gestione = new GestioneContabile(); $gestione->stabile_id = $stabileId; $gestione->anno_gestione = (int) ($data['anno_gestione'] ?? now()->year); $gestione->tipo_gestione = (string) ($data['tipo_gestione'] ?? 'ordinaria'); $gestione->denominazione = $data['denominazione'] ?? null; $gestione->data_inizio = $data['data_inizio'] ?? null; $gestione->data_fine = $data['data_fine'] ?? null; $gestione->stato = 'aperta'; $gestione->gestione_attiva = true; $mesi = array_values(array_unique(array_map('intval', (array) ($data['mesi'] ?? [])))); sort($mesi); $field = match ($gestione->tipo_gestione) { 'ordinaria' => 'mesi_rate_ordinaria', 'riscaldamento' => 'mesi_rate_riscaldamento', 'straordinaria' => 'mesi_rate_straordinaria', default => 'mesi_rate_ordinaria', }; $gestione->{$field} = $mesi; $gestione->save(); }), Action::make('duplica_gestione') ->label('Duplica gestione') ->icon('heroicon-o-squares-plus') ->modalHeading('Duplica gestione') ->form([ \Filament\Forms\Components\Select::make('gestione_id') ->label('Gestione da duplicare') ->options(fn() => GestioneContabile::query() ->where('stabile_id', StabileContext::resolveActiveStabileId(Auth::user())) ->orderByDesc('anno_gestione') ->get() ->mapWithKeys(fn(GestioneContabile $g) => [$g->id => ($g->anno_gestione . ' · ' . ($g->denominazione ?: $g->tipo_gestione))])) ->searchable() ->required(), \Filament\Forms\Components\TextInput::make('anno_gestione') ->label('Anno nuovo') ->numeric() ->required(), ]) ->action(function (array $data): void { $gestione = GestioneContabile::query()->find((int) ($data['gestione_id'] ?? 0)); if (! $gestione) { return; } $clone = $gestione->replicate([ 'gestione_attiva', 'stato', 'created_at', 'updated_at', ]); $clone->anno_gestione = (int) ($data['anno_gestione'] ?? $gestione->anno_gestione); $clone->stato = 'aperta'; $clone->gestione_attiva = true; $clone->save(); }), ]) ->columns([ TextColumn::make('gestione_label') ->label('Gestione') ->getStateUsing(fn(GestioneContabile $record): string => $record->gestione_label) ->sortable(), TextColumn::make('tipo_gestione') ->label('Tipo') ->sortable(), TextColumn::make('numero_straordinaria') ->label('N° Straord.') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('denominazione') ->label('Denominazione') ->searchable() ->wrap(), TextColumn::make('origine_dati') ->label('Origine') ->getStateUsing(function (GestioneContabile $record): string { if (filled($record->codice_archivio_legacy)) { return 'Legacy collegata'; } return 'Locale'; }) ->badge() ->color(fn(string $state): string => $state === 'Legacy collegata' ? 'success' : 'gray'), TextColumn::make('periodo_label') ->label('Periodo') ->getStateUsing(fn(GestioneContabile $record): string => $record->periodo_label) ->wrap(), TextColumn::make('stato') ->label('Stato') ->formatStateUsing(fn(string $state): string => ucfirst($state)) ->badge() ->color(fn(string $state): string => match ($state) { 'aperta' => 'success', 'consolidata' => 'warning', 'chiusa' => 'gray', default => 'gray', }) ->toggleable(isToggledHiddenByDefault: true), IconColumn::make('gestione_attiva') ->label('Attiva') ->boolean(), TextColumn::make('mesi_rate') ->label('Mesi rate') ->getStateUsing(function (GestioneContabile $record): string { $mesi = match ($record->tipo_gestione) { 'ordinaria' => (array) ($record->mesi_rate_ordinaria ?? []), 'riscaldamento' => (array) ($record->mesi_rate_riscaldamento ?? []), 'straordinaria' => (array) ($record->mesi_rate_straordinaria ?? []), default => [], }; $mesi = array_values(array_unique(array_map('intval', $mesi))); sort($mesi); return $mesi ? implode(', ', $mesi) : '—'; }) ->wrap(), ]) ->actions([ Action::make('imposta_mesi_rate') ->label('Mesi rate') ->icon('heroicon-o-calendar') ->modalHeading('Imposta mesi emissione rate') ->fillForm(function (GestioneContabile $record): array { $mesi = match ($record->tipo_gestione) { 'ordinaria' => (array) ($record->mesi_rate_ordinaria ?? []), 'riscaldamento' => (array) ($record->mesi_rate_riscaldamento ?? []), 'straordinaria' => (array) ($record->mesi_rate_straordinaria ?? []), default => [], }; $mesi = array_values(array_unique(array_map('intval', $mesi))); sort($mesi); return [ 'mesi' => $mesi, 'replica_tutti_anni' => true, ]; }) ->form([ CheckboxList::make('mesi') ->label('Mesi in cui emettere le rate') ->options([ 1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu', 7 => 'Lug', 8 => 'Ago', 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic', ]) ->columns(6) ->required(), Toggle::make('replica_tutti_anni') ->label('Replica questi mesi su tutti gli anni (stesso tipo)') ->default(true), ]) ->action(function (GestioneContabile $record, array $data): void { $mesi = array_values(array_unique(array_map('intval', (array) ($data['mesi'] ?? [])))); sort($mesi); $field = match ($record->tipo_gestione) { 'ordinaria' => 'mesi_rate_ordinaria', 'riscaldamento' => 'mesi_rate_riscaldamento', 'straordinaria' => 'mesi_rate_straordinaria', default => 'mesi_rate_ordinaria', }; $query = GestioneContabile::query() ->where('stabile_id', $record->stabile_id) ->where('tipo_gestione', $record->tipo_gestione); if (empty($data['replica_tutti_anni'])) { $query->whereKey($record->getKey()); } $query->each(function (GestioneContabile $g) use ($field, $mesi): void { $g->{$field} = $mesi; $g->save(); }); }), Action::make('apri_archivio_gestioni') ->label('Apri archivio') ->url(fn() => \App\Filament\Pages\Condomini\StabilePage::getUrl(panel: 'admin-filament') . '?tab=gestioni&gestioni_tab=archivio'), Action::make('archivia_gestione') ->label('Sposta in archivio') ->icon('heroicon-o-archive-box') ->requiresConfirmation() ->visible(fn() => $this->scope !== 'archivio') ->action(fn(GestioneContabile $record) => $this->archiveGestione($record)), Action::make('ripristina_gestione') ->label('Ripristina in gestione') ->icon('heroicon-o-arrow-path') ->requiresConfirmation() ->visible(fn() => $this->scope === 'archivio') ->action(fn(GestioneContabile $record) => $this->restoreGestione($record)), ]) ->bulkActions([ BulkAction::make('imposta_mesi_rate') ->label('Imposta mesi rate') ->icon('heroicon-o-calendar') ->modalHeading('Imposta mesi emissione rate (selezionati)') ->form([ CheckboxList::make('mesi') ->label('Mesi in cui emettere le rate') ->options([ 1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu', 7 => 'Lug', 8 => 'Ago', 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic', ]) ->columns(6) ->required(), ]) ->action(function ($records, array $data): void { $mesi = array_values(array_unique(array_map('intval', (array) ($data['mesi'] ?? [])))); sort($mesi); foreach ($records as $record) { if (! $record instanceof GestioneContabile) { continue; } $field = match ($record->tipo_gestione) { 'ordinaria' => 'mesi_rate_ordinaria', 'riscaldamento' => 'mesi_rate_riscaldamento', 'straordinaria' => 'mesi_rate_straordinaria', default => 'mesi_rate_ordinaria', }; $record->{$field} = $mesi; $record->save(); } }), BulkAction::make('archivia_gestioni') ->label('Sposta in archivio') ->icon('heroicon-o-archive-box') ->requiresConfirmation() ->visible(fn() => $this->scope !== 'archivio') ->action(function ($records): void { foreach ($records as $record) { if (! $record instanceof GestioneContabile) { continue; } $this->archiveGestione($record); } }), BulkAction::make('ripristina_gestioni') ->label('Ripristina in gestione') ->icon('heroicon-o-arrow-path') ->requiresConfirmation() ->visible(fn() => $this->scope === 'archivio') ->action(function ($records): void { foreach ($records as $record) { if (! $record instanceof GestioneContabile) { continue; } $this->restoreGestione($record); } }), ]); } public function render() { return view('filament.pages.condomini.components.gestioni-stabile-table'); } public function makeFilamentTranslatableContentDriver(): ?TranslatableContentDriver { return null; } /** * Sposta una gestione in archivio logico (chiusa + non attiva). */ private function archiveGestione(GestioneContabile $gestione): void { if (in_array($gestione->stato, ['chiusa', 'consolidata'], true)) { $gestione->gestione_attiva = false; $gestione->save(); return; } $gestione->stato = 'chiusa'; $gestione->gestione_attiva = false; $gestione->save(); } /** * Ripristina una gestione in elenco attivo. */ private function restoreGestione(GestioneContabile $gestione): void { $gestione->stato = 'aperta'; $gestione->gestione_attiva = true; $gestione->save(); } private function restoreBestGestioneYear(int $year): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return; } $gestioni = GestioneContabile::query() ->where('stabile_id', $stabileId) ->where('anno_gestione', $year) ->get(); if ($gestioni->isEmpty()) { return; } $keep = $this->selectEffectiveGestione($gestioni); $gestioni->each(function (GestioneContabile $g) use ($keep): void { if ($keep && $g->id === $keep->id) { $this->restoreGestione($g); return; } $this->archiveGestione($g); }); } /** * Seleziona la gestione "migliore" del gruppo anno/tipo usando punteggio attività. */ private function selectEffectiveGestione(Collection $group): ?GestioneContabile { if ($group->isEmpty()) { return null; } $ranked = $group->map(function (GestioneContabile $g): array { return [ 'gestione' => $g, 'score' => $this->getGestioneActivityScore($g), 'attiva' => $g->gestione_attiva ? 1 : 0, 'data_fine' => $g->data_fine?->timestamp ?? 0, 'data_inizio' => $g->data_inizio?->timestamp ?? 0, 'id' => $g->id, ]; })->sortByDesc(function (array $row): array { return [$row['score'], $row['attiva'], $row['data_fine'], $row['data_inizio'], $row['id']]; })->values(); return $ranked->first()['gestione'] ?? $group->first(); } /** * Calcola un punteggio attività interrogando tabelle contabili collegate. */ private function getGestioneActivityScore(GestioneContabile $gestione): int { $score = 0; if (Schema::hasTable('operazioni_contabili') && Schema::hasColumn('operazioni_contabili', 'gestione_id')) { if (DB::table('operazioni_contabili')->where('gestione_id', $gestione->id)->exists()) { $score += 100; } } if (Schema::hasTable('incassi') && Schema::hasColumn('incassi', 'gestione_id')) { if (DB::table('incassi')->where('gestione_id', $gestione->id)->exists()) { $score += 50; } } if (Schema::hasTable('incassi_estratto_conto') && Schema::hasColumn('incassi_estratto_conto', 'gestione_id')) { if (DB::table('incassi_estratto_conto')->where('gestione_id', $gestione->id)->exists()) { $score += 30; } } if (Schema::hasTable('registro_ritenute_acconto') && Schema::hasColumn('registro_ritenute_acconto', 'gestione_id')) { if (DB::table('registro_ritenute_acconto')->where('gestione_id', $gestione->id)->exists()) { $score += 10; } } if (Schema::hasTable('voci_spesa') && Schema::hasColumn('voci_spesa', 'gestione_contabile_id')) { $exists = DB::table('voci_spesa') ->where('gestione_contabile_id', $gestione->id) ->exists(); if ($exists) { $score += 20; } } return $score; } }