From f3006544799425a26fa8262bcb6767436af8965e Mon Sep 17 00:00:00 2001 From: michele Date: Sat, 11 Apr 2026 19:15:07 +0000 Subject: [PATCH] Fix supplier settings page and nominativi legacy query --- .../Pages/Condomini/NominativiStabile.php | 75 +++-- .../Pages/Fornitore/ImpostazioniArchivio.php | 259 ++++++++++++++++++ .../fornitore/impostazioni-archivio.blade.php | 114 ++++++++ 3 files changed, 428 insertions(+), 20 deletions(-) create mode 100644 app/Filament/Pages/Fornitore/ImpostazioniArchivio.php create mode 100644 resources/views/filament/pages/fornitore/impostazioni-archivio.blade.php diff --git a/app/Filament/Pages/Condomini/NominativiStabile.php b/app/Filament/Pages/Condomini/NominativiStabile.php index a866e8a..04a50b1 100644 --- a/app/Filament/Pages/Condomini/NominativiStabile.php +++ b/app/Filament/Pages/Condomini/NominativiStabile.php @@ -372,11 +372,13 @@ protected function getTableQuery(): Builder return $this->buildDomainFallbackQuery((int) $activeStabileId); } + $legacyTable = 'vw_legacy_condomin_nominativi'; + $base = LegacyCondominNominativo::query() - ->where('cod_stabile', $codStabile) + ->where($legacyTable . '.cod_stabile', $codStabile) ->where(function (Builder $q): void { - $q->whereRaw("TRIM(COALESCE(nom_cond, '')) <> ''") - ->orWhereRaw("TRIM(COALESCE(inquil_nome, '')) <> ''"); + $q->whereRaw("TRIM(COALESCE(vw_legacy_condomin_nominativi.nom_cond, '')) <> ''") + ->orWhereRaw("TRIM(COALESCE(vw_legacy_condomin_nominativi.inquil_nome, '')) <> ''"); }); $latestPerUnit = DB::connection('gescon_import') @@ -393,13 +395,13 @@ protected function getTableQuery(): Builder ->on('mx.interno', '=', 'vw_legacy_condomin_nominativi.interno') ->on('mx.legacy_year', '=', 'vw_legacy_condomin_nominativi.legacy_year'); }) - ->orderBy('scala') + ->orderBy($legacyTable . '.scala') // Ordine richiesto: per interno (con fallback robusto) - ->orderByRaw("CASE WHEN interno IS NULL OR interno = '' THEN 1 ELSE 0 END") - ->orderByRaw("CASE WHEN interno REGEXP '^[0-9]+' THEN CAST(interno AS UNSIGNED) ELSE 999999 END") - ->orderBy('interno') - ->orderBy('cod_cond') - ->orderBy('id'); + ->orderByRaw("CASE WHEN vw_legacy_condomin_nominativi.interno IS NULL OR vw_legacy_condomin_nominativi.interno = '' THEN 1 ELSE 0 END") + ->orderByRaw("CASE WHEN vw_legacy_condomin_nominativi.interno REGEXP '^[0-9]+' THEN CAST(vw_legacy_condomin_nominativi.interno AS UNSIGNED) ELSE 999999 END") + ->orderBy($legacyTable . '.interno') + ->orderBy($legacyTable . '.cod_cond') + ->orderBy($legacyTable . '.id'); } protected function hasLegacyNominativiForStabile(string $codStabile): bool @@ -566,6 +568,7 @@ public function table(Table $table): Table { $user = Auth::user(); $activeStabileId = $user instanceof User ? (int) (StabileContext::resolveActiveStabileId($user) ?: 0): 0; + $legacyTable = 'vw_legacy_condomin_nominativi'; $codStabile = $activeStabileId > 0 ? $this->resolveLegacyStabileCode((int) $activeStabileId) @@ -611,7 +614,7 @@ public function table(Table $table): Table return $query; } - return $query->where('legacy_year', $data['value']); + return $query->where($table . '.legacy_year', $data['value']); }), SelectFilter::make('scala') ->label('Scala') @@ -646,6 +649,18 @@ public function table(Table $table): Table ->pluck('scala', 'scala') ->all(); }) + ->query(function (Builder $query, array $data) use ($legacyTable): Builder { + if (! isset($data['value']) || $data['value'] === '') { + return $query; + } + + $model = $query->getModel(); + if ($model instanceof LegacyCondominNominativo) { + return $query->where($legacyTable . '.scala', $data['value']); + } + + return $query->where('scala', $data['value']); + }) ->searchable(), SelectFilter::make('piano') @@ -719,12 +734,33 @@ public function table(Table $table): Table TextColumn::make('scala') ->label('Sc.') - ->sortable(), + ->sortable(query: function (Builder $query, string $direction) use ($legacyTable): Builder { + $model = $query->getModel(); + if ($model instanceof LegacyCondominNominativo) { + return $query->orderBy($legacyTable . '.scala', $direction); + } + + return $query->orderBy('scala', $direction); + }), TextColumn::make('interno') ->label('Int.') - ->sortable() - ->searchable(), + ->sortable(query: function (Builder $query, string $direction) use ($legacyTable): Builder { + $model = $query->getModel(); + if ($model instanceof LegacyCondominNominativo) { + return $query->orderBy($legacyTable . '.interno', $direction); + } + + return $query->orderBy('interno', $direction); + }) + ->searchable(query: function (Builder $query, string $search) use ($activeStabileId, $legacyTable): Builder { + $model = $query->getModel(); + if ($model instanceof LegacyCondominNominativo) { + return $query->where($legacyTable . '.interno', 'like', '%' . $search . '%'); + } + + return $this->applyDomainNominativiSearch($query, $search, $activeStabileId); + }), TextColumn::make('nom_cond') ->label('Condomino') @@ -732,10 +768,10 @@ public function table(Table $table): Table $titolo = $this->resolveTitoloOwnerForRow($record); return $this->formatLegacyDenominazione($titolo, $record->nom_cond ?? (string) $state); }) - ->searchable(query: function (Builder $query, string $search) use ($activeStabileId): Builder { + ->searchable(query: function (Builder $query, string $search) use ($activeStabileId, $legacyTable): Builder { $model = $query->getModel(); if ($model instanceof LegacyCondominNominativo) { - return $query->where('nom_cond', 'like', '%' . $search . '%'); + return $query->where($legacyTable . '.nom_cond', 'like', '%' . $search . '%'); } return $this->applyDomainNominativiSearch($query, $search, $activeStabileId); @@ -788,10 +824,10 @@ public function table(Table $table): Table $titolo = $this->resolveTitoloInquilinoForRow($record); return $this->formatLegacyDenominazione($titolo, $v); }) - ->searchable(query: function (Builder $query, string $search) use ($activeStabileId): Builder { + ->searchable(query: function (Builder $query, string $search) use ($activeStabileId, $legacyTable): Builder { $model = $query->getModel(); if ($model instanceof LegacyCondominNominativo) { - return $query->where('inquil_nome', 'like', '%' . $search . '%'); + return $query->where($legacyTable . '.inquil_nome', 'like', '%' . $search . '%'); } return $this->applyDomainNominativiSearch($query, $search, $activeStabileId); @@ -847,8 +883,7 @@ public function table(Table $table): Table return RubricaUniversaleScheda::getUrl(['record' => $rubricaId], panel: 'admin-filament'); }, shouldOpenInNewTab: true) - ->visible(fn($record): bool => (bool) $this->resolveRubricaIdForRow($record)), - ]) - ->defaultSort('scala', 'asc'); + ->visible(fn($record): bool => (bool) $this->resolveRubricaIdForRow($record)), + ]); } } diff --git a/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php b/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php new file mode 100644 index 0000000..a9fd820 --- /dev/null +++ b/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php @@ -0,0 +1,259 @@ +> */ + public array $archiveFolders = []; + + /** @var array> */ + public array $moduleRows = []; + + /** @var array|null */ + public ?array $googleWorkspaceStatus = null; + + public static function canAccess(): bool + { + $user = auth()->user(); + + return $user instanceof User + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); + } + + public function mount(): void + { + [$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true); + + if (! $fornitore instanceof Fornitore) { + $this->missingAdminContext = true; + return; + } + + $this->fornitore = $fornitore; + $this->refreshArchiveSummary(); + } + + public function saveUploadedTecnoRepairMdb(): void + { + if (! $this->fornitore instanceof Fornitore) { + return; + } + + $upload = $this->tecnorepairMdbUpload; + if (! $upload instanceof TemporaryUploadedFile) { + Notification::make()->title('Seleziona prima un file MDB')->warning()->send(); + return; + } + + $extension = strtolower((string) $upload->getClientOriginalExtension()); + if ($extension !== 'mdb') { + Notification::make()->title('Carica un file .mdb')->warning()->send(); + return; + } + + $relativeBase = $this->getArchiveRelativeBase(); + if ($relativeBase === null) { + Notification::make()->title('Archivio fornitore non disponibile')->danger()->send(); + return; + } + + $filename = 'TecnoRepairDB-' . now()->format('Ymd-His') . '.mdb'; + $stored = $upload->storeAs($relativeBase . '/tecnorepair/imports', $filename, 'local'); + + $this->tecnorepairMdbPath = storage_path('app/' . $stored); + $this->tecnorepairMdbUpload = null; + $this->refreshArchiveSummary(); + + Notification::make()->title('Archivio TecnoRepair caricato')->body($filename)->success()->send(); + } + + public function importTecnoRepairFromArchive(bool $dryRun = false): void + { + if (! $this->fornitore instanceof Fornitore) { + return; + } + + $mdbPath = trim($this->tecnorepairMdbPath); + if ($mdbPath === '' || ! is_file($mdbPath)) { + Notification::make()->title('Percorso MDB non valido')->warning()->send(); + return; + } + + $adminId = (int) ($this->fornitore->amministratore_id ?? 0); + if ($adminId <= 0) { + Notification::make()->title('Amministratore del fornitore non trovato')->danger()->send(); + return; + } + + try { + Artisan::call('tecnorepair:import-legacy', [ + 'amministratore' => $adminId, + '--mdb' => $mdbPath, + '--fornitore-id' => (int) $this->fornitore->id, + '--force-primary' => true, + '--dry-run' => $dryRun, + ]); + } catch (\Throwable $e) { + Notification::make()->title('Import TecnoRepair fallito')->body($e->getMessage())->danger()->send(); + return; + } + + $this->lastImportOutput = trim(Artisan::output()); + $this->refreshArchiveSummary(); + + Notification::make() + ->title($dryRun ? 'Simulazione TecnoRepair completata' : 'Import TecnoRepair completato') + ->body($this->lastImportOutput !== '' ? $this->lastImportOutput : 'Operazione completata.') + ->success() + ->send(); + } + + public function getSchedaFornitoreUrl(): string + { + return FornitoreScheda::getUrl(['record' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament'); + } + + public function getTicketsUrl(): string + { + return TicketOperativi::getUrl(['fornitore' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament'); + } + + public function getProdottiUrl(): string + { + return ProdottiCatalogo::getUrl(['fornitore' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament'); + } + + public function getGoogleConnectUrl(): string + { + return route('oauth.google.redirect'); + } + + public function getGoogleDisconnectUrl(): string + { + return route('oauth.google.disconnect'); + } + + private function refreshArchiveSummary(): void + { + $relativeBase = $this->getArchiveRelativeBase(); + $absoluteBase = $relativeBase ? storage_path('app/' . $relativeBase) : null; + + if ($relativeBase !== null) { + foreach ([ + 'documenti', + 'rubrica', + 'fe', + 'prodotti', + 'comunicazioni', + 'tecnorepair/imports', + 'tecnorepair/allegati', + 'temp/upload', + 'temp/processing', + 'logs', + ] as $folder) { + Storage::disk('local')->makeDirectory($relativeBase . '/' . $folder); + } + } + + $this->archiveFolders = $relativeBase === null ? [] : [ + ['label' => 'Base archivio', 'path' => (string) $absoluteBase], + ['label' => 'Rubrica fornitore', 'path' => storage_path('app/' . $relativeBase . '/rubrica')], + ['label' => 'FE fornitore', 'path' => storage_path('app/' . $relativeBase . '/fe')], + ['label' => 'Prodotti e catalogo', 'path' => storage_path('app/' . $relativeBase . '/prodotti')], + ['label' => 'Comunicazioni / Post-it', 'path' => storage_path('app/' . $relativeBase . '/comunicazioni')], + ['label' => 'TecnoRepair import', 'path' => storage_path('app/' . $relativeBase . '/tecnorepair/imports')], + ]; + + if ($relativeBase !== null && $this->tecnorepairMdbPath === '') { + $latest = collect(Storage::disk('local')->files($relativeBase . '/tecnorepair/imports')) + ->filter(fn(string $path): bool => str_ends_with(strtolower($path), '.mdb')) + ->sortDesc() + ->first(); + + if (is_string($latest) && $latest !== '') { + $this->tecnorepairMdbPath = storage_path('app/' . $latest); + } + } + + $stats = $this->fornitore?->tecnorepair_stats ?? ['total' => 0, 'open' => 0, 'closed' => 0, 'serials' => 0]; + $this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus(); + $this->moduleRows = [ + ['module' => 'Rubrica fornitore', 'status' => 'pronto', 'note' => 'Vista fornitore filtrata ma agganciata all\'anagrafica unica.'], + ['module' => 'Google Workspace', 'status' => data_get($this->googleWorkspaceStatus, 'connected', false) ? 'collegato' : 'da collegare', 'note' => 'Rubrica e sincronizzazioni smartphone riusano l\'account Google del contesto amministratore.'], + ['module' => 'TecnoRepair MDB', 'status' => ((int) ($stats['total'] ?? 0) > 0) ? 'importato' : 'da importare', 'note' => 'Schede: ' . (int) ($stats['total'] ?? 0) . ' · aperte: ' . (int) ($stats['open'] ?? 0) . ' · chiuse: ' . (int) ($stats['closed'] ?? 0)], + ['module' => 'Catalogo fornitore', 'status' => 'attivo', 'note' => 'Il catalogo vero resta nella pagina Prodotti.'], + ['module' => 'Comunicazioni / Post-it', 'status' => 'predisposto', 'note' => 'Cartelle e contesto archivio pronti per agganciare chiamate, post-it ed email.'], + ['module' => 'FE e documenti', 'status' => 'predisposto', 'note' => 'Archivio separato per FE, documenti e flussi contabili del fornitore.'], + ]; + } + + /** + * @return array|null + */ + private function resolveGoogleWorkspaceStatus(): ?array + { + $amministratore = $this->fornitore?->amministratore; + if (! $amministratore) { + return null; + } + + $oauth = (array) (($amministratore->impostazioni['google']['oauth'] ?? null) ?: []); + + return [ + 'connected' => (bool) ($oauth['connected'] ?? false), + 'email' => (string) ($oauth['email'] ?? ''), + 'name' => (string) ($oauth['name'] ?? ''), + 'connected_at' => (string) ($oauth['connected_at'] ?? ''), + ]; + } + + private function getArchiveRelativeBase(): ?string + { + if (! $this->fornitore instanceof Fornitore) { + return null; + } + + return ArchivioPaths::fornitoreBase($this->fornitore, $this->fornitore->amministratore); + } +} diff --git a/resources/views/filament/pages/fornitore/impostazioni-archivio.blade.php b/resources/views/filament/pages/fornitore/impostazioni-archivio.blade.php new file mode 100644 index 0000000..6c62ade --- /dev/null +++ b/resources/views/filament/pages/fornitore/impostazioni-archivio.blade.php @@ -0,0 +1,114 @@ + +
+
+
+
+
Impostazioni fornitore e archivio operativo
+
+ @if($this->fornitore) + Qui concentriamo archivio fornitore, moduli attivabili, import TecnoRepair e collegamento Google di {{ $this->fornitore->ragione_sociale ?? ('Fornitore #' . $this->fornitore->id) }}. + @else + Seleziona un fornitore per aprire questa vista. + @endif +
+
+ +
+
+ + @if($this->missingAdminContext) +
+ Questa vista richiede un fornitore selezionato. +
+ @else +
+
+
+
Archivio fornitore
+
Il fornitore ora ha una struttura archivio dedicata, parallela a quella degli stabili, pronta per rubrica, FE, prodotti, comunicazioni e import tecnici.
+ +
+ @foreach($archiveFolders as $row) +
+
{{ $row['label'] }}
+
{{ $row['path'] }}
+
+ @endforeach +
+
+ +
+
TecnoRepair MDB
+
Puoi caricare un archivio MDB dalla pagina web oppure indicare un percorso già disponibile sul server, poi lanciare la sincronizzazione verso il fornitore.
+ +
+ + +
+ +
+ Salva MDB in archivio fornitore + Importa TecnoRepair + Simula import +
+ + @if(filled($lastImportOutput)) +
{{ $lastImportOutput }}
+ @endif +
+
+ +
+
+
Moduli fornitore
+
+ @foreach($moduleRows as $row) +
+
+
{{ $row['module'] }}
+ {{ $row['status'] }} +
+
{{ $row['note'] }}
+
+ @endforeach +
+
+ +
+
Google Workspace
+ @if($googleWorkspaceStatus) +
+
Stato: {{ $googleWorkspaceStatus['connected'] ? 'Collegato' : 'Non collegato' }}
+
Account: {{ $googleWorkspaceStatus['email'] !== '' ? $googleWorkspaceStatus['email'] : '-' }}
+
Nome: {{ $googleWorkspaceStatus['name'] !== '' ? $googleWorkspaceStatus['name'] : '-' }}
+
Ultimo collegamento: {{ $googleWorkspaceStatus['connected_at'] !== '' ? $googleWorkspaceStatus['connected_at'] : '-' }}
+
+
+ Collega / aggiorna Google + @if($googleWorkspaceStatus['connected']) +
+ @csrf + +
+ @endif +
+
Questo collegamento prepara la sincronizzazione rubrica lato fornitore e l'uso dei dati anche sul cellulare tramite l'account Google già governato dal backend.
+ @else +
Contesto amministratore Google non disponibile per questo fornitore.
+ @endif +
+
+
+ @endif +
+