Add Shopify publish/sync per product variant
Deploy / deploy (push) Successful in 14s

Each size row in the product form now shows a Shopify status badge
(green ✓ Shopify / gray Lokal). For saved variants, an action button
appears: 'Zu Shopify' for local-only variants (creates via Shopify
API and stores the returned variant ID), 'Shopify sync' for linked
variants (pushes price/SKU/GTIN to Shopify).

Also fixes:
- gtin missing from ProductSize::$fillable (was silently not saved)
- shopify_product_id added to products table and stored on import
- sizes afterStateHydrated now loads ProductSize directly instead of
  via the belongsToMany pivot (gets product_size_id, shopify_variant_id)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Malek TellissiandClaude Sonnet 4.6 committed 2026-09-12 17:20:26 +02:00
1 parent e187465553
commit 3ebd9845b0
7 files changed
+189 -20

No files matched your search

+57 -11
View File
@@ -18,9 +18,12 @@ use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\HtmlString;
use App\Models\Packing;
use App\Models\ProductSize;
use App\Models\Recipe;
use App\Models\Spice;
use App\Rules\SumEquals100;
use Filament\Forms\Components\Actions\Action as FormAction;
use Filament\Forms\Components\Hidden;
use Picqer\Barcode\BarcodeGeneratorSVG;
class ProductResource extends Resource {
@@ -94,6 +97,15 @@ class ProductResource extends Resource {
->columnSpanFull(),
Repeater::make('sizes')
->schema([
Hidden::make('product_size_id'),
Hidden::make('shopify_variant_id'),
Placeholder::make('shopify_status')
->label('Shopify')
->content(fn (Get $get): HtmlString =>
$get('shopify_variant_id')
? new HtmlString('<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">✓ Shopify</span>')
: new HtmlString('<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500">Lokal</span>')
),
Select::make('packing_id')
->label('Verpackung')
->options(function () {
@@ -141,19 +153,53 @@ class ProductResource extends Resource {
->label('Größen & Preise')
->columns(2)
->addActionLabel('Option hinzufügen')
->extraItemActions([
FormAction::make('shopify')
->label(fn (array $arguments, Repeater $component): string =>
($component->getState()[$arguments['item']]['shopify_variant_id'] ?? null)
? 'Shopify sync' : 'Zu Shopify'
)
->icon(fn (array $arguments, Repeater $component): string =>
($component->getState()[$arguments['item']]['shopify_variant_id'] ?? null)
? 'heroicon-o-arrow-path' : 'heroicon-o-arrow-up-tray'
)
->color(fn (array $arguments, Repeater $component): string =>
($component->getState()[$arguments['item']]['shopify_variant_id'] ?? null)
? 'info' : 'warning'
)
->requiresConfirmation()
->action(function (array $arguments, Repeater $component): void {
$item = $component->getState()[$arguments['item']] ?? [];
$sizeId = (int) ($item['product_size_id'] ?? 0);
if (! $sizeId) {
return;
}
$page = $component->getContainer()->getLivewire();
if ($item['shopify_variant_id'] ?? null) {
$page->syncVariantWithShopify($sizeId);
} else {
$page->publishVariantToShopify($sizeId);
}
})
->visible(fn (array $arguments, Repeater $component): bool =>
! empty($component->getState()[$arguments['item']]['product_size_id'] ?? null)
),
])
->afterStateHydrated(function ($state, $set, $record) {
if ($record) {
$record->load('sizes');
$sizesData = $record->sizes->map(function ($size) {
return [
'packing_id' => $size->id,
'price' => $size->pivot->price,
'description' => $size->pivot->description,
'sku' => $size->pivot->sku,
'gtin' => $size->pivot->gtin,
];
})->toArray();
$sizesData = ProductSize::with('packing')
->where('product_id', $record->id)
->get()
->map(fn ($ps) => [
'product_size_id' => $ps->id,
'shopify_variant_id' => $ps->shopify_variant_id,
'packing_id' => $ps->packing_id,
'price' => $ps->price,
'sku' => $ps->sku,
'gtin' => $ps->gtin,
'description' => $ps->description,
])
->toArray();
$set('sizes', $sizesData);
}