netgescon-day0/app/Console/Commands/SyncAmazonCreatorsCatalogCommand.php

119 lines
4.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Product;
use App\Services\Catalog\AmazonCreatorsCatalogSyncService;
use Illuminate\Console\Command;
use RuntimeException;
class SyncAmazonCreatorsCatalogCommand extends Command
{
protected $signature = 'catalog:sync-amazon-creators
{product : ID o internal_code del prodotto}
{--asin= : ASIN da forzare}
{--query= : Query da forzare per SearchItems}
{--json= : File JSON con payload item o risposta SDK/API}
{--marketplace= : Marketplace, es. www.amazon.it}
{--dry-run : Risolve il mapping senza scrivere offerte/media/codici}';
protected $description = 'Sincronizza un prodotto NetGescon con Amazon Creators API o con un payload JSON esportato dallo SDK.';
public function handle(AmazonCreatorsCatalogSyncService $syncService): int
{
$product = $this->resolveProduct((string) $this->argument('product'));
if (! $product instanceof Product) {
$this->error('Prodotto non trovato.');
return self::FAILURE;
}
try {
$options = [
'asin' => trim((string) $this->option('asin')),
'query' => trim((string) $this->option('query')),
'marketplace' => trim((string) $this->option('marketplace')),
'dry_run' => (bool) $this->option('dry-run'),
'source' => 'api',
];
$jsonPath = trim((string) $this->option('json'));
if ($jsonPath !== '') {
$options['response'] = $this->readJsonFile($jsonPath);
$options['source'] = 'json';
}
$result = $syncService->syncProduct($product, $options);
} catch (\Throwable $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
$this->table(
['Campo', 'Valore'],
[
['Prodotto', (string) ($product->internal_code ?: ('#' . $product->id)) . ' · ' . (string) ($product->name ?? '')],
['Fonte', (string) ($result['source'] ?? 'api')],
['Dry run', ! empty($result['dry_run']) ? 'si' : 'no'],
['ASIN', (string) ($result['asin'] ?? '')],
['EAN', (string) ($result['ean'] ?? '')],
['Titolo Amazon', (string) ($result['title'] ?? '')],
['Prezzo', $this->formatMoney($result['price_amount'] ?? null, (string) ($result['currency'] ?? 'EUR'))],
['Prezzo listino', $this->formatMoney($result['price_compare_at'] ?? null, (string) ($result['currency'] ?? 'EUR'))],
['Disponibilita', (string) ($result['availability'] ?? '')],
['Detail URL', (string) ($result['detail_url'] ?? '')],
['Referral URL', (string) ($result['referral_url'] ?? '')],
['Immagine', (string) ($result['image_url'] ?? '')],
['Codici creati', (string) ($result['identifiers_created'] ?? 0)],
['Media creati', (string) ($result['media_created'] ?? 0)],
['Offer ID', (string) ($result['offer_id'] ?? '')],
]
);
return self::SUCCESS;
}
private function resolveProduct(string $value): ?Product
{
$value = trim($value);
if ($value === '') {
return null;
}
$query = Product::query();
if (is_numeric($value)) {
$product = (clone $query)->find((int) $value);
if ($product instanceof Product) {
return $product;
}
}
return (clone $query)
->where('internal_code', $value)
->orWhere('canonical_key', $value)
->first();
}
/** @return array<string, mixed> */
private function readJsonFile(string $path): array
{
if (! is_file($path)) {
throw new RuntimeException('File JSON non trovato: ' . $path);
}
$decoded = json_decode((string) file_get_contents($path), true);
if (! is_array($decoded)) {
throw new RuntimeException('Il file JSON non contiene un payload valido.');
}
return $decoded;
}
private function formatMoney(mixed $amount, string $currency): string
{
return is_numeric($amount)
? number_format((float) $amount, 2, ',', '.') . ' ' . strtoupper(trim($currency) !== '' ? $currency : 'EUR')
: '-';
}
}