'$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); 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'); } 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('anni_gestione')) { return; } $legacyCode = trim((string) ($stabile->codice_stabile ?? '')); if ($legacyCode === '') { return; } $years = DB::connection('gescon_import') ->table('anni_gestione') ->where('cod_stabile', $legacyCode) ->pluck('anno') ->filter(fn ($value) => is_numeric($value)) ->map(fn ($value) => (int) $value) ->unique() ->sort() ->values(); if ($years->isEmpty()) { return; } $tenantId = GestioneContabile::query() ->where('stabile_id', (int) $stabile->id) ->value('tenant_id'); if (! is_string($tenantId) || trim($tenantId) === '') { return; } $latestYear = (int) $years->max(); foreach ($years as $year) { GestioneContabile::query()->updateOrCreate( [ 'tenant_id' => $tenantId, 'stabile_id' => (int) $stabile->id, 'anno_gestione' => (int) $year, 'tipo_gestione' => 'ordinaria', 'numero_straordinaria' => null, ], [ 'denominazione' => 'Ordinaria ' . $year, 'data_inizio' => $year . '-01-01', 'data_fine' => $year . '-12-31', 'stato' => $year === $latestYear ? 'aperta' : 'chiusa', 'protocollo_prefix' => 'O' . $year, 'gestione_attiva' => $year === $latestYear, 'codice_archivio_legacy' => 'legacy:anni_gestione:' . $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('anno_gestione') ->label('Anno') ->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('data_inizio') ->label('Dal') ->date('d/m/Y') ->sortable(), TextColumn::make('data_fine') ->label('Al') ->date('d/m/Y') ->sortable(), 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', shouldOpenInNewTab: true), 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; } }