Erster Upload
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Packing;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductInventory;
|
||||
use App\Models\ProductSize;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ImportShopify extends Command
|
||||
{
|
||||
protected $signature = 'import:shopify {file : Pfad zur Shopify Inventory-CSV}';
|
||||
protected $description = 'Importiert Produkte aus einem Shopify Inventory-Export';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$file = $this->argument('file');
|
||||
|
||||
if (! file_exists($file)) {
|
||||
$this->error("Datei nicht gefunden: $file");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$rows = $this->parseCsv($file);
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->error('CSV ist leer oder konnte nicht gelesen werden.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$byHandle = collect($rows)->groupBy('Handle');
|
||||
$this->info("Gefunden: {$byHandle->count()} Produkte, " . count($rows) . ' Varianten');
|
||||
|
||||
$stats = ['products' => 0, 'sizes' => 0, 'inventory' => 0, 'skipped' => 0];
|
||||
|
||||
DB::transaction(function () use ($byHandle, &$stats) {
|
||||
foreach ($byHandle as $variants) {
|
||||
$first = $variants->first();
|
||||
|
||||
$product = Product::firstOrCreate(
|
||||
['title' => $first['Title']],
|
||||
['recipes_id' => null],
|
||||
);
|
||||
if ($product->wasRecentlyCreated) {
|
||||
$stats['products']++;
|
||||
}
|
||||
|
||||
foreach ($variants as $row) {
|
||||
$packingRaw = $row['Option1 Value'];
|
||||
|
||||
// Format: "Standbodenbeutel 50 g" oder "Metalldose 40 g"
|
||||
if (! preg_match('/^(.+?)\s+(\d+(?:[.,]\d+)?)\s*g$/i', $packingRaw, $m)) {
|
||||
$this->warn(" ⚠ Verpackungsformat nicht erkannt: '$packingRaw' – übersprungen");
|
||||
$stats['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$packing = Packing::firstOrCreate([
|
||||
'name' => trim($m[1]),
|
||||
'size' => (float) str_replace(',', '.', $m[2]),
|
||||
]);
|
||||
|
||||
$sku = $row['SKU'] ?: null;
|
||||
|
||||
$size = ProductSize::firstOrCreate(
|
||||
['product_id' => $product->id, 'packing_id' => $packing->id],
|
||||
['sku' => $sku, 'price' => 0],
|
||||
);
|
||||
if ($size->wasRecentlyCreated) {
|
||||
$stats['sizes']++;
|
||||
}
|
||||
|
||||
$onHand = (int) $row['On hand (current)'];
|
||||
for ($i = 0; $i < $onHand; $i++) {
|
||||
ProductInventory::create([
|
||||
'product_id' => $product->id,
|
||||
'size_id' => $size->id,
|
||||
]);
|
||||
$stats['inventory']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$this->info("✓ {$stats['products']} Produkte erstellt");
|
||||
$this->info("✓ {$stats['sizes']} Varianten erstellt");
|
||||
$this->info("✓ {$stats['inventory']} Lagereinträge erstellt");
|
||||
|
||||
if ($stats['skipped'] > 0) {
|
||||
$this->warn("⚠ {$stats['skipped']} Zeilen übersprungen (Verpackungsformat unbekannt)");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function parseCsv(string $file): array
|
||||
{
|
||||
$rows = [];
|
||||
$headers = null;
|
||||
|
||||
$handle = fopen($file, 'r');
|
||||
if ($handle === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
while (($data = fgetcsv($handle, 0, ',')) !== false) {
|
||||
if ($headers === null) {
|
||||
$headers = array_map('trim', $data);
|
||||
continue;
|
||||
}
|
||||
if (count($data) === count($headers)) {
|
||||
$rows[] = array_combine($headers, $data);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user