From 687e5c4f4540c573b23321c173c12adb4348c733 Mon Sep 17 00:00:00 2001 From: michele Date: Fri, 28 Aug 2026 17:23:44 +0200 Subject: [PATCH] fix(fornitori-ui): unify compact icon bar header on FornitoreScheda and update ASCII wireframes for Stabili and Fornitori (task-d095b0e23b) --- app/Filament/Pages/Gescon/FornitoreScheda.php | 1238 +---------------- .../pages/gescon/fornitore-scheda.blade.php | 53 +- .../ui-wireframes/fornitori-anagrafica.md | 56 +- skill-netgescon/ui-wireframes/stabili.md | 15 +- 4 files changed, 50 insertions(+), 1312 deletions(-) diff --git a/app/Filament/Pages/Gescon/FornitoreScheda.php b/app/Filament/Pages/Gescon/FornitoreScheda.php index 9b7cca6..abb9699 100755 --- a/app/Filament/Pages/Gescon/FornitoreScheda.php +++ b/app/Filament/Pages/Gescon/FornitoreScheda.php @@ -1159,1242 +1159,6 @@ private function applySupplierIdentityFilterToFeQuery(\Illuminate\Database\Eloqu protected function getHeaderActions(): array { - return [ - Action::make('apri_rubrica') - ->label('Apri anagrafica unica') - ->icon('heroicon-o-user') - ->visible(fn(): bool => (int) ($this->fornitore->rubrica_id ?? 0) > 0) - ->url(fn() => RubricaUniversaleScheda::getUrl([ - 'record' => (int) $this->fornitore->rubrica_id, - ], panel: 'admin-filament')), - - Action::make('sync_rubrica') - ->label('Sincronizza Rubrica') - ->icon('heroicon-o-arrow-path') - ->action(fn() => $this->syncRubricaFromFornitore()), - - Action::make('importa_tag_legacy') - ->label('Importa TAG legacy') - ->icon('heroicon-o-tag') - ->action(fn() => $this->importLegacyTags()), - - Action::make('nuovo_prodotto') - ->label('Nuovo prodotto') - ->icon('heroicon-o-cube') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('name')->label('Nome prodotto')->required()->maxLength(255), - TextInput::make('brand')->label('Marca')->maxLength(255), - TextInput::make('model')->label('Modello')->maxLength(255), - TextInput::make('color_label')->label('Colore')->maxLength(255), - TextInput::make('variant_label')->label('Variante')->maxLength(255), - Toggle::make('track_serials')->label('Gestione seriali')->default(false), - TextInput::make('vendor_sku')->label('Codice fornitore')->maxLength(255), - TextInput::make('ean_code')->label('EAN / GTIN')->maxLength(32), - TextInput::make('qr_code')->label('QR / payload')->maxLength(255), - ]) - ->action(function (array $data): void { - $service = app(FornitoreProductCatalogService::class); - $resolved = $service->resolveOrCreateProduct($this->fornitore, [ - 'name' => (string) ($data['name'] ?? ''), - 'brand' => (string) ($data['brand'] ?? ''), - 'model' => (string) ($data['model'] ?? ''), - 'color_label' => (string) ($data['color_label'] ?? ''), - 'variant_label' => (string) ($data['variant_label'] ?? ''), - 'track_serials' => (bool) ($data['track_serials'] ?? false), - 'type' => 'product', - 'unit_measure' => 'pz', - ]); - - if (filled($data['vendor_sku'] ?? null)) { - $service->upsertIdentifier($resolved['product'], [ - 'fornitore_id' => (int) $this->fornitore->id, - 'code_type' => 'vendor_sku', - 'code_role' => 'supplier', - 'code_value' => (string) $data['vendor_sku'], - 'source' => 'manual', - ]); - } - - if (filled($data['ean_code'] ?? null)) { - $service->upsertIdentifier($resolved['product'], [ - 'fornitore_id' => null, - 'code_type' => 'ean', - 'code_role' => 'barcode', - 'code_value' => (string) $data['ean_code'], - 'source' => 'manual', - ]); - } - - if (filled($data['qr_code'] ?? null)) { - $service->upsertIdentifier($resolved['product'], [ - 'fornitore_id' => null, - 'code_type' => 'qr', - 'code_role' => 'barcode', - 'code_value' => (string) $data['qr_code'], - 'source' => 'manual', - ]); - } - - $this->refreshOperationalBoxes(); - Notification::make()->title('Prodotto salvato')->success()->send(); - }), - - Action::make('estrai_prodotti_fe') - ->label('Estrai prodotti da FE') - ->icon('heroicon-o-document-magnifying-glass') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('limit')->label('Numero FE da analizzare')->numeric()->default(40)->required(), - ]) - ->action(function (array $data): void { - $user = Auth::user(); - if (! $user instanceof User) { - Notification::make()->title('Utente non valido')->danger()->send(); - return; - } - - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId) { - Notification::make()->title('Seleziona uno stabile attivo')->warning()->send(); - return; - } - - $stats = app(FornitoreProductCatalogService::class)->extractFromFattureElettroniche( - $this->fornitore, - (int) $stabileId, - max(1, (int) ($data['limit'] ?? 40)), - ); - - $this->refreshOperationalBoxes(); - Notification::make() - ->title('Estrazione prodotti completata') - ->body('FE: ' . $stats['fatture'] . ' | righe: ' . $stats['lines'] . ' | nuovi prodotti: ' . $stats['products'] . ' | codici: ' . $stats['identifiers']) - ->success() - ->send(); - }), - - Action::make('importa_listino_csv') - ->label('Importa listino CSV') - ->icon('heroicon-o-arrow-down-tray') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('csv_path') - ->label('Percorso CSV listino') - ->default(fn(): string => $this->suggestWholesaleCsvPath()) - ->required(), - TextInput::make('limit')->label('Limite righe')->numeric()->default(0), - Toggle::make('dry_run')->label('Solo simulazione')->default(false), - ]) - ->action(function (array $data): void { - try { - $stats = app(FornitoreProductCatalogService::class)->importWholesaleCsv( - $this->fornitore, - (string) ($data['csv_path'] ?? ''), - max(0, (int) ($data['limit'] ?? 0)), - (bool) ($data['dry_run'] ?? false), - ); - } catch (\Throwable $e) { - Notification::make()->title('Import listino fallito')->body($e->getMessage())->danger()->send(); - return; - } - - $this->refreshOperationalBoxes(); - - Notification::make() - ->title((bool) ($data['dry_run'] ?? false) ? 'Simulazione listino completata' : 'Import listino completato') - ->body( - 'righe: ' . $stats['rows'] - . ' | nuovi: ' . $stats['products'] - . ' | aggiornati: ' . $stats['updated'] - . ' | codici: ' . $stats['identifiers'] - . ' | offerte: ' . ($stats['offers'] ?? 0) - . ' | link sorgente interni: ' . ($stats['private_links'] ?? 0) - . ' | media remoti rimossi: ' . ($stats['remote_media_pruned'] ?? 0) - ) - ->success() - ->send(); - }), - - Action::make('aggiorna_listino_remoto') - ->label('Aggiorna listino remoto') - ->icon('heroicon-o-globe-alt') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('remote_csv_url') - ->label('URL CSV remoto') - ->password() - ->revealable() - ->default((string) data_get($this->fornitore->operational_config_safe, 'catalog_import.remote_csv_url', '')) - ->required(), - TextInput::make('limit')->label('Limite righe')->numeric()->default(0), - Toggle::make('dry_run')->label('Solo simulazione')->default(true), - ]) - ->action(function (array $data): void { - try { - $stats = app(FornitoreProductCatalogService::class)->importWholesaleCsvFromUrl( - $this->fornitore, - (string) ($data['remote_csv_url'] ?? ''), - max(0, (int) ($data['limit'] ?? 0)), - (bool) ($data['dry_run'] ?? false), - ); - } catch (\Throwable $e) { - Notification::make()->title('Aggiornamento remoto fallito')->body($e->getMessage())->danger()->send(); - return; - } - - $this->refreshOperationalBoxes(); - - Notification::make() - ->title((bool) ($data['dry_run'] ?? false) ? 'Simulazione remoto completata' : 'Aggiornamento remoto completato') - ->body( - 'righe: ' . $stats['rows'] - . ' | nuovi: ' . $stats['products'] - . ' | aggiornati: ' . $stats['updated'] - . ' | codici: ' . $stats['identifiers'] - . ' | offerte: ' . ($stats['offers'] ?? 0) - . ' | media: ' . ($stats['media'] ?? 0) - ) - ->success() - ->send(); - }), - - Action::make('importa_listino_xlsx') - ->label('Importa listino XLSX') - ->icon('heroicon-o-table-cells') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('xlsx_path') - ->label('Percorso XLSX listino') - ->default(fn(): string => $this->suggestWholesaleXlsxPath()) - ->required(), - TextInput::make('limit')->label('Limite righe prodotto')->numeric()->default(0), - Toggle::make('dry_run')->label('Solo simulazione')->default(true), - ]) - ->action(function (array $data): void { - try { - $stats = app(FornitoreProductCatalogService::class)->importWholesaleXlsx( - $this->fornitore, - (string) ($data['xlsx_path'] ?? ''), - max(0, (int) ($data['limit'] ?? 0)), - (bool) ($data['dry_run'] ?? false), - ); - } catch (\Throwable $e) { - Notification::make()->title('Import listino XLSX fallito')->body($e->getMessage())->danger()->send(); - return; - } - - $this->refreshOperationalBoxes(); - - Notification::make() - ->title((bool) ($data['dry_run'] ?? false) ? 'Simulazione XLSX completata' : 'Import XLSX completato') - ->body( - 'righe prodotto: ' . $stats['rows'] - . ' | nuovi: ' . $stats['products'] - . ' | aggiornati: ' . $stats['updated'] - . ' | codici: ' . $stats['identifiers'] - . ' | offerte: ' . ($stats['offers'] ?? 0) - . ' | saltati: ' . ($stats['skipped'] ?? 0) - ) - ->success() - ->send(); - }), - - Action::make('impostazioni_listino') - ->label('Parametri listino') - ->icon('heroicon-o-cog-6-tooth') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('csv_path') - ->label('Percorso CSV predefinito') - ->default(function (): string { - $configPath = trim((string) data_get($this->fornitore->operational_config_safe, 'catalog_import.wholesale_csv_path', '')); - - return $configPath !== '' ? $configPath : $this->suggestWholesaleCsvPath(); - }) - ->required(), - TextInput::make('default_limit') - ->label('Limite righe predefinito') - ->numeric() - ->default((int) data_get($this->fornitore->operational_config_safe, 'catalog_import.default_limit', 0)), - TextInput::make('remote_csv_url') - ->label('URL CSV remoto') - ->password() - ->revealable() - ->default((string) data_get($this->fornitore->operational_config_safe, 'catalog_import.remote_csv_url', '')), - Select::make('publication_mode') - ->label('Modalità pubblicazione') - ->options([ - 'internal_only' => 'Solo interno', - 'internal_catalog' => 'Catalogo interno', - 'referral_ready' => 'Pronto per referral', - ]) - ->default((string) data_get($this->fornitore->operational_config_safe, 'catalog_import.publication_mode', 'internal_catalog')) - ->required(), - Toggle::make('auto_internalize_assets') - ->label('Internalizza automaticamente immagini e documenti') - ->default((bool) data_get($this->fornitore->operational_config_safe, 'catalog_import.auto_internalize_assets', true)), - Toggle::make('parse_category_hierarchy') - ->label('Conserva gerarchia categorie') - ->default((bool) data_get($this->fornitore->operational_config_safe, 'catalog_import.parse_category_hierarchy', true)), - Toggle::make('hide_supplier_references_public') - ->label('Nascondi riferimenti fornitore nelle viste pubbliche') - ->default((bool) data_get($this->fornitore->operational_config_safe, 'catalog_import.hide_supplier_references_public', true)), - Toggle::make('track_serials_from_fe') - ->label('Aggancia seriali da FE') - ->default((bool) data_get($this->fornitore->operational_config_safe, 'catalog_import.track_serials_from_fe', true)), - ]) - ->action(function (array $data): void { - $config = is_array($this->fornitore->operational_config ?? null) ? $this->fornitore->operational_config : []; - $config['catalog_import'] = array_merge( - is_array($config['catalog_import'] ?? null) ? $config['catalog_import'] : [], - [ - 'wholesale_csv_path' => trim((string) ($data['csv_path'] ?? '')), - 'default_limit' => max(0, (int) ($data['default_limit'] ?? 0)), - 'remote_csv_url' => trim((string) ($data['remote_csv_url'] ?? '')), - 'publication_mode' => (string) ($data['publication_mode'] ?? 'internal_catalog'), - 'auto_internalize_assets' => (bool) ($data['auto_internalize_assets'] ?? true), - 'parse_category_hierarchy' => (bool) ($data['parse_category_hierarchy'] ?? true), - 'hide_supplier_references_public' => (bool) ($data['hide_supplier_references_public'] ?? true), - 'track_serials_from_fe' => (bool) ($data['track_serials_from_fe'] ?? true), - ] - ); - - $this->fornitore->operational_config = $config; - $this->fornitore->save(); - $this->fornitore->refresh(); - $this->refreshOperationalBoxes(); - - Notification::make()->title('Parametri listino salvati')->success()->send(); - }), - - Action::make('scarica_asset_catalogo') - ->label('Internalizza asset') - ->icon('heroicon-o-photo') - ->visible(fn(): bool => Schema::hasTable('products')) - ->form([ - TextInput::make('limit')->label('Limite prodotti')->numeric()->default(50)->required(), - Toggle::make('dry_run')->label('Solo simulazione')->default(false), - ]) - ->action(function (array $data): void { - $stats = app(ProductAssetIngestionService::class)->ingestForFornitore( - $this->fornitore, - max(1, (int) ($data['limit'] ?? 50)), - (bool) ($data['dry_run'] ?? false), - ); - - $this->refreshOperationalBoxes(); - Notification::make() - ->title((bool) ($data['dry_run'] ?? false) ? 'Simulazione asset completata' : 'Asset catalogo internalizzati') - ->body( - 'prodotti: ' . $stats['products'] - . ' | immagini: ' . $stats['images'] - . ' | documenti: ' . $stats['documents'] - . ' | esistenti: ' . $stats['skipped_existing'] - . ' | non supportati: ' . $stats['skipped_unsupported'] - . ' | errori: ' . $stats['failed'] - ) - ->success() - ->send(); - }), - - Action::make('genera_referral_amazon') - ->label('Prepara link Amazon') - ->icon('heroicon-o-shopping-bag') - ->visible(fn(): bool => Schema::hasTable('products') && Schema::hasTable('product_offers')) - ->form([ - TextInput::make('associate_tag')->label('Referral tag Amazon')->default((string) config('catalog.amazon.associate_tag', ''))->maxLength(64), - Select::make('locale')->label('Marketplace')->options([ - 'it' => 'Amazon IT', - 'de' => 'Amazon DE', - 'fr' => 'Amazon FR', - 'es' => 'Amazon ES', - 'uk' => 'Amazon UK', - ])->default((string) config('catalog.amazon.default_locale', 'it'))->required(), - TextInput::make('limit')->label('Limite prodotti')->numeric()->default(200)->required(), - ]) - ->action(function (array $data): void { - $service = app(ProductOfferService::class); - $products = Product::query() - ->where('default_fornitore_id', (int) $this->fornitore->id) - ->orderBy('id') - ->limit(max(1, (int) ($data['limit'] ?? 200))) - ->get(['id', 'name', 'brand', 'model']); - - $created = 0; - foreach ($products as $product) { - $offer = $service->syncAmazonReferralOffer($product, (string) ($data['associate_tag'] ?? ''), (string) ($data['locale'] ?? 'it')); - if ($offer !== null) { - $created++; - } - } - - $this->refreshOperationalBoxes(); - Notification::make() - ->title('Link Amazon preparati') - ->body('prodotti elaborati: ' . $products->count() . ' | link attivi: ' . $created) - ->success() - ->send(); - }), - - Action::make('importa_tecnorepair') - ->label('Importa TecnoRepair') - ->icon('heroicon-o-wrench-screwdriver') - ->form([ - TextInput::make('mdb_path') - ->label('Percorso MDB TecnoRepair') - ->default('/home/michele/netgescon/netgescon-day0/Miki-Bug-workspace/screenshot/Assistenza gestionale/Archivi/TecnoRepairDB.mdb') - ->required(), - TextInput::make('limit')->label('Limite schede')->numeric()->default(0), - Toggle::make('dry_run')->label('Solo simulazione')->default(false), - Toggle::make('force_primary')->label('Marca come centro principale')->default(true), - ]) - ->action(function (array $data): void { - $adminCode = $this->resolveFornitoreAdminCode(); - if ($adminCode === null) { - Notification::make()->title('Amministratore fornitore non trovato')->danger()->send(); - return; - } - - $params = [ - 'amministratore' => $adminCode, - '--mdb' => (string) ($data['mdb_path'] ?? ''), - '--fornitore-id' => (int) $this->fornitore->id, - ]; - - $limit = max(0, (int) ($data['limit'] ?? 0)); - if ($limit > 0) { - $params['--limit'] = $limit; - } - - if ((bool) ($data['dry_run'] ?? false)) { - $params['--dry-run'] = true; - } - - if ((bool) ($data['force_primary'] ?? true)) { - $params['--force-primary'] = true; - } - - try { - Artisan::call('tecnorepair:import-legacy', $params); - } catch (\Throwable $e) { - Notification::make()->title('Import TecnoRepair fallito')->body($e->getMessage())->danger()->send(); - return; - } - - $this->refreshOperationalBoxes(); - Notification::make() - ->title('Import TecnoRepair completato') - ->body(trim(Artisan::output()) ?: 'Operazione completata.') - ->success() - ->send(); - }), - - Action::make('impostazioni_fe') - ->label('Impostazioni FE') - ->icon('heroicon-o-adjustments-horizontal') - ->visible(fn(): bool => Schema::hasColumn('fornitori', 'escludi_righe_fe')) - ->form([ - Toggle::make('escludi_righe_fe') - ->label('Escludi righe FE (import e visualizzazione)') - ->helperText('Utile per utenze (acqua/luce/gas): si userà il PDF per i dati di consumo invece delle righe XML.') - ->default((bool) ($this->fornitore->escludi_righe_fe ?? false)), - - Toggle::make('invoice_import_enabled') - ->label('Modulo FE fornitore attivo') - ->helperText('Abilita il modulo FE universale per questo fornitore. L archivio resta separato da quello amministratore/stabile.') - ->default((bool) data_get($this->box['fornitore_features'] ?? [], 'invoice_import.enabled', true)), - - Toggle::make('invoice_source_manual_upload') - ->label('Sorgente: upload manuale') - ->default(in_array('manual_upload', (array) data_get($this->box['fornitore_features'] ?? [], 'invoice_import.sources', ['manual_upload', 'pec', 'cassetto_fiscale', 'gestionale_esterno']), true)), - - Toggle::make('invoice_source_pec') - ->label('Sorgente: PEC') - ->default(in_array('pec', (array) data_get($this->box['fornitore_features'] ?? [], 'invoice_import.sources', ['manual_upload', 'pec', 'cassetto_fiscale', 'gestionale_esterno']), true)), - - Toggle::make('invoice_source_cassetto_fiscale') - ->label('Sorgente: Cassetto Fiscale') - ->default(in_array('cassetto_fiscale', (array) data_get($this->box['fornitore_features'] ?? [], 'invoice_import.sources', ['manual_upload', 'pec', 'cassetto_fiscale', 'gestionale_esterno']), true)), - - Toggle::make('invoice_source_gestionale_esterno') - ->label('Sorgente: altro gestionale') - ->default(in_array('gestionale_esterno', (array) data_get($this->box['fornitore_features'] ?? [], 'invoice_import.sources', ['manual_upload', 'pec', 'cassetto_fiscale', 'gestionale_esterno']), true)), - - Select::make('conto_costo_default_id') - ->label('Voce spesa predefinita (sottoconto costo)') - ->helperText('Valore suggerito per imputare i costi quando le righe FE non hanno sottoconto o sono assenti.') - ->options(function (): array { - if (! Schema::hasTable('contabilita_piano_conti')) { - return []; - } - - return PianoConti::query() - ->orderBy('codice') - ->limit(1000) - ->get(['id', 'codice', 'descrizione']) - ->mapWithKeys(fn(PianoConti $c) => [(string) $c->id => $c->codice . ' - ' . $c->descrizione]) - ->all(); - }) - ->searchable() - ->nullable() - ->default(fn(): ?int => $this->box['fornitore_defaults']['conto_costo_default_id'] ? (int) $this->box['fornitore_defaults']['conto_costo_default_id'] : null), - - Select::make('voce_spesa_default_id') - ->label('Voce di spesa predefinita (ripartizione)') - ->helperText('Collegamento “logico” alla voce (utile per consumi/statistiche/ripartizione; es. acqua).') - ->options(function (): array { - $user = Auth::user(); - if (! $user instanceof User) { - return []; - } - - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId || ! Schema::hasTable('voci_spesa')) { - return []; - } - - return VoceSpesa::query() - ->where('stabile_id', (int) $stabileId) - ->orderBy('descrizione') - ->limit(800) - ->get(['id', 'codice', 'descrizione']) - ->mapWithKeys(function (VoceSpesa $v) { - $label = trim((string) ($v->codice ?? '')); - if ($label !== '') { - $label .= ' — '; - } - $label .= (string) ($v->descrizione ?? ('Voce #' . $v->id)); - return [(string) $v->id => $label]; - }) - ->all(); - }) - ->searchable() - ->nullable() - ->default(fn(): ?int => $this->box['fornitore_defaults']['voce_spesa_default_id'] ? (int) $this->box['fornitore_defaults']['voce_spesa_default_id'] : null), - ]) - ->action(function (array $data): void { - if (! Schema::hasColumn('fornitori', 'escludi_righe_fe')) { - Notification::make()->title('Colonna mancante: escludi_righe_fe')->warning()->send(); - return; - } - - $sources = []; - foreach ([ - 'manual_upload' => (bool) ($data['invoice_source_manual_upload'] ?? true), - 'pec' => (bool) ($data['invoice_source_pec'] ?? true), - 'cassetto_fiscale' => (bool) ($data['invoice_source_cassetto_fiscale'] ?? true), - 'gestionale_esterno' => (bool) ($data['invoice_source_gestionale_esterno'] ?? true), - ] as $source => $enabled) { - if ($enabled) { - $sources[] = $source; - } - } - - $features = is_array($this->box['fornitore_features'] ?? null) ? $this->box['fornitore_features'] : []; - $features['invoice_import'] = [ - 'enabled' => (bool) ($data['invoice_import_enabled'] ?? true), - 'sources' => $sources !== [] ? $sources : ['manual_upload'], - ]; - - $this->fornitore->escludi_righe_fe = (bool) ($data['escludi_righe_fe'] ?? false); - if (Schema::hasColumn('fornitori', 'fe_features')) { - $this->fornitore->fe_features = $features; - } - $this->fornitore->save(); - $this->box['fornitore_features'] = $features; - - $user = Auth::user(); - if (! $user instanceof User) { - return; - } - - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId) { - return; - } - - if (! Schema::hasTable('fornitore_stabile_impostazioni')) { - return; - } - - $voceSpesaId = isset($data['voce_spesa_default_id']) && is_numeric($data['voce_spesa_default_id']) ? (int) $data['voce_spesa_default_id'] : null; - $contoCostoId = isset($data['conto_costo_default_id']) && is_numeric($data['conto_costo_default_id']) ? (int) $data['conto_costo_default_id'] : null; - - FornitoreStabileImpostazione::query()->updateOrCreate( - [ - 'stabile_id' => (int) $stabileId, - 'fornitore_id' => (int) $this->fornitore->id, - ], - [ - 'voce_spesa_default_id' => $voceSpesaId ?: null, - 'conto_costo_default_id' => $contoCostoId ?: null, - ] - ); - - $this->hydrateBoxData($user); - }) - ->successNotificationTitle('Impostazioni FE salvate'), - - Action::make('acqua_config') - ->label('Configura acqua') - ->icon('heroicon-o-beaker') - ->form([ - Toggle::make('acqua_enabled') - ->label('Abilita acquisizione dati acqua (PDF)') - ->default((bool) ((is_array($this->box['fornitore_features'] ?? null) ? ($this->box['fornitore_features']['acqua']['enabled'] ?? false) : false))) - ->helperText('Attiva le funzioni acqua su questo fornitore (scan + letture/periodi).'), - - Toggle::make('acqua_scan_enabled') - ->label('Abilita scansione massiva su FE già importate') - ->default((bool) ((is_array($this->box['fornitore_features'] ?? null) ? ($this->box['fornitore_features']['acqua']['scan_enabled'] ?? false) : false))), - - Toggle::make('acqua_ticket_on_anomaly') - ->label('Apri ticket su anomalie/mismatch') - ->default(true) - ->helperText('Se il parser non trova dati o cambia utenza/contatore, apre un ticket interno.'), - - Repeater::make('utenze_acqua') - ->label('Utenze acqua (stabile attivo)') - ->helperText('Compila i campi per ogni contatore/utenza (lo stabile può averne più di uno).') - ->default(function (): array { - $user = Auth::user(); - if (! $user instanceof User) { - return []; - } - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId || ! Schema::hasTable('stabile_servizi')) { - return []; - } - - return StabileServizio::query() - ->where('stabile_id', (int) $stabileId) - ->where('tipo', 'acqua') - ->orderByDesc('attivo') - ->orderBy('nome') - ->limit(50) - ->get([ - 'id', - 'nome', - 'attivo', - 'voce_spesa_id', - 'contatore_matricola', - 'codice_utenza', - 'codice_cliente', - 'codice_contratto', - ]) - ->map(function (StabileServizio $s): array { - return [ - 'id' => (int) $s->id, - 'nome' => (string) ($s->nome ?? ''), - 'attivo' => (bool) $s->attivo, - 'voce_spesa_id' => $s->voce_spesa_id ? (int) $s->voce_spesa_id : null, - 'contatore_matricola' => (string) ($s->contatore_matricola ?? ''), - 'codice_utenza' => (string) ($s->codice_utenza ?? ''), - 'codice_cliente' => (string) ($s->codice_cliente ?? ''), - 'codice_contratto' => (string) ($s->codice_contratto ?? ''), - ]; - }) - ->all(); - }) - ->schema([ - Hidden::make('id'), - TextInput::make('nome') - ->label('Nome utenza') - ->maxLength(255) - ->nullable(), - Toggle::make('attivo') - ->label('Attiva') - ->default(true), - - TextInput::make('contatore_matricola') - ->label('Matricola contatore') - ->maxLength(64) - ->nullable(), - TextInput::make('codice_utenza') - ->label('Codice utenza') - ->maxLength(64) - ->nullable(), - TextInput::make('codice_cliente') - ->label('Codice cliente') - ->maxLength(64) - ->nullable(), - TextInput::make('codice_contratto') - ->label('Codice contratto') - ->maxLength(64) - ->nullable(), - - Select::make('voce_spesa_id') - ->label('Voce di spesa (solo acqua)') - ->options(function (): array { - $user = Auth::user(); - if (! $user instanceof User) { - return []; - } - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId || ! Schema::hasTable('voci_spesa')) { - return []; - } - - return VoceSpesa::query() - ->where('stabile_id', (int) $stabileId) - ->where('categoria', 'acqua') - ->orderBy('descrizione') - ->limit(300) - ->get(['id', 'codice', 'descrizione']) - ->mapWithKeys(function (VoceSpesa $v) { - $label = trim((string) ($v->codice ?? '')); - if ($label !== '') { - $label .= ' — '; - } - $label .= (string) ($v->descrizione ?? ('Voce #' . $v->id)); - return [(string) $v->id => $label]; - }) - ->all(); - }) - ->searchable() - ->nullable(), - ]) - ->addActionLabel('Aggiungi utenza acqua') - ->reorderable(false) - ->collapsed(false), - ]) - ->action(function (array $data): void { - $user = Auth::user(); - if (! $user instanceof User) { - Notification::make()->title('Utente non valido')->danger()->send(); - return; - } - - // Salva toggle feature (preferenza: colonna fornitori.fe_features, fallback: meta per-stabile) - $features = is_array($this->box['fornitore_features'] ?? null) ? $this->box['fornitore_features'] : []; - $features['acqua'] = array_merge(is_array($features['acqua'] ?? null) ? $features['acqua'] : [], [ - 'enabled' => (bool) ($data['acqua_enabled'] ?? false), - 'scan_enabled' => (bool) ($data['acqua_scan_enabled'] ?? false), - 'ticket_on_anomaly' => (bool) ($data['acqua_ticket_on_anomaly'] ?? true), - ]); - - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId) { - Notification::make()->title('Seleziona uno stabile attivo')->warning()->send(); - return; - } - - if (Schema::hasColumn('fornitori', 'fe_features')) { - $this->fornitore->fe_features = $features; - $this->fornitore->save(); - } else { - if (! Schema::hasTable('fornitore_stabile_impostazioni')) { - Notification::make() - ->title('Impossibile salvare feature') - ->body('Manca la colonna fornitori.fe_features e non esiste la tabella fornitore_stabile_impostazioni (eseguire migrazioni).') - ->danger() - ->send(); - return; - } - $settings = FornitoreStabileImpostazione::query()->firstOrNew([ - 'stabile_id' => (int) $stabileId, - 'fornitore_id' => (int) $this->fornitore->id, - ]); - $meta = is_array($settings->meta ?? null) ? $settings->meta : []; - $meta['fe_features'] = $features; - $settings->meta = $meta; - $settings->save(); - } - - if (! Schema::hasTable('stabile_servizi')) { - Notification::make()->title('Tabella servizi non presente')->warning()->send(); - return; - } - - $utenze = is_array($data['utenze_acqua'] ?? null) ? $data['utenze_acqua'] : []; - foreach ($utenze as $row) { - $rowId = isset($row['id']) && is_numeric($row['id']) ? (int) $row['id'] : null; - - $nome = is_string($row['nome'] ?? null) ? trim((string) $row['nome']) : ''; - $attivo = (bool) ($row['attivo'] ?? true); - $voceSpesaId = isset($row['voce_spesa_id']) && is_numeric($row['voce_spesa_id']) ? (int) $row['voce_spesa_id'] : null; - - $matricola = is_string($row['contatore_matricola'] ?? null) ? trim((string) $row['contatore_matricola']) : ''; - $utenzaCod = is_string($row['codice_utenza'] ?? null) ? trim((string) $row['codice_utenza']) : ''; - $cliente = is_string($row['codice_cliente'] ?? null) ? trim((string) $row['codice_cliente']) : ''; - $contratto = is_string($row['codice_contratto'] ?? null) ? trim((string) $row['codice_contratto']) : ''; - - $hasAny = ($nome !== '') || ($matricola !== '') || ($utenzaCod !== '') || ($cliente !== '') || ($contratto !== '') || ($voceSpesaId && $voceSpesaId > 0); - if (! $hasAny) { - continue; - } - - $servizio = null; - if ($rowId) { - $servizio = StabileServizio::query() - ->where('stabile_id', (int) $stabileId) - ->where('tipo', 'acqua') - ->find($rowId); - } - - if (! $servizio) { - $autoName = 'Acqua'; - if ($matricola !== '') { - $autoName .= ' - Contatore ' . $matricola; - } elseif ($utenzaCod !== '') { - $autoName .= ' - Utenza ' . $utenzaCod; - } - - $servizio = new StabileServizio([ - 'stabile_id' => (int) $stabileId, - 'tipo' => 'acqua', - 'nome' => $nome !== '' ? $nome : $autoName, - 'attivo' => true, - ]); - } - - $servizio->fornitore_id = (int) $this->fornitore->id; - $servizio->attivo = $attivo; - if ($nome !== '') { - $servizio->nome = $nome; - } - - $servizio->voce_spesa_id = $voceSpesaId ?: null; - $servizio->contatore_matricola = $matricola !== '' ? $matricola : null; - $servizio->codice_utenza = $utenzaCod !== '' ? $utenzaCod : null; - $servizio->codice_cliente = $cliente !== '' ? $cliente : null; - $servizio->codice_contratto = $contratto !== '' ? $contratto : null; - $servizio->save(); - } - - $this->hydrateBoxData($user); - - Notification::make()->title('Configurazione acqua salvata')->success()->send(); - }), - - Action::make('acqua_scan') - ->label('Scansiona FE acqua') - ->icon('heroicon-o-magnifying-glass') - ->requiresConfirmation() - ->form([ - Toggle::make('solo_stabile_attivo') - ->label('Solo stabile attivo') - ->default(true), - - Toggle::make('solo_non_estratte') - ->label('Solo FE non ancora estratte (acqua)') - ->default(true) - ->helperText('Processa solo fatture dove non risulta già salvato consumo acqua.'), - - Select::make('voce_spesa_id') - ->label('Voce di spesa (opzionale)') - ->options(function (): array { - $user = Auth::user(); - if (! $user instanceof User) { - return []; - } - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId || ! Schema::hasTable('voci_spesa')) { - return []; - } - - return VoceSpesa::query() - ->where('stabile_id', (int) $stabileId) - ->orderBy('descrizione') - ->where('categoria', 'acqua') - ->limit(300) - ->get(['id', 'codice', 'descrizione']) - ->mapWithKeys(function (VoceSpesa $v) { - $label = trim((string) ($v->codice ?? '')); - if ($label !== '') { - $label .= ' — '; - } - $label .= (string) ($v->descrizione ?? ('Voce #' . $v->id)); - return [(string) $v->id => $label]; - }) - ->all(); - }) - ->searchable() - ->nullable(), - - Select::make('force_servizio_id') - ->label('Forza servizio acqua (opzionale)') - ->helperText('Se impostato, blocca l’aggancio se il PDF indica un contatore/utenza diversa (apre ticket).') - ->options(function (): array { - $user = Auth::user(); - if (! $user instanceof User) { - return []; - } - $stabileId = StabileContext::resolveActiveStabileId($user); - if (! $stabileId || ! Schema::hasTable('stabile_servizi')) { - return []; - } - - return StabileServizio::query() - ->where('stabile_id', (int) $stabileId) - ->where('tipo', 'acqua') - ->orderByDesc('attivo') - ->orderBy('nome') - ->limit(200) - ->get(['id', 'nome', 'contatore_matricola', 'codice_utenza']) - ->mapWithKeys(function (StabileServizio $s) { - $label = (string) ($s->nome ?: ('Servizio #' . $s->id)); - $bits = []; - if (is_string($s->contatore_matricola) && $s->contatore_matricola !== '') { - $bits[] = 'Contatore ' . $s->contatore_matricola; - } - if (is_string($s->codice_utenza) && $s->codice_utenza !== '') { - $bits[] = 'Utenza ' . $s->codice_utenza; - } - if (count($bits) > 0) { - $label .= ' — ' . implode(' · ', $bits); - } - return [(string) $s->id => $label]; - }) - ->all(); - }) - ->searchable() - ->nullable(), - - TextInput::make('limit') - ->label('Limite FE da processare') - ->numeric() - ->default(50) - ->minValue(1) - ->maxValue(500), - - Toggle::make('crea_ticket') - ->label('Crea ticket su anomalie') - ->default(true), - ]) - ->action(function (array $data): void { - $user = Auth::user(); - if (! $user instanceof User) { - Notification::make()->title('Utente non valido')->danger()->send(); - return; - } - - $features = is_array($this->box['fornitore_features'] ?? null) ? $this->box['fornitore_features'] : []; - $acqua = is_array($features['acqua'] ?? null) ? $features['acqua'] : []; - if (! (bool) ($acqua['enabled'] ?? false)) { - Notification::make()->title('Acqua non attiva su questo fornitore')->warning()->send(); - return; - } - - $soloStabileAttivo = (bool) ($data['solo_stabile_attivo'] ?? true); - $soloNonEstratte = (bool) ($data['solo_non_estratte'] ?? true); - $activeStabileId = StabileContext::resolveActiveStabileId($user); - if ($soloStabileAttivo && ! $activeStabileId) { - Notification::make()->title('Seleziona uno stabile attivo')->warning()->send(); - return; - } - - $stabileIds = []; - if ($soloStabileAttivo) { - $stabileIds = [(int) $activeStabileId]; - } else { - $stabileIds = Stabile::query() - ->where('amministratore_id', (int) $this->fornitore->amministratore_id) - ->pluck('id') - ->map(fn($v) => (int) $v) - ->all(); - } - - if (count($stabileIds) < 1) { - Notification::make()->title('Nessuno stabile trovato')->warning()->send(); - return; - } - - $limit = isset($data['limit']) && is_numeric($data['limit']) ? (int) $data['limit'] : 50; - $limit = max(1, min(500, $limit)); - - $voceSpesaId = isset($data['voce_spesa_id']) && is_numeric($data['voce_spesa_id']) ? (int) $data['voce_spesa_id'] : null; - $forceServizioId = isset($data['force_servizio_id']) && is_numeric($data['force_servizio_id']) ? (int) $data['force_servizio_id'] : null; - $creaTicket = (bool) ($data['crea_ticket'] ?? true); - - $normalize = static function (?string $value): string { - $value = strtoupper(trim((string) $value)); - $value = str_replace(' ', '', $value); - return $value; - }; - - $ids = []; - $piva = $normalize($this->fornitore->partita_iva); - $cf = $normalize($this->fornitore->codice_fiscale); - if ($piva !== '') { - $ids[] = $piva; - if (str_starts_with($piva, 'IT') && strlen($piva) > 2) { - $ids[] = substr($piva, 2); - } - } - if ($cf !== '') { - $ids[] = $cf; - } - $ids = array_values(array_unique(array_filter($ids, fn($v) => $v !== ''))); - - $feQuery = FatturaElettronica::query() - ->whereIn('stabile_id', $stabileIds) - ->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($ids): void { - $q->where('fornitore_id', (int) $this->fornitore->id); - if (! empty($ids)) { - $q->orWhere(function (\Illuminate\Database\Eloquent\Builder $qq) use ($ids): void { - foreach ($ids as $id) { - $qq->orWhereRaw("REPLACE(UPPER(fornitore_piva), ' ', '') = ?", [$id]) - ->orWhereRaw("REPLACE(UPPER(fornitore_cf), ' ', '') = ?", [$id]); - } - }); - } - }); - - if ($soloNonEstratte) { - $feQuery->where(function (\Illuminate\Database\Eloquent\Builder $q): void { - $q->whereNull('consumo_raw') - ->orWhere('consumo_raw', 'not like', '%"type":"acqua"%'); - }); - } - - $feQuery - ->orderByDesc('data_fattura') - ->orderByDesc('id') - ->limit($limit); - - $fatture = $feQuery->get(['id', 'stabile_id', 'fornitore_id', 'sdi_file', 'totale', 'data_fattura', 'allegato_pdf_path', 'allegato_pdf_hash', 'xml_content', 'consumo_raw']); - - $processed = 0; - $ok = 0; - $skipped = 0; - $tickets = 0; - - foreach ($fatture as $fe) { - $processed++; - - // Ensure Documento (best-effort) - try { - if ((int) $user->id > 0) { - app(FatturaElettronicaProtocolloService::class)->ensureDocumentoProtocollato($fe, (int) $user->id); - $fe->refresh(); - } - } catch (\Throwable) { - // ignore - } - - $doc = Documento::query() - ->where('documentable_type', FatturaElettronica::class) - ->where('documentable_id', (int) $fe->id) - ->first(); - - if (! $doc) { - $skipped++; - if ($creaTicket) { - try { - $ing = app(ConsumiAcquaIngestionService::class)->ingest( - $fe, - ['error' => 'Documento non presente', 'consumi' => []], - $voceSpesaId, - (int) $user->id, - true, - $forceServizioId - ); - $tickets += (int) ($ing['tickets'] ?? 0); - } catch (\Throwable) { - // ignore - } - } - continue; - } - - $text = is_string($doc->contenuto_ocr) ? trim($doc->contenuto_ocr) : ''; - if ($text === '') { - $res = app(PdfTextExtractionService::class)->extractAndStore($doc, false); - if (($res['status'] ?? null) !== 'ok') { - $skipped++; - if ($creaTicket) { - try { - $ing = app(ConsumiAcquaIngestionService::class)->ingest( - $fe, - ['error' => (string) ($res['message'] ?? 'OCR fallita'), 'consumi' => []], - $voceSpesaId, - (int) $user->id, - true, - $forceServizioId - ); - $tickets += (int) ($ing['tickets'] ?? 0); - } catch (\Throwable) { - // ignore - } - } - continue; - } - $doc->refresh(); - $text = is_string($doc->contenuto_ocr) ? trim($doc->contenuto_ocr) : ''; - } - - if ($text === '') { - $skipped++; - if ($creaTicket) { - try { - $ing = app(ConsumiAcquaIngestionService::class)->ingest( - $fe, - ['error' => 'Testo OCR vuoto', 'consumi' => []], - $voceSpesaId, - (int) $user->id, - true, - $forceServizioId - ); - $tickets += (int) ($ing['tickets'] ?? 0); - } catch (\Throwable) { - // ignore - } - } - continue; - } - - $parsed = app(AcquaPdfTextParser::class)->parse($text); - $codici = is_array($parsed['codici'] ?? null) ? $parsed['codici'] : []; - $contatore = is_array($parsed['contatore'] ?? null) ? $parsed['contatore'] : []; - $consumi = is_array($parsed['consumi'] ?? null) ? $parsed['consumi'] : []; - $generale = is_array($parsed['generale'] ?? null) ? $parsed['generale'] : []; - $finestra = is_array($parsed['finestra_autolettura'] ?? null) ? $parsed['finestra_autolettura'] : []; - $letture = is_array($parsed['riepilogo_letture'] ?? null) ? $parsed['riepilogo_letture'] : []; - $quadro = is_array($parsed['quadro_dettaglio'] ?? null) ? $parsed['quadro_dettaglio'] : []; - $tariffe = is_array($parsed['tariffe'] ?? null) ? $parsed['tariffe'] : []; - $ivaInfo = is_array($parsed['iva'] ?? null) ? $parsed['iva'] : []; - $hasTariffData = count(array_filter([ - $generale['periodicita_fatturazione'] ?? null, - $tariffe['profilo'] ?? null, - $ivaInfo['codice'] ?? null, - $quadro['quota_fissa'] ?? null, - ], static fn($v): bool => $v !== null && $v !== '')) > 0; - - $hasAny = false; - foreach ([$codici['utenza'] ?? null, $codici['cliente'] ?? null, $codici['contratto'] ?? null, $contatore['matricola'] ?? null] as $v) { - if (is_string($v) && trim($v) !== '') { - $hasAny = true; - break; - } - } - if (! $hasAny && count($consumi) < 1 && ! $hasTariffData) { - $skipped++; - if ($creaTicket) { - try { - $ing = app(ConsumiAcquaIngestionService::class)->ingest($fe, $parsed, $voceSpesaId, (int) $user->id, true, $forceServizioId); - $tickets += (int) ($ing['tickets'] ?? 0); - } catch (\Throwable) { - // ignore - } - } - continue; - } - - // Salva anche su FE (consumo_raw + campi generici) - $payload = [ - 'type' => 'acqua', - 'source' => 'pdf_ocr', - 'codici' => [ - 'utenza' => $codici['utenza'] ?? null, - 'cliente' => $codici['cliente'] ?? null, - 'contratto' => $codici['contratto'] ?? null, - ], - 'contatore' => [ - 'matricola' => $contatore['matricola'] ?? null, - ], - 'consumi' => array_values($consumi), - 'generale' => $generale, - 'finestra_autolettura' => $finestra, - 'riepilogo_letture' => array_values($letture), - 'quadro_dettaglio' => $quadro, - 'tariffe' => $tariffe, - 'iva' => [ - 'codice' => $ivaInfo['codice'] ?? null, - 'aliquota_percentuale' => $ivaInfo['aliquota_percentuale'] ?? null, - 'descrizione' => $ivaInfo['descrizione'] ?? null, - ], - 'parsed_at' => now()->toISOString(), - ]; - - $totMc = null; - $ref = null; - if (count($consumi) > 0) { - $sum = 0.0; - $has = false; - foreach ($consumi as $c) { - if (isset($c['valore']) && is_numeric($c['valore'])) { - $sum += (float) $c['valore']; - $has = true; - } - } - if ($has) { - $totMc = $sum; - } - $first = $consumi[0] ?? []; - $dal = $first['dal'] ?? null; - $al = $first['al'] ?? null; - if (is_string($dal) && is_string($al) && $dal !== '' && $al !== '') { - $ref = $dal . ' - ' . $al; - } - } - - $fe->consumo_unita = 'mc'; - $fe->consumo_valore = $totMc; - $fe->consumo_riferimento = $ref; - $fe->consumo_raw = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - $fe->save(); - - try { - app(ConsumiAcquaTariffeIngestionService::class)->ingest($fe, $parsed, null); - } catch (\Throwable) { - // best-effort - } - - $ing = app(ConsumiAcquaIngestionService::class)->ingest($fe, $parsed, $voceSpesaId, (int) $user->id, $creaTicket, $forceServizioId); - - if (($ing['status'] ?? null) === 'ok') { - try { - app(ConsumiAcquaTariffeIngestionService::class)->ingest( - $fe, - $parsed, - isset($ing['servizio_id']) && is_numeric($ing['servizio_id']) ? (int) $ing['servizio_id'] : null - ); - } catch (\Throwable) { - // best-effort - } - } - - if (($ing['status'] ?? null) === 'ok') { - $ok++; - } elseif (($ing['status'] ?? null) === 'no-data' && $hasTariffData) { - $ok++; - } elseif (($ing['status'] ?? null) === 'mismatch') { - $tickets += (int) ($ing['tickets'] ?? 1); - $skipped++; - } elseif (($ing['status'] ?? null) === 'no-data') { - $tickets += (int) ($ing['tickets'] ?? 0); - $skipped++; - } else { - $skipped++; - } - } - - $body = 'Processate: ' . $processed . ' · OK: ' . $ok . ' · Skipped: ' . $skipped; - if ($tickets > 0) { - $body .= ' · Ticket: ' . $tickets; - } - - Notification::make()->title('Scansione acqua completata')->body($body)->success()->send(); - }), - - Action::make('torna') - ->label('Torna') - ->icon('heroicon-o-arrow-left') - ->url(function() { - $candidate = request()->query('back'); - if (is_string($candidate) && trim($candidate) !== '') { - return $candidate; - } - $prevUrl = url()->previous(); - $currentUrl = request()->fullUrl(); - $isLivewire = request()->hasHeader('X-Livewire') || request()->filled('_token') || str_contains($prevUrl, '/livewire/message'); - if ($prevUrl && $prevUrl !== $currentUrl && !$isLivewire) { - return $prevUrl; - } - return '/admin-filament/fornitori'; - }), - ]; + return []; } } diff --git a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php index 198ae93..0192d5b 100755 --- a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php +++ b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php @@ -1,11 +1,11 @@ @php $tabs = [ - 'profilo' => 'Dati condivisi', - 'accessi' => 'Dipendenti e accessi', - 'assistenza' => 'Assistenza interna', - 'catalogo' => 'Catalogo e prodotti', - 'contabilita' => 'Contabilita', + 'profilo' => '👥 Dati Condivisi', + 'accessi' => '🔑 Accessi & Dipendenti', + 'assistenza' => '🛠️ Assistenza Interna', + 'catalogo' => '📦 Catalogo Prodotti', + 'contabilita' => '🧾 Contabilità & FE', ]; $catalogoActions = [ ['action' => 'nuovo_prodotto', 'label' => 'Nuovo prodotto', 'color' => 'gray'], @@ -36,30 +36,29 @@ @endphp
-
-
-
-
Scheda fornitore
-
La scheda e divisa in tab operative per separare dati condivisi, accessi, assistenza, catalogo e contabilita senza perdere il contesto del fornitore.
+ {{-- BARRA PULSANTINI ICONICI COMPATTI UNIFICATA (SENZA SCROLL ORIZZONTALE) --}} +
+
+
+ 🏢 Elenco Fornitori + @if((int) ($fornitore->rubrica_id ?? 0) > 0) + 👤 Apri Rubrica (#{{ $fornitore->rubrica_id }}) + @endif
-
-
Fornitore
-
{{ $fornitore->ragione_sociale ?? trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? '')) ?: ('Fornitore #' . $fornitore->id) }}
-
-
-
- @foreach($tabs as $tabKey => $tabLabel) - - @endforeach +
+ @foreach($tabs as $tabKey => $tabLabel) + + @endforeach +
diff --git a/skill-netgescon/ui-wireframes/fornitori-anagrafica.md b/skill-netgescon/ui-wireframes/fornitori-anagrafica.md index e891d3f..adc0761 100644 --- a/skill-netgescon/ui-wireframes/fornitori-anagrafica.md +++ b/skill-netgescon/ui-wireframes/fornitori-anagrafica.md @@ -1,4 +1,4 @@ -# ASCII Wireframe - Gestione Fornitori & Anagrafica Accreditate (/admin-filament/gescon/anagrafica/fornitori) +# ASCII Wireframe - Gestione Fornitori & Anagrafica Accreditate (/admin-filament/anagrafica/fornitori/{record}) ## 0. STICKY HEADER BANNER - IMPERSONIFICAZIONE CONTESTUALE ATTIVA (Se attiva) @@ -10,42 +10,27 @@ ## 0. STICKY HEADER BANNER - IMPERSONIFICAZIONE CONTESTUALE ATTIVA (Se attiva) --- -## 1. TAB 1: 🏢 ELENCO FORNITORI ACCREDITATI STUDIO (#13 CECILIA TORDINI) +## 1. BARRA PULSANTINI ICONICI COMPATTI UNIFICATA & SOTTO-TAB FORNITORE ```text +-------------------------------------------------------------------------------------------------------------------+ -| NETGESCON - ANAGRAFICA UNIFICATA STABILI & FORNITORI [Studio: Cecilia Tordini]| +| NETGESCON - SCHEDA ANAGRAFICA FORNITORE [Studio: Cecilia Tordini]| +-------------------------------------------------------------------------------------------------------------------+ -| PATH: Home > NetGescon > Anagrafica > Fornitori & Imprese Accreditate | +| PATH: Home > NetGescon > Anagrafica > Fornitori > Scheda #236 (TECNOREPAIR S.R.L.) | +-------------------------------------------------------------------------------------------------------------------+ -| BARRA PULSANTINI ICONICI COMPATTI (SENZA SCROLL ORIZZONTALE): | -| [ 🏢 Elenco ] [ ✏️ Modifica (#10) ] [ 📄 Fatture FE ] [ 🔑 Matrice ACL ] | -+-------------------------------------------------------------------------------------------------------------------+ -| | -| [ + NUOVO FORNITORE ] [ 🔄 IMPORTA DA LEGACY MDB ] [ 🔍 Cerca per Ragione Sociale, P.IVA, CF, Tag... ] | -| | -| FILTRI TAG CATEGORIA: [ Tutti ▾ ] [ Idraulici ] [ Elettricisti ] [ Pulizie ] [ Ascensori ] [ Amministrativi ] | -| | -| +----+----------------------------+-----------------+-----------------------+-------------------+-----------------+ -| | ID | RAGIONE SOCIALE / IMPRESA | P.IVA / C.F. | RECAPITI MULTICANALE | CATEGORIE / TAGS | AZIONI | -| +----+----------------------------+-----------------+-----------------------+-------------------+-----------------+ -| | 10 | TECNOREPAIR S.R.L. | 01234567890 | ✉ info@tecnorepair.it | [Idraulico] | [✏️ Modifica] | -| | | | | 📱 335-9988776 | [Elettricista] | [📄 Fatture FE] | -| | 11 | CLEAN & SHINE PULIZIE | 09876543210 | ✉ ordini@cleanshine.it| [Pulizie Parti C.]| [✏️ Modifica] | -| | 12 | ASCENSORI ROMA NORD S.A.S. | RMCNSC65L11H501K| ☎ 06-44332211 | [Manut. Ascensore]| [✏️ Modifica] | -| | 13 | EDILIZIA SUBALPINA S.R.L. | 05544332211 | ✉ cantiere@subalpina.it| [Edile] [Tetti] | [✏️ Modifica] | -| +----+----------------------------+-----------------+-----------------------+-------------------+-----------------+ -| Mostrati 370 di 370 fornitori accreditati per gli stabili dello studio | +| BARRA PULSANTINI ICONICI COMPATTI UNIFICATA (SENZA SCROLL ORIZZONTALE): | +| PRIMARIA: [ 🏢 Elenco Fornitori ] [ ✏️ Modifica (#236) ] [ 👤 Apri Rubrica ] | +| SOTTO-TAB:[ 👥 Dati Condivisi ] [ 🔑 Accessi & Dipendenti ] [ 🛠️ Assistenza Interna ] [ 📦 Catalogo ] [ 🧾 FE ] | +-------------------------------------------------------------------------------------------------------------------+ ``` --- -## 2. TAB 2: ✏️ SCHEDA DETTAGLIO & EDITING INLINE FORNITORE (#10 TECNOREPAIR S.R.L.) +## 2. SCHEDA DETTAGLIO & EDITING INLINE FORNITORE (#236 TECNOREPAIR S.R.L.) ```text +-------------------------------------------------------------------------------------------------------------------+ -| DETTAGLIO ED EDITING SCHEDA FORNITORE (#10 TECNOREPAIR S.R.L.) | +| DETTAGLIO ED EDITING SCHEDA FORNITORE (#236 TECNOREPAIR S.R.L.) | +-------------------------------------------------------------------------------------------------------------------+ | [ Ragione Sociale: TECNOREPAIR S.R.L. ] | | [ Partita IVA: 01234567890 ] [ Codice Fiscale: 01234567890 ] | @@ -65,11 +50,11 @@ ## 2. TAB 2: ✏️ SCHEDA DETTAGLIO & EDITING INLINE FORNITORE (#10 TECNOREPAIR --- -## 3. TAB 3: 📄 FATTURE ELETTRONICHE & XML MATCHING FORNITORE +## 3. FATTURE ELETTRONICHE & XML MATCHING FORNITORE ```text +-------------------------------------------------------------------------------------------------------------------+ -| REGISTRO FATTURE ELETTRONICHE XML / SDI - FORNITORE #10 (TECNOREPAIR S.R.L. - P.IVA 01234567890) | +| REGISTRO FATTURE ELETTRONICHE XML / SDI - FORNITORE #236 (TECNOREPAIR S.R.L. - P.IVA 01234567890) | +-------------------------------------------------------------------------------------------------------------------+ | FATTURE ASSEGNATE AUTOMATICAMENTE DA MATCHING P.IVA / C.F. SUGLI STABILI DELLO STUDIO: | | | @@ -82,22 +67,3 @@ ## 3. TAB 3: 📄 FATTURE ELETTRONICHE & XML MATCHING FORNITORE | [ 🔄 CARICA NUOVO FILE XML/ZIP ] [ 📄 ESPORTA REGISTRO FATTURE ] | +-------------------------------------------------------------------------------------------------------------------+ ``` - ---- - -## 4. TAB 4: 🔑 MATRICE PERMESSI ACL & ACCREDITAMENTO STUDIO - -```text -+-------------------------------------------------------------------------------------------------------------------+ -| ACCREDITAMENTO E MATRICE VISIBILITÀ FORNITORE - STUDIO #13 (CECILIA TORDINI) | -+-------------------------------------------------------------------------------------------------------------------+ -| CONFIGURAZIONE ACCESSO PORTALE FORNITORI PER TECNOREPAIR S.R.L.: | -| | -| • Permesso di Login Portale: [X] Abilitato (Email: info@tecnorepair.it) | -| • Assegnazione Ticket Urgenze: [X] Abilitata per tutti i 16 stabili dello studio | -| • Caricamento Autonomo Fatture XML: [X] Abilitato | -| • Visibilità Storico Pagamenti: [X] Solo fatture del proprio P.IVA | -| | -| [ ✓ SALVA CONFIGURAZIONE ACCREDITAMENTO ] | -+-------------------------------------------------------------------------------------------------------------------+ -``` diff --git a/skill-netgescon/ui-wireframes/stabili.md b/skill-netgescon/ui-wireframes/stabili.md index 8b0defb..fc76daa 100644 --- a/skill-netgescon/ui-wireframes/stabili.md +++ b/skill-netgescon/ui-wireframes/stabili.md @@ -10,7 +10,7 @@ ## 0. STICKY HEADER BANNER - IMPERSONIFICAZIONE CONTESTUALE ATTIVA (Se attiva) --- -## 1. TAB 1: 🏢 ELENCO STABILI CONDOMINIALI +## 1. BARRA PULSANTINI ICONICI COMPATTI UNIFICATA & SOTTO-TAB ```text +-------------------------------------------------------------------------------------------------------------------+ @@ -19,7 +19,16 @@ ## 1. TAB 1: 🏢 ELENCO STABILI CONDOMINIALI | PATH: Home > Condomini > Gestione Stabili | +-------------------------------------------------------------------------------------------------------------------+ | BARRA PULSANTINI ICONICI COMPATTI UNIFICATA (SENZA SCROLL ORIZZONTALE): | -| [ 🏢 Elenco Stabili ] [ ✏️ Modifica Stabile (#23) ] | [ 📊 Cruscotto ] [ 👥 Nominativi ] [ 🏛️ Catasto ] [ 📐 Millesimi ]| +| PRIMARIA: [ 🏢 Elenco Stabili ] [ ✏️ Modifica Stabile (#0021) ] | +| SOTTO-TAB:[ 📊 Cruscotto ] [ 👥 Nominativi ] [ 🏛️ Catasto ] [ 📐 Millesimi ] [ 📄 Fatture FE ] | ++-------------------------------------------------------------------------------------------------------------------+ +``` + +--- + +## 2. TAB 1: 🏢 ELENCO STABILI CONDOMINIALI + +```text +-------------------------------------------------------------------------------------------------------------------+ | | | [ + NUOVO STABILE ] [ 🔄 SYNC DA STABILI.MDB ] [ 🔍 Cerca per Codice, Denominazione, Codice Fiscale... ] | @@ -42,7 +51,7 @@ ## 1. TAB 1: 🏢 ELENCO STABILI CONDOMINIALI --- -## 2. TAB 2: ✏️ SCHEDA DETTAGLIO & EDITING INLINE STABILE (#0021 SUPERCONDOMINIO MILIZIE 3) +## 3. TAB 2: ✏️ SCHEDA DETTAGLIO & EDITING INLINE STABILE (#0021 SUPERCONDOMINIO MILIZIE 3) ```text +-------------------------------------------------------------------------------------------------------------------+