Add SKU/GTIN fields, live barcode preview, and Shopify price import
Deploy / deploy (push) Successful in 26s
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:
1 parent
35cacb2e99
commit
25b32c717b
8 files changed
+124
-13
No files matched your search
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'] ?? '',
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in new issue
Block a user