From 25b32c717bb178e5276358616f0cbebc5379aab5 Mon Sep 17 00:00:00 2001 From: Malek Tellissi Date: Sat, 12 Sep 2026 04:46:00 +0200 Subject: [PATCH] Add SKU/GTIN fields, live barcode preview, and Shopify price import - Add SKU and GTIN fields to product variant repeater - Render EAN/UPC barcode live from GTIN input (picqer/php-barcode-generator) - Pull variant price from Shopify API instead of defaulting to 0 - Sync sku/gtin through EditProduct and CreateProduct save logic Co-Authored-By: Claude Sonnet 4.6 --- app/Console/Commands/ShopifyImport.php | 2 +- .../User/Resources/ProductResource.php | 41 ++++++++-- .../ProductResource/Pages/CreateProduct.php | 10 ++- .../ProductResource/Pages/EditProduct.php | 7 +- app/Models/Product.php | 3 +- app/Services/ShopifyService.php | 1 + composer.json | 1 + composer.lock | 82 ++++++++++++++++++- 8 files changed, 129 insertions(+), 18 deletions(-) diff --git a/app/Console/Commands/ShopifyImport.php b/app/Console/Commands/ShopifyImport.php index 44fba00..ce15150 100644 --- a/app/Console/Commands/ShopifyImport.php +++ b/app/Console/Commands/ShopifyImport.php @@ -71,7 +71,7 @@ class ShopifyImport extends Command $size = ProductSize::firstOrCreate( ['product_id' => $product->id, 'packing_id' => $packing->id], - ['sku' => $variant['sku'], 'shopify_variant_id' => $variant['id'], 'price' => 0], + ['sku' => $variant['sku'], 'shopify_variant_id' => $variant['id'], 'price' => $variant['price']], ); // Bestehende Size ohne Variant-ID nachträglich verknüpfen diff --git a/app/Filament/User/Resources/ProductResource.php b/app/Filament/User/Resources/ProductResource.php index ba7de1e..75d884c 100644 --- a/app/Filament/User/Resources/ProductResource.php +++ b/app/Filament/User/Resources/ProductResource.php @@ -8,13 +8,17 @@ use App\Models\Product; use Filament\Forms; use Filament\Forms\Form; use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; +use Filament\Forms\Get; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; +use Illuminate\Support\HtmlString; use App\Models\Packing; +use Picqer\Barcode\BarcodeGeneratorSVG; class ProductResource extends Resource { protected static ?string $model = Product::class; @@ -51,10 +55,6 @@ class ProductResource extends Resource { ->required(), Repeater::make('sizes') ->schema([ - TextInput::make('gtin') - ->label('GTIN'), - TextInput::make('description') - ->label('Bezeichnung'), Select::make('packing_id') ->label('Verpackung') ->options(function () { @@ -68,25 +68,48 @@ class ProductResource extends Resource { ->label('Preis (in EUR)') ->numeric() ->required(), + TextInput::make('sku') + ->label('SKU'), + TextInput::make('gtin') + ->label('GTIN') + ->live(debounce: 500), + Placeholder::make('barcode_preview') + ->label('Barcode') + ->content(function (Get $get): HtmlString { + $gtin = trim((string) $get('gtin')); + if (empty($gtin)) { + return new HtmlString(''); + } + $type = match (strlen($gtin)) { + 8 => BarcodeGeneratorSVG::TYPE_EAN_8, + 13 => BarcodeGeneratorSVG::TYPE_EAN_13, + 12 => BarcodeGeneratorSVG::TYPE_UPC_A, + 14 => BarcodeGeneratorSVG::TYPE_CODABAR, + default => BarcodeGeneratorSVG::TYPE_CODE_128, + }; + $svg = (new BarcodeGeneratorSVG())->getBarcode($gtin, $type, 2, 60); + return new HtmlString($svg); + }) + ->columnSpanFull() + ->visible(fn (Get $get) => filled($get('gtin'))), ]) ->label('Größen & Preise') ->columns(2) ->addActionLabel('Option hinzufügen') ->afterStateHydrated(function ($state, $set, $record) { if ($record) { - // Load the spices relationship with pivot data $record->load('sizes'); - // Map the spices and their pivot data to the format the repeater expects $sizesData = $record->sizes->map(function ($size) { return [ - 'packing_id' => $size->id, - 'price' => $size->pivot->price, + 'packing_id' => $size->id, + 'price' => $size->pivot->price, 'description' => $size->pivot->description, + 'sku' => $size->pivot->sku, + 'gtin' => $size->pivot->gtin, ]; })->toArray(); - // Set the state for the repeater field to the mapped spices data $set('sizes', $sizesData); } return $state; diff --git a/app/Filament/User/Resources/ProductResource/Pages/CreateProduct.php b/app/Filament/User/Resources/ProductResource/Pages/CreateProduct.php index fe5aae9..eb3c93d 100644 --- a/app/Filament/User/Resources/ProductResource/Pages/CreateProduct.php +++ b/app/Filament/User/Resources/ProductResource/Pages/CreateProduct.php @@ -17,10 +17,12 @@ class CreateProduct extends CreateRecord foreach ($data['sizes'] as $size) { ProductSize::create([ - 'product_id' => $record->id, - 'packing_id' => $size['packing_id'], - 'description' => $size['description'], - 'price' => $size['price'], + 'product_id' => $record->id, + 'packing_id' => $size['packing_id'], + 'description' => $size['description'] ?? null, + 'price' => $size['price'], + 'sku' => $size['sku'] ?? null, + 'gtin' => $size['gtin'] ?? null, ]); } return $record; diff --git a/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php b/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php index e9e75ca..4c35679 100644 --- a/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php +++ b/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php @@ -27,7 +27,12 @@ class EditProduct extends EditRecord $sizes = collect($this->data['sizes'] ?? []) ->mapWithKeys(function ($size) { return [ - $size['packing_id'] => ['description' => $size['description'], 'price' =>$size['price']], + $size['packing_id'] => [ + 'description' => $size['description'] ?? null, + 'price' => $size['price'], + 'sku' => $size['sku'] ?? null, + 'gtin' => $size['gtin'] ?? null, + ], ]; }) ->toArray(); diff --git a/app/Models/Product.php b/app/Models/Product.php index 1487dd4..f07bfe5 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -22,8 +22,7 @@ class Product extends Model public function sizes() { return $this->belongsToMany(Packing::class, 'products_sizes') - ->withPivot('price') - ->withPivot('description') + ->withPivot('price', 'description', 'sku', 'gtin') ->withTimestamps(); } diff --git a/app/Services/ShopifyService.php b/app/Services/ShopifyService.php index e5f074d..b57734d 100644 --- a/app/Services/ShopifyService.php +++ b/app/Services/ShopifyService.php @@ -53,6 +53,7 @@ class ShopifyService $variants[] = [ 'id' => (string) $variant['id'], 'sku' => $variant['sku'] ?? '', + 'price' => (float) ($variant['price'] ?? 0), 'inventory_quantity' => (int) ($variant['inventory_quantity'] ?? 0), 'product_title' => $product['title'], 'option1' => $variant['option1'] ?? '', diff --git a/composer.json b/composer.json index f51696e..aef153b 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "laravel/framework": "^11.9", "laravel/tinker": "^2.9", "leandrocfe/filament-apex-charts": "^3.1", + "picqer/php-barcode-generator": "^3.3", "spatie/laravel-activitylog": "^4.9", "z3d0x/filament-logger": "^0.7.2" }, diff --git a/composer.lock b/composer.lock index 515fd5d..d8e2d98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a582ff19a1af282fb47d6332accfd2d9", + "content-hash": "45cc716f18a426dc09aceb48f3a350d8", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -4055,6 +4055,86 @@ ], "time": "2026-08-24T00:54:40+00:00" }, + { + "name": "picqer/php-barcode-generator", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/picqer/php-barcode-generator.git", + "reference": "6beada2a30623832ff41b8c7d307c169736b8552" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/picqer/php-barcode-generator/zipball/6beada2a30623832ff41b8c7d307c169736b8552", + "reference": "6beada2a30623832ff41b8c7d307c169736b8552", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "ext-bcmath": "Barcode IMB (Intelligent Mail Barcode) needs bcmath extension", + "ext-gd": "For JPG and PNG generators, GD or Imagick is required", + "ext-imagick": "For JPG and PNG generators, GD or Imagick is required" + }, + "type": "library", + "autoload": { + "psr-4": { + "Picqer\\Barcode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Casper Bakker", + "email": "info@picqer.com", + "homepage": "https://picqer.com" + }, + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "homepage": "http://nicolaasuni.tecnick.com" + } + ], + "description": "An easy to use, non-bloated, barcode generator in PHP. Creates SVG, PNG, JPG and HTML images from the most used 1D barcode standards.", + "homepage": "https://github.com/picqer/php-barcode-generator", + "keywords": [ + "CODABAR", + "Code11", + "Code93", + "EAN13", + "KIX", + "KIXCODE", + "MSI", + "POSTNET", + "Pharma", + "Standard 2 of 5", + "barcode", + "barcode generator", + "code128", + "code39", + "ean", + "html", + "jpeg", + "jpg", + "php", + "png", + "svg", + "upc" + ], + "support": { + "issues": "https://github.com/picqer/php-barcode-generator/issues", + "source": "https://github.com/picqer/php-barcode-generator/tree/v3.3.0" + }, + "time": "2026-08-22T09:50:32+00:00" + }, { "name": "psr/cache", "version": "3.0.0",