Deploy / deploy (push) Successful in 14s
New header action on the product edit page creates the entire product (title, description, all variants) in Shopify via API. The returned shopify_product_id and per-variant shopify_variant_ids are stored locally. After success the page reloads so status badges update. Button is hidden once the product is already linked to Shopify. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
6.5 KiB
PHP
195 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\User\Resources\ProductResource\Pages;
|
|
|
|
use App\Filament\User\Resources\ProductResource;
|
|
use App\Models\ProductSize;
|
|
use App\Models\Recipe;
|
|
use App\Services\ShopifyService;
|
|
use Filament\Actions;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Resources\Pages\EditRecord;
|
|
|
|
class EditProduct extends EditRecord
|
|
{
|
|
protected static string $resource = ProductResource::class;
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
Actions\Action::make('shopify-create-product')
|
|
->label('In Shopify veröffentlichen')
|
|
->icon('heroicon-o-arrow-up-tray')
|
|
->color('warning')
|
|
->requiresConfirmation()
|
|
->modalHeading('Produkt in Shopify anlegen')
|
|
->modalDescription('Erstellt das Produkt mit allen Varianten in Shopify und verknüpft die IDs.')
|
|
->visible(fn () => ! $this->record->shopify_product_id)
|
|
->action(fn () => $this->createProductInShopify()),
|
|
Actions\Action::make('rezept-versionen')
|
|
->label('Rezept-Versionen')
|
|
->icon('heroicon-o-clock')
|
|
->color('gray')
|
|
->url(fn () => ProductResource::getUrl('recipe-history', ['record' => $this->record])),
|
|
Actions\DeleteAction::make(),
|
|
];
|
|
}
|
|
|
|
protected function afterSave(): void
|
|
{
|
|
$this->syncSizes();
|
|
$this->syncRecipe();
|
|
}
|
|
|
|
protected function syncSizes(): void
|
|
{
|
|
$product = $this->record;
|
|
$incoming = collect($this->data['sizes'] ?? []);
|
|
$keepIds = [];
|
|
|
|
foreach ($incoming as $size) {
|
|
if (empty($size['packing_id'])) {
|
|
continue;
|
|
}
|
|
|
|
$gtin = preg_replace('/\D/', '', (string) ($size['gtin'] ?? ''));
|
|
|
|
$ps = ProductSize::updateOrCreate(
|
|
[
|
|
'product_id' => $product->id,
|
|
'packing_id' => (int) $size['packing_id'],
|
|
],
|
|
[
|
|
'price' => $size['price'],
|
|
'sku' => $size['sku'] ?: null,
|
|
'gtin' => $gtin !== '' ? $gtin : null,
|
|
'description' => $size['description'] ?? null,
|
|
]
|
|
);
|
|
|
|
$keepIds[] = $ps->id;
|
|
}
|
|
|
|
// Only delete local-only sizes (no shopify_variant_id) that were removed
|
|
ProductSize::where('product_id', $product->id)
|
|
->whereNotIn('id', $keepIds)
|
|
->whereNull('shopify_variant_id')
|
|
->delete();
|
|
}
|
|
|
|
protected function syncRecipe(): void
|
|
{
|
|
$product = $this->record;
|
|
$spices = $this->data['recipe_spices'] ?? [];
|
|
|
|
if (empty($spices)) {
|
|
return;
|
|
}
|
|
|
|
$newPivot = collect($spices)->mapWithKeys(fn ($s) => [
|
|
(int) $s['spice_id'] => (float) $s['amount_in_percent'],
|
|
])->toArray();
|
|
|
|
$current = $product->recipe;
|
|
|
|
if ($current) {
|
|
$current->load('spices');
|
|
|
|
$existingPivot = $current->spices->mapWithKeys(fn ($s) => [
|
|
$s->id => (float) $s->pivot->amount_in_percent,
|
|
])->toArray();
|
|
|
|
if ($existingPivot == $newPivot) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
$nextVersion = Recipe::where('product_id', $product->id)->max('version') + 1;
|
|
|
|
$recipe = Recipe::create([
|
|
'name' => $product->title,
|
|
'product_id' => $product->id,
|
|
'version' => $nextVersion,
|
|
]);
|
|
|
|
$recipe->spices()->sync(
|
|
collect($newPivot)->mapWithKeys(fn ($pct, $id) => [
|
|
$id => ['amount_in_percent' => $pct],
|
|
])->toArray()
|
|
);
|
|
|
|
$product->update(['recipes_id' => $recipe->id]);
|
|
}
|
|
|
|
public function createProductInShopify(): void
|
|
{
|
|
$product = $this->record;
|
|
|
|
if ($product->shopify_product_id) {
|
|
Notification::make()->title('Bereits in Shopify verknüpft')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$sizes = ProductSize::with('packing')->where('product_id', $product->id)->get();
|
|
|
|
try {
|
|
$result = app(ShopifyService::class)->createProduct($product, $sizes);
|
|
|
|
$product->update(['shopify_product_id' => $result['shopify_product_id']]);
|
|
|
|
foreach ($result['variants'] as $packingId => $variantId) {
|
|
ProductSize::where('product_id', $product->id)
|
|
->where('packing_id', $packingId)
|
|
->update(['shopify_variant_id' => $variantId]);
|
|
}
|
|
|
|
Notification::make()->title('Produkt in Shopify erstellt')->success()->send();
|
|
$this->redirect(ProductResource::getUrl('edit', ['record' => $product]));
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Shopify-Fehler')->body($e->getMessage())->danger()->send();
|
|
}
|
|
}
|
|
|
|
public function publishVariantToShopify(int $sizeId): void
|
|
{
|
|
$size = ProductSize::with('packing')->find($sizeId);
|
|
$product = $this->record;
|
|
|
|
if (! $size || $size->shopify_variant_id || ! $product->shopify_product_id) {
|
|
Notification::make()
|
|
->title('Nicht möglich')
|
|
->body($product->shopify_product_id
|
|
? 'Variante bereits in Shopify verknüpft.'
|
|
: 'Produkt hat keine Shopify-ID — zuerst shopify:import ausführen.')
|
|
->warning()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$variantId = app(ShopifyService::class)->createVariant($product->shopify_product_id, $size);
|
|
$size->update(['shopify_variant_id' => $variantId]);
|
|
Notification::make()->title('Variante in Shopify veröffentlicht')->success()->send();
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Shopify-Fehler')->body($e->getMessage())->danger()->send();
|
|
}
|
|
}
|
|
|
|
public function syncVariantWithShopify(int $sizeId): void
|
|
{
|
|
$size = ProductSize::find($sizeId);
|
|
|
|
if (! $size || ! $size->shopify_variant_id) {
|
|
Notification::make()->title('Keine Shopify-Verknüpfung')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
app(ShopifyService::class)->updateVariant($size);
|
|
Notification::make()->title('Shopify synchronisiert')->success()->send();
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Shopify-Fehler')->body($e->getMessage())->danger()->send();
|
|
}
|
|
}
|
|
}
|