Add shopify:import command for initial product/inventory import
Deploy / deploy (push) Successful in 14s
Deploy / deploy (push) Successful in 14s
Fetches all products and variants from Shopify API, creates local Products, Packings and ProductSizes with shopify_variant_id, and fills ProductInventory rows to match Shopify's inventory_quantity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e099e7a0a0
commit
94d94effa9
2 files changed
+108
-4
No files matched your search
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Packing;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Models\ProductInventory;
|
||||||
|
use App\Models\ProductSize;
|
||||||
|
use App\Services\ShopifyService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ShopifyImport extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'shopify:import {--dry-run : Nur anzeigen, nichts speichern}';
|
||||||
|
protected $description = 'Importiert Produkte und Lagerbestand direkt aus der Shopify API';
|
||||||
|
|
||||||
|
public function handle(ShopifyService $shopify): int
|
||||||
|
{
|
||||||
|
$dryRun = $this->option('dry-run');
|
||||||
|
$variants = $shopify->getAllVariants();
|
||||||
|
|
||||||
|
if (empty($variants)) {
|
||||||
|
$this->error('Keine Varianten von Shopify erhalten (Details im Log).');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Shopify-Varianten geladen: ' . count($variants));
|
||||||
|
|
||||||
|
$stats = ['products' => 0, 'sizes' => 0, 'inventory' => 0, 'skipped' => 0];
|
||||||
|
|
||||||
|
DB::transaction(function () use ($variants, $dryRun, &$stats) {
|
||||||
|
foreach ($variants as $variant) {
|
||||||
|
$packingRaw = $variant['option1'];
|
||||||
|
|
||||||
|
// Format: "Standbodenbeutel 100 g" oder "Metalldose 40 g"
|
||||||
|
if (! preg_match('/^(.+?)\s+(\d+(?:[.,]\d+)?)\s*g$/i', $packingRaw, $m)) {
|
||||||
|
$this->warn(" ⚠ Verpackungsformat nicht erkannt: '{$packingRaw}' ({$variant['sku']}) – übersprungen");
|
||||||
|
$stats['skipped']++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line(" {$variant['product_title']} | {$packingRaw} | SKU {$variant['sku']} | Bestand {$variant['inventory_quantity']}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packing = Packing::firstOrCreate([
|
||||||
|
'name' => trim($m[1]),
|
||||||
|
'size' => (float) str_replace(',', '.', $m[2]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$product = Product::firstOrCreate(
|
||||||
|
['title' => $variant['product_title']],
|
||||||
|
['recipes_id' => null],
|
||||||
|
);
|
||||||
|
if ($product->wasRecentlyCreated) {
|
||||||
|
$stats['products']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = ProductSize::firstOrCreate(
|
||||||
|
['product_id' => $product->id, 'packing_id' => $packing->id],
|
||||||
|
['sku' => $variant['sku'], 'shopify_variant_id' => $variant['id'], 'price' => 0],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bestehende Size ohne Variant-ID nachträglich verknüpfen
|
||||||
|
if (! $size->wasRecentlyCreated && ! $size->shopify_variant_id) {
|
||||||
|
$size->update(['shopify_variant_id' => $variant['id'], 'sku' => $variant['sku']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size->wasRecentlyCreated) {
|
||||||
|
$stats['sizes']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lagerbestand: aktuelle Differenz auffüllen
|
||||||
|
$localQty = ProductInventory::where('size_id', $size->id)->whereNull('out_at')->count();
|
||||||
|
$diff = $variant['inventory_quantity'] - $localQty;
|
||||||
|
|
||||||
|
if ($diff > 0) {
|
||||||
|
for ($i = 0; $i < $diff; $i++) {
|
||||||
|
ProductInventory::create([
|
||||||
|
'product_id' => $product->id,
|
||||||
|
'size_id' => $size->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$stats['inventory'] += $diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (! $dryRun) {
|
||||||
|
$this->info("✓ {$stats['products']} Produkte erstellt");
|
||||||
|
$this->info("✓ {$stats['sizes']} Varianten erstellt");
|
||||||
|
$this->info("✓ {$stats['inventory']} Lagereinträge erstellt");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stats['skipped']) {
|
||||||
|
$this->warn("⚠ {$stats['skipped']} Varianten übersprungen (Verpackungsformat unbekannt)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,10 +50,11 @@ class ShopifyService
|
|||||||
foreach ($response->json('products', []) as $product) {
|
foreach ($response->json('products', []) as $product) {
|
||||||
foreach ($product['variants'] as $variant) {
|
foreach ($product['variants'] as $variant) {
|
||||||
$variants[] = [
|
$variants[] = [
|
||||||
'id' => (string) $variant['id'],
|
'id' => (string) $variant['id'],
|
||||||
'sku' => $variant['sku'] ?? '',
|
'sku' => $variant['sku'] ?? '',
|
||||||
'inventory_quantity' => (int) ($variant['inventory_quantity'] ?? 0),
|
'inventory_quantity' => (int) ($variant['inventory_quantity'] ?? 0),
|
||||||
'product_title' => $product['title'],
|
'product_title' => $product['title'],
|
||||||
|
'option1' => $variant['option1'] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user