Add SKU/GTIN fields, live barcode preview, and Shopify price import
Deploy / deploy (push) Successful in 26s

- 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 <noreply@anthropic.com>
This commit is contained in:
Malek TellissiandClaude Sonnet 4.6 committed 2026-09-12 04:46:00 +02:00
1 parent 35cacb2e99
commit 25b32c717b
8 files changed
+124 -13

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ class ShopifyImport extends Command
$size = ProductSize::firstOrCreate( $size = ProductSize::firstOrCreate(
['product_id' => $product->id, 'packing_id' => $packing->id], ['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 // Bestehende Size ohne Variant-ID nachträglich verknüpfen
@@ -8,13 +8,17 @@ use App\Models\Product;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Support\HtmlString;
use App\Models\Packing; use App\Models\Packing;
use Picqer\Barcode\BarcodeGeneratorSVG;
class ProductResource extends Resource { class ProductResource extends Resource {
protected static ?string $model = Product::class; protected static ?string $model = Product::class;
@@ -51,10 +55,6 @@ class ProductResource extends Resource {
->required(), ->required(),
Repeater::make('sizes') Repeater::make('sizes')
->schema([ ->schema([
TextInput::make('gtin')
->label('GTIN'),
TextInput::make('description')
->label('Bezeichnung'),
Select::make('packing_id') Select::make('packing_id')
->label('Verpackung') ->label('Verpackung')
->options(function () { ->options(function () {
@@ -68,25 +68,48 @@ class ProductResource extends Resource {
->label('Preis (in EUR)') ->label('Preis (in EUR)')
->numeric() ->numeric()
->required(), ->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') ->label('Größen & Preise')
->columns(2) ->columns(2)
->addActionLabel('Option hinzufügen') ->addActionLabel('Option hinzufügen')
->afterStateHydrated(function ($state, $set, $record) { ->afterStateHydrated(function ($state, $set, $record) {
if ($record) { if ($record) {
// Load the spices relationship with pivot data
$record->load('sizes'); $record->load('sizes');
// Map the spices and their pivot data to the format the repeater expects
$sizesData = $record->sizes->map(function ($size) { $sizesData = $record->sizes->map(function ($size) {
return [ return [
'packing_id' => $size->id, 'packing_id' => $size->id,
'price' => $size->pivot->price, 'price' => $size->pivot->price,
'description' => $size->pivot->description, 'description' => $size->pivot->description,
'sku' => $size->pivot->sku,
'gtin' => $size->pivot->gtin,
]; ];
})->toArray(); })->toArray();
// Set the state for the repeater field to the mapped spices data
$set('sizes', $sizesData); $set('sizes', $sizesData);
} }
return $state; return $state;
@@ -19,8 +19,10 @@ class CreateProduct extends CreateRecord
ProductSize::create([ ProductSize::create([
'product_id' => $record->id, 'product_id' => $record->id,
'packing_id' => $size['packing_id'], 'packing_id' => $size['packing_id'],
'description' => $size['description'], 'description' => $size['description'] ?? null,
'price' => $size['price'], 'price' => $size['price'],
'sku' => $size['sku'] ?? null,
'gtin' => $size['gtin'] ?? null,
]); ]);
} }
return $record; return $record;
@@ -27,7 +27,12 @@ class EditProduct extends EditRecord
$sizes = collect($this->data['sizes'] ?? []) $sizes = collect($this->data['sizes'] ?? [])
->mapWithKeys(function ($size) { ->mapWithKeys(function ($size) {
return [ 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(); ->toArray();
+1 -2
View File
@@ -22,8 +22,7 @@ class Product extends Model
public function sizes() public function sizes()
{ {
return $this->belongsToMany(Packing::class, 'products_sizes') return $this->belongsToMany(Packing::class, 'products_sizes')
->withPivot('price') ->withPivot('price', 'description', 'sku', 'gtin')
->withPivot('description')
->withTimestamps(); ->withTimestamps();
} }
+1
View File
@@ -53,6 +53,7 @@ class ShopifyService
$variants[] = [ $variants[] = [
'id' => (string) $variant['id'], 'id' => (string) $variant['id'],
'sku' => $variant['sku'] ?? '', 'sku' => $variant['sku'] ?? '',
'price' => (float) ($variant['price'] ?? 0),
'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'] ?? '', 'option1' => $variant['option1'] ?? '',
+1
View File
@@ -11,6 +11,7 @@
"laravel/framework": "^11.9", "laravel/framework": "^11.9",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"leandrocfe/filament-apex-charts": "^3.1", "leandrocfe/filament-apex-charts": "^3.1",
"picqer/php-barcode-generator": "^3.3",
"spatie/laravel-activitylog": "^4.9", "spatie/laravel-activitylog": "^4.9",
"z3d0x/filament-logger": "^0.7.2" "z3d0x/filament-logger": "^0.7.2"
}, },
Generated
+81 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a582ff19a1af282fb47d6332accfd2d9", "content-hash": "45cc716f18a426dc09aceb48f3a350d8",
"packages": [ "packages": [
{ {
"name": "anourvalar/eloquent-serialize", "name": "anourvalar/eloquent-serialize",
@@ -4055,6 +4055,86 @@
], ],
"time": "2026-08-24T00:54:40+00:00" "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", "name": "psr/cache",
"version": "3.0.0", "version": "3.0.0",