From d0b1e58b4672955b6a37f3a1513d68c9fc9dc490 Mon Sep 17 00:00:00 2001 From: michele Date: Mon, 27 Apr 2026 17:06:49 +0000 Subject: [PATCH] feat(catalog): add shared product hub panels --- .../Fornitore/TicketInterventoScheda.php | 21 ++ app/Filament/Pages/Gescon/FornitoreScheda.php | 54 +--- .../Catalog/ProductHubViewService.php | 250 ++++++++++++++++++ .../catalog/product-hub-panel.blade.php | 104 ++++++++ .../ticket-intervento-scheda.blade.php | 21 ++ .../pages/gescon/fornitore-scheda.blade.php | 104 +------- 6 files changed, 408 insertions(+), 146 deletions(-) create mode 100644 app/Services/Catalog/ProductHubViewService.php create mode 100644 resources/views/filament/components/catalog/product-hub-panel.blade.php diff --git a/app/Filament/Pages/Fornitore/TicketInterventoScheda.php b/app/Filament/Pages/Fornitore/TicketInterventoScheda.php index 31e416d..2cfae2a 100644 --- a/app/Filament/Pages/Fornitore/TicketInterventoScheda.php +++ b/app/Filament/Pages/Fornitore/TicketInterventoScheda.php @@ -18,6 +18,7 @@ use App\Models\TicketIntervento; use App\Models\TicketInterventoSessione; use App\Models\User; +use App\Services\Catalog\ProductHubViewService; use BackedEnum; use Filament\Notifications\Notification; use Filament\Pages\Page; @@ -93,6 +94,12 @@ class TicketInterventoScheda extends Page /** @var array> */ public array $historicalMaterialRows = []; + /** @var array> */ + public array $ticketProductHubRows = []; + + /** @var array> */ + public array $stabileInstalledProductRows = []; + public string $apparatoMarca = ''; public string $apparatoModello = ''; @@ -999,9 +1006,23 @@ protected function reloadIntervento(): void $this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus(); $this->loadAccessSummary(); $this->loadMaterialMemory(); + $this->loadTicketProductHubRows(); $this->qrToken = ''; } + protected function loadTicketProductHubRows(): void + { + $stabileId = (int) ($this->intervento->ticket?->stabile_id ?? 0) ?: null; + + $this->ticketProductHubRows = app(ProductHubViewService::class) + ->buildRows($this->fornitore, $stabileId, 12); + + $this->stabileInstalledProductRows = array_values(array_filter( + $this->ticketProductHubRows, + fn(array $row): bool => ((float) ($row['stable_usage_quantity'] ?? 0)) > 0 || ((int) ($row['stable_usage_count'] ?? 0)) > 0 + )); + } + protected function getActiveSession(): ?TicketInterventoSessione { if (! $this->sessionTrackingAvailable) { diff --git a/app/Filament/Pages/Gescon/FornitoreScheda.php b/app/Filament/Pages/Gescon/FornitoreScheda.php index 430664e..bbd6b6f 100644 --- a/app/Filament/Pages/Gescon/FornitoreScheda.php +++ b/app/Filament/Pages/Gescon/FornitoreScheda.php @@ -18,6 +18,7 @@ use App\Modules\Contabilita\Models\FatturaFornitore as ContabilitaFatturaFornitore; use App\Modules\Contabilita\Models\PianoConti; use App\Services\Catalog\FornitoreProductCatalogService; +use App\Services\Catalog\ProductHubViewService; use App\Services\Catalog\ProductAssetIngestionService; use App\Services\Catalog\ProductOfferService; use App\Services\Consumi\AcquaPdfTextParser; @@ -590,57 +591,8 @@ private function refreshCatalogRows(): void $this->catalogoProdottiRows = []; $this->tecnorepairRows = []; - if (Schema::hasTable('products')) { - $query = Product::query() - ->withCount(['identifiers', 'serials', 'media']) - ->where('default_fornitore_id', (int) $this->fornitore->id) - ->orderBy('name') - ->limit(20); - - if (Schema::hasTable('product_offers')) { - $query->with(['offers' => function ($offerQuery): void { - $offerQuery->where('is_active', true)->orderBy('price_amount'); - }]); - } - - $this->catalogoProdottiRows = $query - ->get(['id', 'internal_code', 'name', 'brand', 'model', 'color_label', 'track_serials', 'meta']) - ->map(function (Product $product): array { - $offers = Schema::hasTable('product_offers') ? ($product->offers ?? collect()) : collect(); - $internalOffer = $offers->first(fn($offer) => (bool) ($offer->is_internal ?? false)); - $bestCompetitor = $offers - ->filter(fn($offer) => ! (bool) ($offer->is_internal ?? false) && filled($offer->price_amount)) - ->sortBy('price_amount') - ->first(); - $amazonOffer = $offers->first(fn($offer) => in_array((string) ($offer->source_type ?? ''), ['amazon_referral', 'amazon_creators_api'], true)); - - return [ - 'id' => (int) $product->id, - 'internal_code' => (string) ($product->internal_code ?? ''), - 'name' => (string) ($product->name ?? ''), - 'brand' => (string) ($product->brand ?? ''), - 'model' => (string) ($product->model ?? ''), - 'color_label' => (string) ($product->color_label ?? ''), - 'track_serials' => (bool) $product->track_serials, - 'identifiers_count' => (int) ($product->identifiers_count ?? 0), - 'serials_count' => (int) ($product->serials_count ?? 0), - 'media_count' => (int) ($product->media_count ?? 0), - 'source' => (string) (($product->meta['source'] ?? 'manual') ?: 'manual'), - 'publication_mode' => (string) (($product->meta['catalog']['publication_mode'] ?? '') ?: ''), - 'public_reference' => (string) (($product->meta['catalog']['public_reference'] ?? '') ?: ($product->internal_code ?? '')), - 'private_links' => count(array_filter(is_array($product->meta['catalog']['private_source_links'] ?? null) ? $product->meta['catalog']['private_source_links'] : [], fn($value) => filled($value))), - 'categories' => (string) (($product->meta['catalog']['categories'] ?? '') ?: ''), - 'stock_quantity' => is_numeric($product->meta['stock']['quantity'] ?? null) ? (int) $product->meta['stock']['quantity'] : null, - 'price_wholesale' => is_numeric($product->meta['pricing']['wholesale'] ?? null) ? (float) $product->meta['pricing']['wholesale'] : null, - 'offers_count' => $offers->count(), - 'own_offer_price' => $internalOffer && filled($internalOffer->price_amount) ? (float) $internalOffer->price_amount : null, - 'best_competitor_price' => $bestCompetitor && filled($bestCompetitor->price_amount) ? (float) $bestCompetitor->price_amount : null, - 'best_competitor_source' => $bestCompetitor ? (string) ($bestCompetitor->source_name ?? $bestCompetitor->source_type ?? '') : '', - 'amazon_referral_url' => $amazonOffer ? (string) ($amazonOffer->referral_url ?? '') : '', - ]; - }) - ->all(); - } + $this->catalogoProdottiRows = app(ProductHubViewService::class) + ->buildRows($this->fornitore, null, 20); if (Schema::hasTable('assistenza_tecnorepair_schede_legacy')) { $this->tecnorepairRows = $this->fornitore->tecnorepairSchede() diff --git a/app/Services/Catalog/ProductHubViewService.php b/app/Services/Catalog/ProductHubViewService.php new file mode 100644 index 0000000..23397fe --- /dev/null +++ b/app/Services/Catalog/ProductHubViewService.php @@ -0,0 +1,250 @@ +> + */ + public function buildRows(Fornitore $fornitore, ?int $stabileId = null, int $limit = 20): array + { + if (! Schema::hasTable('products')) { + return []; + } + + $products = Product::query() + ->withCount(['identifiers', 'serials', 'media']) + ->with([ + 'identifiers', + 'offers' => function ($offerQuery) use ($fornitore): void { + $offerQuery + ->where('is_active', true) + ->where(function ($builder) use ($fornitore): void { + $builder->whereNull('fornitore_id') + ->orWhere('fornitore_id', (int) $fornitore->id); + }) + ->orderByDesc('is_internal') + ->orderBy('price_amount'); + }, + ]) + ->where('is_active', true) + ->where(function ($query) use ($fornitore): void { + $query->where('default_fornitore_id', (int) $fornitore->id) + ->orWhereHas('offers', fn($offerQuery) => $offerQuery->where('fornitore_id', (int) $fornitore->id)); + }) + ->limit(max($limit * 3, 30)) + ->get(['id', 'default_fornitore_id', 'internal_code', 'name', 'brand', 'model', 'color_label', 'track_serials', 'meta']); + + if ($products->isEmpty()) { + return []; + } + + $productIds = $products->pluck('id')->map(fn($value): int => (int) $value)->all(); + $usageByProduct = $this->buildUsageSummary($fornitore, $productIds, $stabileId); + $pricingByProduct = $this->buildPurchaseSummary($fornitore, $productIds); + + return $products + ->map(function (Product $product) use ($fornitore, $usageByProduct, $pricingByProduct): array { + $offers = $product->offers ?? collect(); + $internalOffer = $offers->first(fn(ProductOffer $offer): bool => (bool) ($offer->is_internal ?? false)); + $bestExternal = $offers + ->filter(fn(ProductOffer $offer): bool => ! (bool) ($offer->is_internal ?? false) && filled($offer->price_amount)) + ->sortBy('price_amount') + ->first(); + $amazonOffer = $offers->first(fn(ProductOffer $offer): bool => in_array((string) ($offer->source_type ?? ''), ['amazon_referral', 'amazon_creators_api'], true)); + $identifier = $product->identifiers + ->first(fn(ProductIdentifier $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $fornitore->id) ?? $product->identifiers->first(); + $meta = is_array($product->meta ?? null) ? $product->meta : []; + $catalogMeta = is_array($meta['catalog'] ?? null) ? $meta['catalog'] : []; + $usage = $usageByProduct[(int) $product->id] ?? $this->emptyUsageSummary(); + $purchaseSummary = $pricingByProduct[(int) $product->id] ?? $this->emptyPurchaseSummary(); + + return [ + 'id' => (int) $product->id, + 'internal_code' => (string) ($product->internal_code ?? ''), + 'name' => (string) ($product->name ?? ''), + 'brand' => (string) ($product->brand ?? ''), + 'model' => (string) ($product->model ?? ''), + 'color_label' => (string) ($product->color_label ?? ''), + 'track_serials' => (bool) $product->track_serials, + 'identifier' => (string) ($identifier->code_value ?? ''), + 'identifiers_count' => (int) ($product->identifiers_count ?? 0), + 'serials_count' => (int) ($product->serials_count ?? 0), + 'media_count' => (int) ($product->media_count ?? 0), + 'source' => (string) (($meta['source'] ?? 'manual') ?: 'manual'), + 'publication_mode' => (string) (($catalogMeta['publication_mode'] ?? '') ?: ''), + 'public_reference' => (string) (($catalogMeta['public_reference'] ?? '') ?: ($product->internal_code ?? '')), + 'private_links' => count(array_filter(is_array($catalogMeta['private_source_links'] ?? null) ? $catalogMeta['private_source_links'] : [], fn($value) => filled($value))), + 'categories' => (string) (($catalogMeta['categories'] ?? '') ?: ''), + 'stock_quantity' => is_numeric($meta['stock']['quantity'] ?? null) ? (int) $meta['stock']['quantity'] : null, + 'price_wholesale' => is_numeric($meta['pricing']['wholesale'] ?? null) ? (float) $meta['pricing']['wholesale'] : null, + 'offers_count' => $offers->count(), + 'own_offer_price' => $internalOffer && filled($internalOffer->price_amount) ? (float) $internalOffer->price_amount : null, + 'best_competitor_price' => $bestExternal && filled($bestExternal->price_amount) ? (float) $bestExternal->price_amount : null, + 'best_competitor_source' => $bestExternal ? (string) ($bestExternal->source_name ?? $bestExternal->source_type ?? '') : '', + 'amazon_referral_url' => $amazonOffer ? (string) ($amazonOffer->referral_url ?? $amazonOffer->external_url ?? '') : '', + 'usage_count' => (int) ($usage['usage_count'] ?? 0), + 'usage_quantity' => (float) ($usage['usage_quantity'] ?? 0), + 'last_used_at' => (string) ($usage['last_used_at'] ?? ''), + 'stable_usage_count' => (int) ($usage['stable_usage_count'] ?? 0), + 'stable_usage_quantity' => (float) ($usage['stable_usage_quantity'] ?? 0), + 'stable_last_used_at' => (string) ($usage['stable_last_used_at'] ?? ''), + 'purchase_lines_count' => (int) ($purchaseSummary['purchase_lines_count'] ?? 0), + 'last_purchase_total' => $purchaseSummary['last_purchase_total'] ?? null, + 'last_purchase_date' => (string) ($purchaseSummary['last_purchase_date'] ?? ''), + 'purchase_history_labels' => (array) ($purchaseSummary['purchase_history_labels'] ?? []), + 'sort_score' => ((int) ($usage['stable_usage_count'] ?? 0) * 1000) + + ((int) round((float) ($usage['stable_usage_quantity'] ?? 0) * 100)) + + ((int) ($usage['usage_count'] ?? 0) * 10), + ]; + }) + ->sort(function (array $left, array $right): int { + if (($left['sort_score'] ?? 0) === ($right['sort_score'] ?? 0)) { + return strcmp((string) ($left['name'] ?? ''), (string) ($right['name'] ?? '')); + } + + return (int) ($right['sort_score'] ?? 0) <=> (int) ($left['sort_score'] ?? 0); + }) + ->take($limit) + ->values() + ->all(); + } + + /** + * @param array $productIds + * @return array> + */ + private function buildUsageSummary(Fornitore $fornitore, array $productIds, ?int $stabileId): array + { + if ($productIds === [] || ! Schema::hasTable('ticket_interventi')) { + return []; + } + + $interventi = TicketIntervento::query() + ->with(['ticket:id,stabile_id']) + ->where('fornitore_id', (int) $fornitore->id) + ->whereNotNull('materiali_utilizzati') + ->latest('updated_at') + ->limit(250) + ->get(['id', 'ticket_id', 'materiali_utilizzati', 'updated_at']); + + $summary = []; + + foreach ($interventi as $intervento) { + $currentStabileId = (int) ($intervento->ticket?->stabile_id ?? 0); + + foreach ((array) ($intervento->materiali_utilizzati ?? []) as $item) { + if (! is_array($item)) { + continue; + } + + $productId = isset($item['product_id']) && is_numeric($item['product_id']) ? (int) $item['product_id'] : 0; + if ($productId <= 0 || ! in_array($productId, $productIds, true)) { + continue; + } + + $qty = isset($item['qty']) && is_numeric($item['qty']) ? (float) $item['qty'] : 1.0; + + if (! isset($summary[$productId])) { + $summary[$productId] = $this->emptyUsageSummary(); + } + + $summary[$productId]['usage_count']++; + $summary[$productId]['usage_quantity'] += $qty; + $summary[$productId]['last_used_at'] = $summary[$productId]['last_used_at'] !== '' + ? $summary[$productId]['last_used_at'] + : (optional($intervento->updated_at)->format('d/m/Y H:i') ?: ''); + + if ($stabileId !== null && $stabileId > 0 && $currentStabileId === $stabileId) { + $summary[$productId]['stable_usage_count']++; + $summary[$productId]['stable_usage_quantity'] += $qty; + $summary[$productId]['stable_last_used_at'] = $summary[$productId]['stable_last_used_at'] !== '' + ? $summary[$productId]['stable_last_used_at'] + : (optional($intervento->updated_at)->format('d/m/Y H:i') ?: ''); + } + } + } + + return $summary; + } + + /** + * @param array $productIds + * @return array> + */ + private function buildPurchaseSummary(Fornitore $fornitore, array $productIds): array + { + if ($productIds === [] || ! Schema::hasTable('contabilita_fatture_fornitori_righe') || ! Schema::hasTable('contabilita_fatture_fornitori')) { + return []; + } + + $rows = FatturaFornitoreRiga::query() + ->with(['fattura:id,fornitore_id,data_documento,numero_documento']) + ->whereIn('product_id', $productIds) + ->whereHas('fattura', fn($query) => $query->where('fornitore_id', (int) $fornitore->id)) + ->orderByDesc('id') + ->limit(300) + ->get(['id', 'fattura_id', 'product_id', 'descrizione', 'imponibile_euro', 'totale_euro']); + + $summary = []; + + foreach ($rows->groupBy('product_id') as $productId => $group) { + $labels = $group + ->take(3) + ->map(function (FatturaFornitoreRiga $row): string { + $date = $row->fattura?->data_documento?->format('d/m/Y') ?: 'data n/d'; + $number = trim((string) ($row->fattura?->numero_documento ?? 'riga #' . $row->id)); + $total = is_numeric($row->totale_euro) ? number_format((float) $row->totale_euro, 2, ',', '.') . ' EUR' : 'totale n/d'; + + return $date . ' · ' . $number . ' · ' . $total; + }) + ->values() + ->all(); + + /** @var FatturaFornitoreRiga|null $latest */ + $latest = $group->first(); + + $summary[(int) $productId] = [ + 'purchase_lines_count' => $group->count(), + 'last_purchase_total' => $latest && is_numeric($latest->totale_euro) ? (float) $latest->totale_euro : null, + 'last_purchase_date' => $latest?->fattura?->data_documento?->format('d/m/Y') ?: '', + 'purchase_history_labels' => $labels, + ]; + } + + return $summary; + } + + /** @return array */ + private function emptyUsageSummary(): array + { + return [ + 'usage_count' => 0, + 'usage_quantity' => 0.0, + 'last_used_at' => '', + 'stable_usage_count' => 0, + 'stable_usage_quantity' => 0.0, + 'stable_last_used_at' => '', + ]; + } + + /** @return array */ + private function emptyPurchaseSummary(): array + { + return [ + 'purchase_lines_count' => 0, + 'last_purchase_total' => null, + 'last_purchase_date' => '', + 'purchase_history_labels' => [], + ]; + } +} \ No newline at end of file diff --git a/resources/views/filament/components/catalog/product-hub-panel.blade.php b/resources/views/filament/components/catalog/product-hub-panel.blade.php new file mode 100644 index 0000000..4ab6a7b --- /dev/null +++ b/resources/views/filament/components/catalog/product-hub-panel.blade.php @@ -0,0 +1,104 @@ +@php + $panelTitle = $title ?? 'Hub prodotti'; + $panelDescription = $description ?? ''; + $panelRows = is_array($rows ?? null) ? $rows : []; + $panelEmptyMessage = $emptyMessage ?? 'Nessun prodotto disponibile.'; + $showStableUsage = (bool) ($showStableUsage ?? false); +@endphp + +
+
+
+
{{ $panelTitle }}
+ @if($panelDescription !== '') +
{{ $panelDescription }}
+ @endif +
+
{{ count($panelRows) }} prodotti
+
+ + @if($panelRows === []) +
{{ $panelEmptyMessage }}
+ @else +
+ @foreach($panelRows as $row) +
+
+ {{ $row['internal_code'] !== '' ? $row['internal_code'] : ('PROD-' . $row['id']) }} + @if(($row['identifier'] ?? '') !== '') + {{ $row['identifier'] }} + @endif + @if(($row['track_serials'] ?? false)) + seriali + @endif +
+ +
+
{{ $row['name'] !== '' ? $row['name'] : 'Prodotto senza nome' }}
+
{{ trim(($row['brand'] ?? '') . ' ' . ($row['model'] ?? '')) !== '' ? trim(($row['brand'] ?? '') . ' ' . ($row['model'] ?? '')) : 'Marca/modello non disponibili' }}
+ @if(($row['color_label'] ?? '') !== '') +
Colore: {{ $row['color_label'] }}
+ @endif +
+ +
+
+
Giacenza e utilizzo
+
Giacenza nota: {{ ($row['stock_quantity'] ?? null) !== null ? (int) $row['stock_quantity'] : 'n/d' }}
+
Usato nei ticket: {{ rtrim(rtrim(number_format((float) ($row['usage_quantity'] ?? 0), 2, ',', ''), '0'), ',') ?: '0' }}
+ @if($showStableUsage) +
Installato nello stabile: {{ rtrim(rtrim(number_format((float) ($row['stable_usage_quantity'] ?? 0), 2, ',', ''), '0'), ',') ?: '0' }}
+ @endif + @if(($row['last_used_at'] ?? '') !== '') +
Ultimo uso: {{ $row['last_used_at'] }}
+ @endif + @if($showStableUsage && ($row['stable_last_used_at'] ?? '') !== '') +
Ultimo uso stabile: {{ $row['stable_last_used_at'] }}
+ @endif +
+ +
+
Prezzi
+
Offerta interna: {{ ($row['own_offer_price'] ?? null) !== null ? ('€ ' . number_format((float) $row['own_offer_price'], 2, ',', '.')) : 'n/d' }}
+
Miglior esterno: {{ ($row['best_competitor_price'] ?? null) !== null ? ('€ ' . number_format((float) $row['best_competitor_price'], 2, ',', '.')) : 'n/d' }}{{ ($row['best_competitor_source'] ?? '') !== '' ? (' · ' . $row['best_competitor_source']) : '' }}
+
Ultimo acquisto: {{ ($row['last_purchase_total'] ?? null) !== null ? ('€ ' . number_format((float) $row['last_purchase_total'], 2, ',', '.')) : 'n/d' }}{{ ($row['last_purchase_date'] ?? '') !== '' ? (' · ' . $row['last_purchase_date']) : '' }}
+ @if(($row['price_wholesale'] ?? null) !== null) +
Wholesale salvato: € {{ number_format((float) $row['price_wholesale'], 2, ',', '.') }}
+ @endif +
+
+ + @if(!empty($row['purchase_history_labels'])) +
+
Storico acquisti registrato
+
+ @foreach($row['purchase_history_labels'] as $label) + {{ $label }} + @endforeach +
+
+ @endif + +
+ Offerte: {{ (int) ($row['offers_count'] ?? 0) }} + Codici: {{ (int) ($row['identifiers_count'] ?? 0) }} + Seriali: {{ (int) ($row['serials_count'] ?? 0) }} + Media: {{ (int) ($row['media_count'] ?? 0) }} +
+ +
+ @if(($row['amazon_referral_url'] ?? '') !== '') + Apri Amazon + @endif + @if(($row['private_links'] ?? 0) > 0) + {{ (int) $row['private_links'] }} link sorgente + @endif + @if(($row['categories'] ?? '') !== '') + {{ $row['categories'] }} + @endif +
+
+ @endforeach +
+ @endif +
\ No newline at end of file diff --git a/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php b/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php index 593b1e4..045bce0 100644 --- a/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php +++ b/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php @@ -83,6 +83,7 @@
+
Vista compatta TecnoRepair-like
@@ -666,6 +667,26 @@ @endif
+
+ @if(count($stabileInstalledProductRows) > 0) + @include('filament.components.catalog.product-hub-panel', [ + 'title' => 'Prodotti già installati nello stabile', + 'description' => 'Riepilogo calcolato dai ticket/interventi già registrati per questo stabile. Serve per riusare lampade, ricambi e codici coerenti con lo storico.', + 'rows' => $stabileInstalledProductRows, + 'emptyMessage' => 'Nessun prodotto installato rilevato nello storico ticket di questo stabile.', + 'showStableUsage' => true, + ]) + @endif + + @include('filament.components.catalog.product-hub-panel', [ + 'title' => 'Hub catalogo per questo ticket', + 'description' => 'Catalogo fornitore riusabile dal ticket: prezzi correnti, ultime righe acquisto, giacenza nota e riferimento Amazon quando disponibile.', + 'rows' => $ticketProductHubRows, + 'emptyMessage' => 'Nessun prodotto catalogato disponibile per questo fornitore.', + 'showStableUsage' => true, + ]) +
+
Allegati ticket
diff --git a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php index 5d7be47..5ccf79d 100644 --- a/resources/views/filament/pages/gescon/fornitore-scheda.blade.php +++ b/resources/views/filament/pages/gescon/fornitore-scheda.blade.php @@ -334,101 +334,14 @@ class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium"
-
- - - - - - - - - - - - - - - - @forelse($catalogoProdottiRows as $row) - - - - - - - - - - - - @empty - - - - @endforelse - -
CodiceProdottoMarca / modelloOrigineRif. internoCodiciSerialiOfferteCatalogo
{{ $row['internal_code'] !== '' ? $row['internal_code'] : '-' }} -
{{ $row['name'] !== '' ? $row['name'] : '-' }}
- @if($row['color_label'] !== '') -
Colore: {{ $row['color_label'] }}
- @endif -
{{ trim(($row['brand'] ?? '') . ' ' . ($row['model'] ?? '')) ?: '-' }} - @if(($row['source'] ?? '') === 'fattura_elettronica') - FE - @elseif(($row['source'] ?? '') === 'ncom_wholesale_csv') - CSV - @else - manuale - @endif - -
{{ $row['public_reference'] !== '' ? $row['public_reference'] : '-' }}
- @if(($row['publication_mode'] ?? '') === 'internal_only') -
solo interno
- @endif -
{{ (int) ($row['identifiers_count'] ?? 0) }} - {{ (int) ($row['serials_count'] ?? 0) }} - @if($row['track_serials']) - track - @endif - -
{{ (int) ($row['offers_count'] ?? 0) }} totali
-
- @if(($row['own_offer_price'] ?? null) !== null) - tuo € {{ number_format((float) $row['own_offer_price'], 2, ',', '.') }} - @else - tuo n/d - @endif -
-
- @if(($row['best_competitor_price'] ?? null) !== null) - competitor € {{ number_format((float) $row['best_competitor_price'], 2, ',', '.') }} - @if(($row['best_competitor_source'] ?? '') !== '') - | {{ $row['best_competitor_source'] }} - @endif - @else - competitor n/d - @endif -
- @if(($row['amazon_referral_url'] ?? '') !== '') -
Amazon referral pronto
- @endif -
-
Link sorgente: {{ (int) ($row['private_links'] ?? 0) }}
-
- @if(($row['price_wholesale'] ?? null) !== null) - € {{ number_format((float) $row['price_wholesale'], 2, ',', '.') }} - @else - prezzo n/d - @endif - @if(($row['stock_quantity'] ?? null) !== null) - | stock {{ (int) $row['stock_quantity'] }} - @endif -
- @if(($row['categories'] ?? '') !== '') -
{{ $row['categories'] }}
- @endif -
Nessun prodotto presente per questo fornitore.
+
+ @include('filament.components.catalog.product-hub-panel', [ + 'title' => 'Hub prodotti fornitore', + 'description' => 'Vista trasversale pronta da riusare anche nei ticket: giacenza nota, prezzi correnti, ultime righe acquisto e storico installazioni da ticket.', + 'rows' => $catalogoProdottiRows, + 'emptyMessage' => 'Nessun prodotto presente per questo fornitore.', + 'showStableUsage' => false, + ])
@@ -437,6 +350,7 @@ class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium"
IBAN: {{ $fornitore->iban ?? '-' }}
BIC: {{ $fornitore->bic ?? '-' }}
+
SIA: {{ $fornitore->sia_effettivo ?? '-' }}
Modalità predefinita: {{ $fornitore->modalita_pagamento_predefinita ?? '-' }}