650 lines
28 KiB
PHP
Executable File
650 lines
28 KiB
PHP
Executable File
<?php
|
|
namespace App\Filament\Pages\Fornitore;
|
|
|
|
use App\Filament\Pages\Contabilita\FatturaElettronicaScheda;
|
|
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
|
|
use App\Models\Fornitore;
|
|
use App\Models\Product;
|
|
use App\Models\ProductIdentifier;
|
|
use App\Models\ProductOffer;
|
|
use App\Models\ProductSerial;
|
|
use App\Models\User;
|
|
use App\Modules\Contabilita\Models\FatturaFornitoreRiga;
|
|
use App\Services\Catalog\AmazonCreatorsApiService;
|
|
use App\Services\Catalog\AmazonCreatorsCatalogSyncService;
|
|
use App\Services\Catalog\FornitoreProductCatalogService;
|
|
use App\Support\StabileContext;
|
|
use BackedEnum;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use RuntimeException;
|
|
use UnitEnum;
|
|
|
|
class ProdottiCatalogo extends Page
|
|
{
|
|
use ResolvesOperatoreContext;
|
|
|
|
protected static ?string $navigationLabel = 'Prodotti';
|
|
|
|
protected static ?string $title = 'Catalogo prodotti fornitore';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-archive-box';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Fornitore';
|
|
|
|
protected static ?int $navigationSort = 4;
|
|
|
|
protected static ?string $slug = 'fornitore/prodotti';
|
|
|
|
protected string $view = 'filament.pages.fornitore.prodotti-catalogo';
|
|
|
|
public ?int $fornitoreId = null;
|
|
|
|
public ?string $fornitoreLabel = null;
|
|
|
|
public bool $missingAdminContext = false;
|
|
|
|
public string $search = '';
|
|
|
|
public string $activeTab = 'panoramica';
|
|
|
|
public string $amazonQuery = '';
|
|
|
|
public int $feExtractionLimit = 500;
|
|
|
|
public bool $amazonConfigured = false;
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $amazonRows = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $rows = [];
|
|
|
|
public ?int $activeStabileId = null;
|
|
|
|
public ?string $activeStabileLabel = null;
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $invoiceRows = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $serialRows = [];
|
|
|
|
public ?int $selectedProductId = null;
|
|
|
|
/** @var array<string, mixed>|null */
|
|
public ?array $detailCard = null;
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore', 'fornitore']);
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
[$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true);
|
|
|
|
if (! $fornitore instanceof Fornitore) {
|
|
$this->missingAdminContext = true;
|
|
return;
|
|
}
|
|
|
|
$this->fornitoreId = (int) $fornitore->id;
|
|
$this->fornitoreLabel = $this->getFornitoreLabel($fornitore);
|
|
$this->amazonConfigured = app(AmazonCreatorsApiService::class)->isConfigured();
|
|
$requestedTab = (string) request()->query('tab', 'panoramica');
|
|
if ($this->isValidTab($requestedTab)) {
|
|
$this->activeTab = $requestedTab;
|
|
}
|
|
$this->search = trim((string) request()->query('q', ''));
|
|
$this->selectedProductId = request()->integer('product');
|
|
|
|
$user = Auth::user();
|
|
if ($user instanceof User) {
|
|
$stabile = StabileContext::getActiveStabile($user);
|
|
$this->activeStabileId = $stabile ? (int) $stabile->id : null;
|
|
$this->activeStabileLabel = $stabile
|
|
? trim(implode(' - ', array_filter([
|
|
trim((string) ($stabile->codice_stabile ?? '')),
|
|
trim((string) ($stabile->denominazione ?? '')),
|
|
])))
|
|
: null;
|
|
}
|
|
|
|
$this->refreshRows();
|
|
|
|
if (($this->selectedProductId ?? 0) > 0) {
|
|
$this->openProductDetail((int) $this->selectedProductId);
|
|
}
|
|
}
|
|
|
|
public function updatedSearch(): void
|
|
{
|
|
$this->refreshRows();
|
|
}
|
|
|
|
public function setActiveTab(string $tab): void
|
|
{
|
|
if ($this->isValidTab($tab)) {
|
|
$this->activeTab = $tab;
|
|
}
|
|
}
|
|
|
|
public function refreshRows(): void
|
|
{
|
|
if (! $this->fornitoreId) {
|
|
$this->rows = [];
|
|
$this->serialRows = [];
|
|
return;
|
|
}
|
|
|
|
$fornitore = Fornitore::query()->find((int) $this->fornitoreId);
|
|
$supplierIds = $fornitore instanceof Fornitore ? $fornitore->catalogScopeSupplierIds() : [(int) $this->fornitoreId];
|
|
|
|
$term = trim($this->search);
|
|
$normalized = preg_replace('/[^A-Za-z0-9]+/', '', strtoupper($term)) ?? '';
|
|
|
|
$products = Product::query()
|
|
->with(['identifiers', 'offers'])
|
|
->withCount(['identifiers', 'offers', 'serials'])
|
|
->where('is_active', true)
|
|
->where(function ($query) use ($supplierIds): void {
|
|
$query->whereNull('default_fornitore_id')
|
|
->orWhereIn('default_fornitore_id', $supplierIds)
|
|
->orWhereHas('offers', fn($offerQuery) => $offerQuery->whereIn('fornitore_id', $supplierIds));
|
|
})
|
|
->when($term !== '', function ($query) use ($term, $normalized): void {
|
|
$query->where(function ($builder) use ($term, $normalized): void {
|
|
$builder->where('name', 'like', '%' . $term . '%')
|
|
->orWhere('brand', 'like', '%' . $term . '%')
|
|
->orWhere('model', 'like', '%' . $term . '%')
|
|
->orWhere('internal_code', 'like', '%' . $term . '%');
|
|
|
|
if ($normalized !== '') {
|
|
$builder->orWhereHas('identifiers', function ($identifierQuery) use ($normalized): void {
|
|
$identifierQuery->where('normalized_code', $normalized)
|
|
->orWhere('code_value', 'like', '%' . $normalized . '%');
|
|
});
|
|
}
|
|
});
|
|
})
|
|
->limit(120)
|
|
->get();
|
|
|
|
$this->rows = $products->map(function (Product $product) use ($supplierIds): array {
|
|
$identifier = $product->identifiers
|
|
->first(fn(ProductIdentifier $item): bool => in_array((int) ($item->fornitore_id ?? 0), $supplierIds, true)) ?? $product->identifiers->first();
|
|
$offer = $product->offers
|
|
->first(fn(ProductOffer $item): bool => in_array((int) ($item->fornitore_id ?? 0), $supplierIds, true)) ?? $product->offers->first();
|
|
|
|
return [
|
|
'id' => (int) $product->id,
|
|
'internal_code' => (string) ($product->internal_code ?? ''),
|
|
'label' => trim(implode(' ', array_filter([(string) $product->name, (string) ($product->brand ?? ''), (string) ($product->model ?? '')]))),
|
|
'barcode' => (string) ($identifier->code_value ?? ''),
|
|
'source' => (string) ($offer?->source_name ?: 'Catalogo interno'),
|
|
'price' => $offer?->price_amount !== null ? number_format((float) $offer->price_amount, 2, ',', '.') . ' ' . (string) ($offer->currency ?? 'EUR') : '-',
|
|
'external_url' => (string) ($offer?->referral_url ?: $offer?->external_url ?: $this->buildAmazonSearchUrl((string) $product->name)),
|
|
'serials_count' => (int) ($product->serials_count ?? 0),
|
|
'offers_count' => (int) ($product->offers_count ?? 0),
|
|
'identifiers_count' => (int) ($product->identifiers_count ?? 0),
|
|
];
|
|
})->all();
|
|
|
|
if (($this->selectedProductId ?? 0) > 0 && ! collect($this->rows)->contains(fn(array $row): bool => (int) ($row['id'] ?? 0) === (int) $this->selectedProductId)) {
|
|
$this->detailCard = null;
|
|
}
|
|
|
|
$this->refreshInvoiceRows($term, $normalized);
|
|
$this->refreshSerialRows($term, $normalized);
|
|
}
|
|
|
|
public function getFornitoreSchedaUrl(): ?string
|
|
{
|
|
if (! $this->fornitoreId) {
|
|
return null;
|
|
}
|
|
|
|
return \App\Filament\Pages\Gescon\FornitoreScheda::getUrl(['record' => (int) $this->fornitoreId], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getSerialiUrl(): string
|
|
{
|
|
return SerialiCatalogo::getUrl([
|
|
'fornitore' => (int) ($this->fornitoreId ?? 0),
|
|
'q' => trim($this->search),
|
|
], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getContabilitaFornitoriUrl(): string
|
|
{
|
|
return \App\Filament\Pages\Contabilita\FornitoriArchivio::getUrl(panel: 'admin-filament');
|
|
}
|
|
|
|
public function scanFattureElettronicheCatalogo(): void
|
|
{
|
|
if (! $this->fornitoreId || ! $this->activeStabileId) {
|
|
Notification::make()
|
|
->title('Contesto fornitore o stabile non disponibile')
|
|
->warning()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$fornitore = Fornitore::query()->find((int) $this->fornitoreId);
|
|
if (! $fornitore instanceof Fornitore) {
|
|
Notification::make()->title('Fornitore non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$limit = max(1, min(5000, (int) $this->feExtractionLimit));
|
|
$stats = app(FornitoreProductCatalogService::class)
|
|
->extractFromFattureElettroniche($fornitore, (int) $this->activeStabileId, $limit);
|
|
|
|
$this->refreshRows();
|
|
$this->activeTab = 'seriali';
|
|
|
|
Notification::make()
|
|
->title('Scansione FE completata')
|
|
->body(
|
|
'Fatture: ' . (int) ($stats['fatture'] ?? 0)
|
|
. ' · righe: ' . (int) ($stats['lines'] ?? 0)
|
|
. ' · nuovi prodotti: ' . (int) ($stats['products'] ?? 0)
|
|
. ' · identificativi: ' . (int) ($stats['identifiers'] ?? 0)
|
|
. ' · righe agganciate: ' . (int) ($stats['linked_rows'] ?? 0)
|
|
. ' · saltate: ' . (int) ($stats['skipped'] ?? 0)
|
|
)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function searchAmazonCatalog(): void
|
|
{
|
|
if (! $this->amazonConfigured) {
|
|
Notification::make()
|
|
->title('Amazon Creators API non configurata')
|
|
->body('Valorizza prima le variabili AMAZON_CREATORS_* in ambiente.')
|
|
->danger()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$query = trim($this->amazonQuery);
|
|
if ($query === '') {
|
|
Notification::make()->title('Inserisci una query Amazon')->warning()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->amazonRows = app(AmazonCreatorsCatalogSyncService::class)
|
|
->searchCatalog($query, 8);
|
|
} catch (RuntimeException $e) {
|
|
$this->amazonRows = [];
|
|
|
|
Notification::make()->title('Ricerca Amazon non riuscita')->body($e->getMessage())->danger()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($this->amazonRows === []) {
|
|
Notification::make()->title('Nessun risultato Amazon')->warning()->send();
|
|
}
|
|
}
|
|
|
|
public function saveAmazonCandidate(string $asin): void
|
|
{
|
|
$asin = trim($asin);
|
|
$row = collect($this->amazonRows)->first(fn(array $item): bool => (string) ($item['asin'] ?? '') === $asin);
|
|
if (! is_array($row) || ! is_array($row['item_payload'] ?? null)) {
|
|
Notification::make()->title('Risultato Amazon non disponibile')->danger()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$fornitore = $this->resolveCurrentFornitore();
|
|
if (! $fornitore instanceof Fornitore) {
|
|
Notification::make()->title('Fornitore non trovato')->danger()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$result = app(AmazonCreatorsCatalogSyncService::class)
|
|
->importItemForSupplier($fornitore, $row['item_payload'], [
|
|
'source' => 'amazon_catalog_search_ui',
|
|
'search_query' => trim($this->amazonQuery),
|
|
]);
|
|
} catch (RuntimeException $e) {
|
|
Notification::make()->title('Import Amazon non riuscito')->body($e->getMessage())->danger()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$product = $result['product'] ?? null;
|
|
|
|
Notification::make()
|
|
->title(! empty($result['created']) ? 'Prodotto Amazon memorizzato' : 'Prodotto Amazon aggiornato')
|
|
->body($product instanceof Product
|
|
? 'Articolo salvato nel catalogo come candidato da verificare: ' . ((string) ($product->internal_code ?? '') !== '' ? $product->internal_code . ' · ' : '') . (string) ($product->name ?? '')
|
|
: 'Import completato.')
|
|
->success()
|
|
->send();
|
|
|
|
$this->refreshRows();
|
|
$this->searchAmazonCatalog();
|
|
}
|
|
|
|
public function saveAllAmazonCandidates(): void
|
|
{
|
|
if ($this->amazonRows === []) {
|
|
Notification::make()->title('Nessun risultato Amazon da salvare')->warning()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$fornitore = $this->resolveCurrentFornitore();
|
|
if (! $fornitore instanceof Fornitore) {
|
|
Notification::make()->title('Fornitore non trovato')->danger()->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$result = app(AmazonCreatorsCatalogSyncService::class)->importSearchResultsForSupplier($fornitore, $this->amazonRows, [
|
|
'source' => 'amazon_catalog_search_bulk_ui',
|
|
'search_query' => trim($this->amazonQuery),
|
|
]);
|
|
|
|
$body = 'Nuovi: ' . (int) ($result['created'] ?? 0)
|
|
. ' · aggiornati: ' . (int) ($result['updated'] ?? 0)
|
|
. ' · saltati: ' . (int) ($result['skipped'] ?? 0);
|
|
|
|
$errors = array_values(array_filter(array_map(static fn(mixed $value): string => trim((string) $value), (array) ($result['errors'] ?? []))));
|
|
if ($errors !== []) {
|
|
$body .= ' · errori: ' . count($errors) . ' (' . Str::limit(implode(' | ', $errors), 180) . ')';
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Import Amazon completato')
|
|
->body($body)
|
|
->success()
|
|
->send();
|
|
|
|
$this->refreshRows();
|
|
$this->searchAmazonCatalog();
|
|
}
|
|
|
|
public function getLavorazioniUrl(): string
|
|
{
|
|
return LavorazioniOperative::getUrl(['fornitore' => (int) $this->fornitoreId], panel: 'admin-filament');
|
|
}
|
|
|
|
public function openProductDetail(int $productId): void
|
|
{
|
|
$scopeSupplierIds = [(int) $this->fornitoreId];
|
|
if ($this->fornitoreId) {
|
|
$fornitore = Fornitore::query()->find((int) $this->fornitoreId);
|
|
if ($fornitore instanceof Fornitore) {
|
|
$scopeSupplierIds = $fornitore->catalogScopeSupplierIds();
|
|
}
|
|
}
|
|
|
|
$product = Product::query()
|
|
->with([
|
|
'identifiers.fornitore' => fn($query) => $query->orderBy('ragione_sociale')->orderBy('nome')->orderBy('cognome'),
|
|
'identifiers' => fn($query) => $query->orderBy('code_type')->orderBy('code_value'),
|
|
'offers.fornitore' => fn($query) => $query->orderBy('ragione_sociale')->orderBy('nome')->orderBy('cognome'),
|
|
'offers' => fn($query) => $query->orderByDesc('last_seen_at')->orderByDesc('id'),
|
|
'serials' => fn($query) => $query->orderByDesc('purchase_date')->orderByDesc('id')->limit(8),
|
|
])
|
|
->find($productId);
|
|
|
|
if (! $product instanceof Product) {
|
|
$this->selectedProductId = null;
|
|
$this->detailCard = null;
|
|
return;
|
|
}
|
|
|
|
$identifiers = $product->identifiers
|
|
->filter(fn(ProductIdentifier $identifier): bool => (int) ($identifier->fornitore_id ?? 0) === 0 || in_array((int) ($identifier->fornitore_id ?? 0), $scopeSupplierIds, true))
|
|
->values();
|
|
|
|
$offers = $product->offers
|
|
->filter(fn(ProductOffer $offer): bool => (int) ($offer->fornitore_id ?? 0) === 0 || in_array((int) ($offer->fornitore_id ?? 0), $scopeSupplierIds, true))
|
|
->values();
|
|
|
|
$linkedSuppliers = collect()
|
|
->merge($identifiers->pluck('fornitore'))
|
|
->merge($offers->pluck('fornitore'))
|
|
->filter(fn(mixed $supplier): bool => $supplier instanceof Fornitore)
|
|
->unique(fn(Fornitore $supplier): int => (int) $supplier->id)
|
|
->map(fn(Fornitore $supplier): array=> [
|
|
'id' => (int) $supplier->id,
|
|
'label' => $this->getFornitoreLabel($supplier),
|
|
])
|
|
->values()
|
|
->all();
|
|
|
|
$this->selectedProductId = (int) $product->id;
|
|
$this->detailCard = [
|
|
'id' => (int) $product->id,
|
|
'internal_code' => (string) ($product->internal_code ?? ''),
|
|
'name' => (string) ($product->name ?? ''),
|
|
'brand' => (string) ($product->brand ?? ''),
|
|
'model' => (string) ($product->model ?? ''),
|
|
'description' => (string) ($product->description ?? ''),
|
|
'track_serials' => (bool) ($product->track_serials ?? false),
|
|
'linked_suppliers' => $linkedSuppliers,
|
|
'scope_supplier_ids' => $scopeSupplierIds,
|
|
'identifiers' => $identifiers->map(fn(ProductIdentifier $identifier): array=> [
|
|
'type' => (string) ($identifier->code_type ?? ''),
|
|
'role' => (string) ($identifier->code_role ?? ''),
|
|
'value' => (string) ($identifier->code_value ?? ''),
|
|
'supplier' => $identifier->fornitore instanceof Fornitore ? $this->getFornitoreLabel($identifier->fornitore) : '',
|
|
])->all(),
|
|
'offers' => $offers->map(fn(ProductOffer $offer): array=> [
|
|
'source' => (string) ($offer->source_name ?? 'Catalogo interno'),
|
|
'title' => (string) ($offer->title ?? ''),
|
|
'price' => $offer->price_amount !== null ? number_format((float) $offer->price_amount, 2, ',', '.') . ' ' . (string) ($offer->currency ?? 'EUR') : '-',
|
|
'availability' => (string) ($offer->availability ?? ''),
|
|
'url' => (string) ($offer->referral_url ?: $offer->external_url ?: ''),
|
|
'supplier' => $offer->fornitore instanceof Fornitore ? $this->getFornitoreLabel($offer->fornitore) : '',
|
|
])->all(),
|
|
'serials' => $product->serials->map(fn(ProductSerial $serial): array=> [
|
|
'serial' => (string) ($serial->serial_number ?? $serial->serial_number_2 ?? ''),
|
|
'invoice' => (string) ($serial->purchase_invoice_ref ?? ''),
|
|
'date' => $serial->purchase_date?->format('d/m/Y') ?: '',
|
|
'price' => $serial->purchase_price !== null ? number_format((float) $serial->purchase_price, 2, ',', '.') . ' ' . (string) ($serial->purchase_currency ?? 'EUR') : '-',
|
|
])->all(),
|
|
];
|
|
}
|
|
|
|
public function getRubricaUrl(): string
|
|
{
|
|
return RubricaClienti::getUrl(['fornitore' => (int) $this->fornitoreId], panel: 'admin-filament');
|
|
}
|
|
|
|
protected function buildAmazonSearchUrl(string $term): string
|
|
{
|
|
$query = ['k' => trim($term)];
|
|
$tag = trim((string) config('services.amazon.referral_tag', ''));
|
|
if ($tag !== '') {
|
|
$query['tag'] = $tag;
|
|
}
|
|
|
|
return 'https://www.amazon.it/s?' . http_build_query($query);
|
|
}
|
|
|
|
private function refreshInvoiceRows(string $term, string $normalized): void
|
|
{
|
|
$this->invoiceRows = [];
|
|
|
|
if (
|
|
$term === ''
|
|
|| ! $this->activeStabileId
|
|
|| ! Schema::hasTable('contabilita_fatture_fornitori_righe')
|
|
|| ! Schema::hasTable('contabilita_fatture_fornitori')
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$righe = FatturaFornitoreRiga::query()
|
|
->with([
|
|
'fattura:id,stabile_id,fornitore_id,fattura_elettronica_id,data_documento,numero_documento',
|
|
'fattura.fornitore:id,ragione_sociale,nome,cognome',
|
|
'product:id,internal_code,name',
|
|
])
|
|
->whereHas('fattura', fn(Builder $query) => $query->where('stabile_id', (int) $this->activeStabileId))
|
|
->where(function (Builder $query) use ($term, $normalized): void {
|
|
$query->where('descrizione', 'like', '%' . $term . '%');
|
|
|
|
if ($normalized !== '') {
|
|
$query->orWhereHas('product', function (Builder $productQuery) use ($term, $normalized): void {
|
|
$productQuery->where('internal_code', 'like', '%' . $term . '%')
|
|
->orWhere('name', 'like', '%' . $term . '%')
|
|
->orWhereHas('identifiers', function (Builder $identifierQuery) use ($normalized): void {
|
|
$identifierQuery->where('normalized_code', $normalized)
|
|
->orWhere('code_value', 'like', '%' . $normalized . '%');
|
|
});
|
|
});
|
|
}
|
|
})
|
|
->orderByDesc('id')
|
|
->limit(24)
|
|
->get();
|
|
|
|
$this->invoiceRows = $righe->map(function (FatturaFornitoreRiga $riga): array {
|
|
$fattura = $riga->fattura;
|
|
$fornitore = $fattura?->fornitore;
|
|
|
|
$fornitoreLabel = trim((string) ($fornitore?->ragione_sociale ?? ''));
|
|
if ($fornitoreLabel === '') {
|
|
$fornitoreLabel = trim(implode(' ', array_filter([
|
|
trim((string) ($fornitore?->nome ?? '')),
|
|
trim((string) ($fornitore?->cognome ?? '')),
|
|
])));
|
|
}
|
|
|
|
return [
|
|
'fattura_id' => (int) ($fattura?->id ?? 0),
|
|
'fattura_elettronica_id' => (int) ($fattura?->fattura_elettronica_id ?? 0),
|
|
'fornitore' => $fornitoreLabel !== '' ? $fornitoreLabel : 'Fornitore non associato',
|
|
'numero_documento' => trim((string) ($fattura?->numero_documento ?? '')),
|
|
'data_documento' => $fattura?->data_documento?->format('d/m/Y') ?: '',
|
|
'descrizione' => trim((string) ($riga->descrizione ?? '')),
|
|
'totale_euro' => is_numeric($riga->totale_euro) ? (float) $riga->totale_euro : null,
|
|
'product_id' => (int) ($riga->product_id ?? 0),
|
|
'product_label' => trim(implode(' · ', array_filter([
|
|
trim((string) ($riga->product?->internal_code ?? '')),
|
|
trim((string) ($riga->product?->name ?? '')),
|
|
]))),
|
|
'fe_url' => ($fattura?->fattura_elettronica_id ?? null)
|
|
? FatturaElettronicaScheda::getUrl(['record' => (int) $fattura->fattura_elettronica_id], panel : 'admin-filament')
|
|
: null,
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
private function refreshSerialRows(string $term, string $normalized): void
|
|
{
|
|
if (! $this->fornitoreId) {
|
|
$this->serialRows = [];
|
|
return;
|
|
}
|
|
|
|
$rows = ProductSerial::query()
|
|
->with(['product:id,internal_code,name,brand,model'])
|
|
->where('fornitore_id', (int) $this->fornitoreId)
|
|
->when($term !== '', function ($query) use ($term, $normalized): void {
|
|
$query->where(function ($builder) use ($term, $normalized): void {
|
|
$builder->where('serial_number', 'like', '%' . $term . '%')
|
|
->orWhere('serial_number_2', 'like', '%' . $term . '%')
|
|
->orWhere('product_model', 'like', '%' . $term . '%')
|
|
->orWhere('product_code', 'like', '%' . $term . '%')
|
|
->orWhere('purchase_invoice_ref', 'like', '%' . $term . '%');
|
|
|
|
if ($normalized !== '') {
|
|
$builder->orWhereHas('product', function (Builder $productQuery) use ($term, $normalized): void {
|
|
$productQuery->where('internal_code', 'like', '%' . $term . '%')
|
|
->orWhere('name', 'like', '%' . $term . '%')
|
|
->orWhereHas('identifiers', function (Builder $identifierQuery) use ($normalized): void {
|
|
$identifierQuery->where('normalized_code', $normalized)
|
|
->orWhere('code_value', 'like', '%' . $normalized . '%');
|
|
});
|
|
});
|
|
}
|
|
});
|
|
})
|
|
->orderByDesc('purchase_date')
|
|
->orderByDesc('id')
|
|
->limit(40)
|
|
->get();
|
|
|
|
$this->serialRows = $rows->map(function (ProductSerial $serial): array {
|
|
return [
|
|
'id' => (int) $serial->id,
|
|
'serial_number' => (string) ($serial->serial_number ?? ''),
|
|
'serial_number_2' => (string) ($serial->serial_number_2 ?? ''),
|
|
'product_label' => trim(implode(' · ', array_filter([
|
|
trim((string) ($serial->product?->internal_code ?? '')),
|
|
trim((string) ($serial->product?->name ?? '')),
|
|
trim((string) ($serial->product_model ?? '')),
|
|
]))),
|
|
'purchase_invoice_ref' => (string) ($serial->purchase_invoice_ref ?? ''),
|
|
'purchase_date' => $serial->purchase_date?->format('d/m/Y') ?: '',
|
|
'purchase_price' => $serial->purchase_price !== null ? (float) $serial->purchase_price : null,
|
|
'purchase_currency' => (string) ($serial->purchase_currency ?? 'EUR'),
|
|
'source' => (string) ($serial->source ?? ''),
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
private function isValidTab(string $tab): bool
|
|
{
|
|
return in_array($tab, ['panoramica', 'catalogo', 'seriali', 'acquisti'], true);
|
|
}
|
|
|
|
private function resolveCurrentFornitore(): ?Fornitore
|
|
{
|
|
return $this->fornitoreId ? Fornitore::query()->find((int) $this->fornitoreId) : null;
|
|
}
|
|
|
|
/** @return array<int, array<string, mixed>> */
|
|
public function getAmazonSetupChecklistProperty(): array
|
|
{
|
|
$items = [
|
|
['key' => 'AMAZON_CREATORS_ENABLED', 'required' => true, 'value' => (bool) config('services.amazon.creators_enabled', false) ? 'true' : 'false'],
|
|
['key' => 'AMAZON_CREATORS_API_BASE_URL', 'required' => true, 'value' => (string) config('services.amazon.creators_api_base_url', '')],
|
|
['key' => 'AMAZON_CREATORS_TOKEN_URL', 'required' => true, 'value' => (string) config('services.amazon.creators_token_url', '')],
|
|
['key' => 'AMAZON_CREATORS_CREDENTIAL_ID', 'required' => true, 'value' => (string) config('services.amazon.creators_credential_id', '')],
|
|
['key' => 'AMAZON_CREATORS_CREDENTIAL_SECRET', 'required' => true, 'value' => (string) config('services.amazon.creators_credential_secret', '')],
|
|
['key' => 'AMAZON_CREATORS_SEARCH_PATH', 'required' => true, 'value' => (string) data_get(config('services.amazon.creators_paths', []), 'searchItems', '')],
|
|
['key' => 'AMAZON_CREATORS_GET_ITEMS_PATH', 'required' => true, 'value' => (string) data_get(config('services.amazon.creators_paths', []), 'getItems', '')],
|
|
['key' => 'AMAZON_MARKETPLACE', 'required' => false, 'value' => (string) config('services.amazon.marketplace', 'www.amazon.it')],
|
|
['key' => 'AMAZON_REFERRAL_TAG', 'required' => false, 'value' => (string) config('services.amazon.referral_tag', '')],
|
|
];
|
|
|
|
return array_map(static function (array $item): array {
|
|
$rawValue = trim((string) ($item['value'] ?? ''));
|
|
$isBoolean = ($item['key'] ?? '') === 'AMAZON_CREATORS_ENABLED';
|
|
$configured = $isBoolean ? $rawValue === 'true' : $rawValue !== '';
|
|
|
|
return [
|
|
'key' => (string) ($item['key'] ?? ''),
|
|
'required' => (bool) ($item['required'] ?? false),
|
|
'configured' => $configured,
|
|
'display' => $rawValue !== '' ? Str::limit($rawValue, 54) : 'non valorizzata',
|
|
];
|
|
}, $items);
|
|
}
|
|
}
|