hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(): void { $this->mountInteractsWithTable(); } protected function getTableQuery(): Builder { $user = Auth::user(); if (! $user instanceof User) { return Stabile::query()->whereRaw('1 = 0'); } if ($user->hasAnyRole(['super-admin'])) { return Stabile::query(); } $amm = $user->amministratore; if ($amm) { return Stabile::query()->where('amministratore_id', $amm->id); } $allowedIds = StabileContext::accessibleStabili($user) ->pluck('id') ->map(fn($v) => (int) $v) ->values(); if ($allowedIds->isEmpty()) { return Stabile::query()->whereRaw('1 = 0'); } return Stabile::query()->whereIn('id', $allowedIds); } public function table(Table $table): Table { return $table ->striped() ->defaultSort('denominazione') ->headerActions([ Action::make('sync_all_gescon') ->label('Sync GESCON (tutti)') ->icon('heroicon-o-arrow-path') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->can('gescon-import.execute'); }) ->form([ TextInput::make('path') ->label('Percorso archivio GESCON') ->helperText('Cartella base che contiene //... e /dbc/Stabili.mdb') ->default('/mnt/gescon-archives/gescon') ->maxLength(500), TextInput::make('anno') ->label('Anno gestione (opzionale)') ->helperText('Inserisci un anno (es. 2025) o "all" per tutte le cartelle.'), Checkbox::make('dry')->label('Dry-run (solo simulazione)')->default(false), Checkbox::make('with_unita')->label('Includi unità immobiliari')->default(false), Checkbox::make('with_soggetti')->label('Includi anagrafiche (soggetti)')->default(false), Checkbox::make('with_diritti')->label('Includi diritti (proprietà/inquilini)')->default(false), Checkbox::make('with_millesimi')->label('Includi tabelle millesimali')->default(false), Checkbox::make('with_voci')->label('Includi voci (piano dei conti / voci spesa)')->default(false), Checkbox::make('with_gestioni')->label('Includi gestioni')->default(false), Checkbox::make('with_banche')->label('Includi banche')->default(false), ]) ->requiresConfirmation() ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { return; } $amm = $user->amministratore; if (! $amm) { Notification::make() ->title('Amministratore non associato') ->body('Impossibile eseguire sync senza amministratore associato all\'utente.') ->danger() ->send(); return; } $options = [ 'path' => (string) ($data['path'] ?? '/mnt/gescon-archives/gescon'), 'anno' => isset($data['anno']) && trim((string) $data['anno']) !== '' ? trim((string) $data['anno']) : null, 'with_unita' => (bool) ($data['with_unita'] ?? false), 'with_soggetti' => (bool) ($data['with_soggetti'] ?? false), 'with_diritti' => (bool) ($data['with_diritti'] ?? false), 'with_millesimi' => (bool) ($data['with_millesimi'] ?? false), 'with_voci' => (bool) ($data['with_voci'] ?? false), 'with_gestioni' => (bool) ($data['with_gestioni'] ?? false), 'with_banche' => (bool) ($data['with_banche'] ?? false), 'dry' => (bool) ($data['dry'] ?? false), ]; $query = $this->getTableQuery(); $stabili = $query->get(); $svc = new EssentialImportService(); foreach ($stabili as $record) { $legacyCode = trim((string) ($record->codice_stabile ?? '')); if ($legacyCode === '') { $legacyCode = trim((string) ($record->codice_interno ?? '')); } if ($legacyCode === '') { continue; } $svc->syncStabile($amm, $legacyCode, $options); } Notification::make() ->title('Sync completato') ->body('Sincronizzati tutti gli stabili visibili.') ->success() ->send(); }), Action::make('crea_stabile') ->label('Crea stabile') ->icon('heroicon-o-plus') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore']); }) ->form([ TextInput::make('codice_stabile') ->label('Codice stabile (cartella legacy)') ->helperText('Esempio: 0021. Serve per collegare la cartella in /mnt/gescon-archives/gescon') ->required() ->maxLength(20), TextInput::make('denominazione') ->label('Denominazione') ->required() ->maxLength(255), TextInput::make('indirizzo') ->label('Indirizzo') ->maxLength(255), TextInput::make('cap') ->label('CAP') ->maxLength(10), TextInput::make('citta') ->label('Città') ->maxLength(100), TextInput::make('provincia') ->label('Provincia') ->maxLength(5), Checkbox::make('entra_subito') ->label('Entra subito nello stabile') ->default(true), ]) ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { Notification::make()->title('Utente non valido')->danger()->send(); return; } $amm = $user->amministratore; if (! $amm) { $amm = Amministratore::query()->orderBy('id')->first(); } if (! $amm) { Notification::make() ->title('Nessun amministratore disponibile') ->body('Serve almeno un record in tabella amministratori per creare uno stabile.') ->danger() ->send(); return; } $codice = trim((string) ($data['codice_stabile'] ?? '')); if ($codice === '') { Notification::make()->title('Codice mancante')->danger()->send(); return; } if (Stabile::query()->where('codice_stabile', $codice)->exists()) { Notification::make() ->title('Codice già presente') ->body('Esiste già uno stabile con codice ' . $codice) ->danger() ->send(); return; } $stabile = Stabile::query()->create([ 'amministratore_id' => $amm->id, 'codice_stabile' => $codice, 'denominazione' => (string) ($data['denominazione'] ?? $codice), 'indirizzo' => (string) ($data['indirizzo'] ?? ''), 'cap' => (string) ($data['cap'] ?? ''), 'citta' => (string) ($data['citta'] ?? ''), 'provincia' => (string) ($data['provincia'] ?? ''), 'stato' => 'attivo', 'attivo' => true, ]); Notification::make() ->title('Stabile creato') ->body('Codice: ' . $codice) ->success() ->send(); StabileContext::setActiveStabileId($user, (int) $stabile->id); if (! empty($data['entra_subito'])) { $this->redirect(\App\Filament\Pages\Condomini\CruscottoStabile::getUrl(panel: 'admin-filament')); } }), Action::make('crea_demo') ->label('Crea stabile demo') ->icon('heroicon-o-sparkles') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin']); }) ->form([ TextInput::make('denominazione') ->label('Denominazione') ->default('Condominio Demo') ->required(), Checkbox::make('entra_subito') ->label('Entra subito nello stabile') ->default(true), ]) ->requiresConfirmation() ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { Notification::make()->title('Utente non valido')->danger()->send(); return; } $amm = $user->amministratore; if (! $amm) { $amm = Amministratore::query()->orderBy('id')->first(); } if (! $amm) { Notification::make() ->title('Nessun amministratore disponibile') ->body('Serve almeno un record in tabella amministratori per creare uno stabile.') ->danger() ->send(); return; } $codice = 'DEMO' . Str::upper(Str::random(4)); while (Stabile::query()->where('codice_stabile', $codice)->exists()) { $codice = 'DEMO' . Str::upper(Str::random(4)); } $stabile = Stabile::query()->create([ 'amministratore_id' => $amm->id, 'codice_stabile' => $codice, 'denominazione' => (string) ($data['denominazione'] ?? 'Condominio Demo'), 'indirizzo' => 'Via Demo 1', 'cap' => '00000', 'citta' => 'Demo', 'provincia' => 'RM', 'stato' => 'attivo', 'attivo' => true, ]); $palazzina = Palazzina::query()->create([ 'stabile_id' => $stabile->id, 'codice_palazzina' => 'A', 'denominazione' => 'Palazzina A', 'numero_scale' => 1, 'numero_piani_fuori_terra' => 3, 'numero_piani_interrati' => 0, 'ha_piano_terra' => true, 'appartamenti_per_piano' => 2, 'attiva' => true, 'created_by' => $user->id, ]); $unita = UnitaImmobiliare::query()->create([ 'stabile_id' => $stabile->id, 'palazzina_id' => $palazzina->id, 'codice_unita' => 'A-1-0-01', 'palazzina' => 'A', 'scala' => '1', 'piano' => 0, 'interno' => '01', 'denominazione' => 'Unità Demo', 'tipo_unita' => 'appartamento', 'attiva' => true, 'unita_demo' => true, 'created_by' => $user->id, ]); $persona = Persona::query()->create([ 'tipologia' => 'fisica', 'cognome' => 'Demo', 'nome' => 'Condomino', 'codice_fiscale' => null, 'telefono_principale' => null, 'email_principale' => null, 'attivo' => true, ]); PersonaUnitaRelazione::query()->create([ 'persona_id' => $persona->id, 'unita_id' => $unita->id, 'tipo_relazione' => 'proprietario', 'quota_relazione' => 100, 'attivo' => true, 'riceve_comunicazioni' => true, 'riceve_convocazioni' => true, 'vota_assemblea' => true, ]); Notification::make() ->title('Stabile demo creato') ->body('Codice: ' . $codice) ->success() ->send(); StabileContext::setActiveStabileId($user, (int) $stabile->id); if (! empty($data['entra_subito'])) { $this->redirect(CruscottoStabile::getUrl(panel: 'admin-filament')); } }), ]) ->columns([ TextColumn::make('codice_stabile') ->label('Codice stabile') ->searchable() ->sortable() ->toggleable(), TextColumn::make('cod_stabile') ->label('Codice mnemonico') ->searchable() ->sortable() ->toggleable(), TextColumn::make('denominazione') ->label('Denominazione') ->searchable() ->sortable() ->wrap(), TextColumn::make('amministratore_corrente') ->label('Amministratore') ->getStateUsing(function (Stabile $record): string { $amministratore = $record->amministratore; if (! $amministratore instanceof Amministratore) { return '-'; } $label = trim((string) ($amministratore->denominazione_studio ?: $amministratore->nome_completo)); if ($label === '') { $label = 'Amministratore'; } return $label . ' (ID ' . (int) $amministratore->id . ')'; }) ->searchable(false) ->toggleable() ->wrap(), TextColumn::make('indirizzo_completo') ->label('Indirizzo') ->toggleable() ->wrap(), TextColumn::make('stato') ->label('Stato') ->sortable() ->toggleable(), IconColumn::make('attivo') ->label('Attivo') ->boolean() ->toggleable(), TextColumn::make('updated_at') ->label('Aggiornato') ->dateTime('d/m/Y H:i') ->sortable() ->toggleable(), ]) ->actions([ Action::make('scheda') ->label('Scheda') ->icon('heroicon-o-identification') ->url(fn(Stabile $record) => StabileScheda::getUrl([ 'record' => (int) $record->id, ], panel: 'admin-filament')), Action::make('modifica') ->label('Modifica') ->icon('heroicon-o-pencil-square') ->visible(function (Stabile $record): bool { $user = Auth::user(); if (! $user instanceof User) { return false; } if ($user->hasAnyRole(['super-admin', 'admin'])) { return true; } // Amministratore: può modificare solo i propri. $ammId = (int) ($user->amministratore?->id ?? 0); return $ammId > 0 && (int) ($record->amministratore_id ?? 0) === $ammId; }) ->form([ TextInput::make('codice_stabile') ->label('Codice stabile (legacy)') ->helperText('Codice cartella GESCON (es. 0021).') ->required() ->maxLength(20), TextInput::make('cod_stabile') ->label('Codice operatore (mnemonico)') ->helperText('Codice breve mostrato in Filament (es. 148).') ->maxLength(20), TextInput::make('denominazione')->label('Denominazione')->required()->maxLength(255), TextInput::make('indirizzo')->label('Indirizzo')->maxLength(255), TextInput::make('cap')->label('CAP')->maxLength(10), TextInput::make('citta')->label('Città')->maxLength(100), TextInput::make('provincia')->label('Provincia')->maxLength(5), Select::make('stato') ->label('Stato') ->options([ 'attivo' => 'Attivo', 'inattivo' => 'Inattivo', ]) ->default('attivo') ->required(), Checkbox::make('attivo')->label('Attivo')->default(true), ]) ->fillForm(fn(Stabile $record): array=> [ 'codice_stabile' => (string) ($record->codice_stabile ?? ''), 'cod_stabile' => (string) ($record->cod_stabile ?? ''), 'denominazione' => (string) ($record->denominazione ?? ''), 'indirizzo' => (string) ($record->indirizzo ?? ''), 'cap' => (string) ($record->cap ?? ''), 'citta' => (string) ($record->citta ?? ''), 'provincia' => (string) ($record->provincia ?? ''), 'stato' => (string) ($record->stato ?? 'attivo'), 'attivo' => (bool) ($record->attivo ?? true), ]) ->action(function (Stabile $record, array $data): void { $codice = trim((string) ($data['codice_stabile'] ?? '')); if ($codice === '') { Notification::make()->title('Codice mancante')->danger()->send(); return; } $codOperatore = trim((string) ($data['cod_stabile'] ?? '')); $codOperatore = $codOperatore !== '' ? $codOperatore : null; $dupe = Stabile::query() ->where('codice_stabile', $codice) ->whereKeyNot((int) $record->id) ->exists(); if ($dupe) { Notification::make()->title('Codice già presente')->danger()->send(); return; } $record->fill([ 'codice_stabile' => $codice, 'cod_stabile' => $codOperatore, 'denominazione' => (string) ($data['denominazione'] ?? $codice), 'indirizzo' => (string) ($data['indirizzo'] ?? ''), 'cap' => (string) ($data['cap'] ?? ''), 'citta' => (string) ($data['citta'] ?? ''), 'provincia' => (string) ($data['provincia'] ?? ''), 'stato' => (string) ($data['stato'] ?? 'attivo'), 'attivo' => (bool) ($data['attivo'] ?? true), ]); $record->save(); Notification::make()->title('Stabile aggiornato')->success()->send(); }), Action::make('piano_conti_standard') ->label('Piano conti standard') ->icon('heroicon-o-clipboard-document-list') ->requiresConfirmation() ->action(function (Stabile $record): void { if (! SchemaFacade::hasTable('piano_conti_condominio')) { Notification::make()->title('Tabella piano conti non disponibile')->warning()->send(); return; } $exists = PianoContiCondominio::query() ->where('stabile_id', $record->id) ->exists(); if ($exists) { Notification::make()->title('Piano conti già presente')->warning()->send(); return; } $standard = [ ['codice' => 'SP.AMM', 'descrizione' => 'Spese di amministrazione', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.PUL', 'descrizione' => 'Spese pulizia e igiene', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.ILL', 'descrizione' => 'Spese illuminazione', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.ACQ', 'descrizione' => 'Spese acqua', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.RIS', 'descrizione' => 'Spese riscaldamento', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.ASC', 'descrizione' => 'Spese ascensore', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.ASS', 'descrizione' => 'Assicurazione fabbricato', 'tipo_conto' => 'ECONOMICO_COSTO'], ['codice' => 'SP.MAN', 'descrizione' => 'Manutenzione ordinaria', 'tipo_conto' => 'ECONOMICO_COSTO'], ]; foreach ($standard as $row) { PianoContiCondominio::firstOrCreate( ['stabile_id' => $record->id, 'codice' => $row['codice']], ['descrizione' => $row['descrizione'], 'tipo_conto' => $row['tipo_conto']] ); } Notification::make()->title('Piano conti standard creato')->success()->send(); }), Action::make('trasferisci') ->label('Cambia amministratore') ->icon('heroicon-o-arrow-right-circle') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin']); }) ->form([ Select::make('to_amministratore_id') ->label('Nuovo amministratore') ->options(function (): array { return Amministratore::query() ->orderBy('id') ->get() ->mapWithKeys(function (Amministratore $a): array { $label = trim((string) ($a->denominazione_studio ?: $a->nome_completo)); if ($label === '') { $label = 'Amministratore'; } $code = (string) ($a->codice_univoco ?: $a->codice_amministratore ?: ''); $suffix = $code !== '' ? (' [' . $code . ']') : ''; return [$a->id => $label . $suffix . ' (ID ' . $a->id . ')']; }) ->all(); }) ->searchable() ->required(), Textarea::make('reason') ->label('Motivazione') ->helperText('Traccia il motivo del passaggio: riallineamento, presa in carico interna, correzione operativa, ecc.') ->rows(3) ->required() ->maxLength(5000), Checkbox::make('also_rubrica') ->label('Aggiorna anche Rubrica collegata (se presente)') ->default(true), ]) ->requiresConfirmation() ->action(function (Stabile $record, array $data): void { $user = Auth::user(); $toId = (int) ($data['to_amministratore_id'] ?? 0); if ($toId <= 0) { Notification::make()->title('Amministratore non valido')->danger()->send(); return; } if ((int) ($record->amministratore_id ?? 0) === $toId) { Notification::make()->title('Nessuna modifica')->body('Stabile già assegnato a questo amministratore.')->warning()->send(); return; } $dest = Amministratore::query()->find($toId); if (! $dest) { Notification::make()->title('Amministratore non trovato')->danger()->send(); return; } $transfer = app(StabileTransferService::class)->transfer( stabile: $record, destination: $dest, actor: $user instanceof User ? $user : null, alsoRubrica: (bool) ($data['also_rubrica'] ?? true), reason: trim((string) ($data['reason'] ?? '')), source: 'portal-superadmin', ipAddress: request()->ip(), meta: [ 'panel' => 'admin-filament', 'action' => 'stabili-archivio-trasferisci', ], ); Notification::make() ->title('Trasferimento completato') ->body('Nuovo amministratore: ' . ($dest->codice_univoco ?: ('ID ' . $dest->id)) . ' · storico #' . (int) $transfer->id) ->success() ->send(); }), Action::make('storico_trasferimenti') ->label('Storico passaggi') ->icon('heroicon-o-clock') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin']); }) ->modalHeading('Storico passaggi amministratore') ->modalSubmitAction(false) ->modalCancelActionLabel('Chiudi') ->modalContent(function (Stabile $record): View { $transfers = StabileAmministratoreTransfer::query() ->with(['fromAmministratore', 'toAmministratore', 'changedByUser']) ->where('stabile_id', (int) $record->id) ->latest('id') ->limit(25) ->get(); return view('filament.pages.gescon.partials.stabile-transfer-history', [ 'stabile' => $record, 'transfers' => $transfers, ]); }), Action::make('entra') ->label('Entra') ->icon('heroicon-o-arrow-right') ->action(function (Stabile $record): void { $user = Auth::user(); if (! $user instanceof User) { return; } if (! StabileContext::setActiveStabileId($user, (int) $record->id)) { Notification::make()->title('Accesso negato')->danger()->send(); return; } $this->redirect(CruscottoStabile::getUrl(panel: 'admin-filament')); }), Action::make('sync_gescon') ->label('Sync GESCON') ->icon('heroicon-o-arrow-path') ->visible(function (): bool { $user = Auth::user(); return $user instanceof User && $user->can('gescon-import.execute'); }) ->form([ TextInput::make('path') ->label('Percorso archivio GESCON') ->helperText('Cartella base che contiene //... e /dbc/Stabili.mdb') ->default('/mnt/gescon-archives/gescon') ->maxLength(500), TextInput::make('anno') ->label('Anno gestione (opzionale)') ->helperText('Inserisci un anno (es. 2025) o "all" per tutte le cartelle.'), Checkbox::make('dry')->label('Dry-run (solo simulazione)')->default(false), Checkbox::make('with_unita')->label('Includi unità immobiliari')->default(false), Checkbox::make('with_soggetti')->label('Includi anagrafiche (soggetti)')->default(false), Checkbox::make('with_diritti')->label('Includi diritti (proprietà/inquilini)')->default(false), Checkbox::make('with_millesimi')->label('Includi tabelle millesimali')->default(false), Checkbox::make('with_voci')->label('Includi voci (piano dei conti / voci spesa)')->default(false), Checkbox::make('with_gestioni')->label('Includi gestioni')->default(false), Checkbox::make('with_banche')->label('Includi banche')->default(false), ]) ->requiresConfirmation() ->action(function (Stabile $record, array $data): void { $user = Auth::user(); if (! $user instanceof User) { Notification::make()->title('Utente non valido')->danger()->send(); return; } $amm = $user->amministratore; if (! $amm) { Notification::make() ->title('Amministratore non associato') ->body('Impossibile eseguire sync senza amministratore associato all\'utente.') ->danger() ->send(); return; } $legacyCode = trim((string) ($record->codice_stabile ?? '')); if ($legacyCode === '') { $legacyCode = trim((string) ($record->codice_interno ?? '')); } if ($legacyCode === '') { Notification::make() ->title('Codice legacy mancante') ->body('Lo stabile non ha codice_stabile/codice_interno: non so quale cartella GESCON usare.') ->danger() ->send(); return; } $svc = new EssentialImportService(); $res = $svc->syncStabile($amm, $legacyCode, [ 'path' => (string) ($data['path'] ?? '/mnt/gescon-archives/gescon'), 'anno' => isset($data['anno']) && trim((string) $data['anno']) !== '' ? trim((string) $data['anno']) : null, 'with_unita' => (bool) ($data['with_unita'] ?? false), 'with_soggetti' => (bool) ($data['with_soggetti'] ?? false), 'with_diritti' => (bool) ($data['with_diritti'] ?? false), 'with_millesimi' => (bool) ($data['with_millesimi'] ?? false), 'with_voci' => (bool) ($data['with_voci'] ?? false), 'with_gestioni' => (bool) ($data['with_gestioni'] ?? false), 'with_banche' => (bool) ($data['with_banche'] ?? false), 'dry' => (bool) ($data['dry'] ?? false), ]); $title = ($res['changed'] ?? false) ? 'Sync completato' : 'Nessuna variazione (hash invariato)'; Notification::make() ->title($title) ->body('Stabile: ' . $legacyCode) ->success() ->send(); }), Action::make('elimina') ->label('Elimina') ->icon('heroicon-o-trash') ->color('danger') ->visible(function (Stabile $record): bool { $user = Auth::user(); if (! $user instanceof User) { return false; } if ($user->hasAnyRole(['super-admin', 'admin'])) { return true; } // Amministratore: può eliminare solo i propri, e solo se non ci sono dipendenze. $ammId = (int) ($user->amministratore?->id ?? 0); return $ammId > 0 && (int) ($record->amministratore_id ?? 0) === $ammId; }) ->requiresConfirmation() ->action(function (Stabile $record): void { $checks = [ 'Palazzine' => fn() => $record->palazzine()->exists(), 'Unità immobiliari' => fn() => $record->unitaImmobiliari()->exists(), 'Tabelle millesimali' => fn() => $record->tabelleMillesimali()->exists(), 'Voci spesa' => fn() => $record->vociSpesa()->exists(), 'Gestioni contabili' => fn() => $record->gestioniContabili()->exists(), 'Gestioni condominiali' => fn() => $record->gestioniCondominiali()->exists(), 'Documenti collegati' => fn() => $record->documentiCollegati()->exists(), 'Dati bancari' => fn() => $record->datiBancari()->exists(), 'Tickets' => fn() => $record->tickets()->exists(), ]; $blocked = []; foreach ($checks as $label => $fn) { try { if ($fn()) { $blocked[] = $label; } } catch (\Throwable) { // ignore } } if (! empty($blocked)) { Notification::make() ->title('Eliminazione bloccata') ->body('Lo stabile ha dati collegati: ' . implode(', ', $blocked)) ->danger() ->send(); return; } $record->delete(); Notification::make()->title('Stabile eliminato')->success()->send(); }), ]); } }