Add Shopify pull-from feature and fix product creation payload
Deploy / deploy (push) Successful in 14s

- Add "Aus Shopify laden" button on product edit page that pulls description
  and variant prices/SKUs/GTINs from Shopify back into SMS
- Add ShopifyService::getProduct() for fetching a single product
- Add 'options' to createProduct() payload so Shopify properly creates variants
  (without it Shopify ignores variant option values)
- Add request/response logging to createProduct() for diagnosing transfer issues
- Remove dead code from ProductResource (mutateFormData*, getRecord, getActions)
- Clean up imports in ProductResource

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Malek TellissiandClaude Sonnet 4.6 committed 2026-09-12 18:07:24 +02:00
1 parent 9ef24fc45f
commit 811f5a015e
3 files changed
+99 -58

No files matched your search

@@ -4,26 +4,25 @@ namespace App\Filament\User\Resources;
use App\Filament\User\Resources\ProductResource\Pages;
use App\Filament\User\Resources\ProductResource\RelationManagers;
use App\Models\Packing;
use App\Models\Product;
use App\Models\ProductSize;
use App\Models\Spice;
use App\Rules\SumEquals100;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Forms\Components\Actions\Action as FormAction;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\Resource;
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 {
@@ -235,52 +234,4 @@ class ProductResource extends Resource {
public static function getPluralLabel(): string {
return 'Produkte';
}
protected function mutateFormDataBeforeCreate(array $data): array {
if (isset($data['images'])) {
$data['images'] = json_encode($data['images']);
}
return $data;
}
protected function mutateFormDataBeforeUpdate(array $data): array {
if (isset($data['images'])) {
$data['images'] = json_encode($data['images']);
}
return $data;
}
public static function getRecord($id) {
return Product::with('sizes')->findOrFail($id); // Eager load spices relationship
}
public static function getTableRecordTitle() {
return function ($record) {
$images = json_decode($record->images, true);
return implode(', ', $images);
};
}
public static function getActions(): array {
return [
Actions\DeleteAction::make(),
Action::make('deleteImage')
->action(function ($record, $data) {
$images = json_decode($record->images, true);
$index = array_search($data['image'], $images);
if ($index !== false) {
unset($images[$index]);
$record->update(['images' => array_values($images)]);
}
})
->form([
Forms\Components\Select::make('image')
->options(function ($record) {
return array_combine($record->images, $record->images);
})
->required(),
]),
];
}
}
@@ -31,6 +31,15 @@ class EditProduct extends EditRecord
->exists()
)
->action(fn () => $this->createProductInShopify()),
Actions\Action::make('pull-from-shopify')
->label('Aus Shopify laden')
->icon('heroicon-o-arrow-down-tray')
->color('gray')
->requiresConfirmation()
->modalHeading('Aus Shopify aktualisieren')
->modalDescription('Überschreibt Beschreibung und Variantenpreise/-SKUs mit den aktuellen Shopify-Daten.')
->visible(fn () => (bool) $this->record->shopify_product_id)
->action(fn () => $this->pullFromShopify()),
Actions\Action::make('rezept-versionen')
->label('Rezept-Versionen')
->icon('heroicon-o-clock')
@@ -181,6 +190,50 @@ class EditProduct extends EditRecord
}
}
public function pullFromShopify(): void
{
$product = $this->record;
if (! $product->shopify_product_id) {
Notification::make()->title('Kein Shopify-Produkt verknüpft')->warning()->send();
return;
}
try {
$data = app(ShopifyService::class)->getProduct($product->shopify_product_id);
$updates = [];
if (! empty($data['description'])) {
$updates['description'] = $data['description'];
}
if ($updates) {
$product->update($updates);
}
$variantMap = collect($data['variants'])->keyBy('shopify_variant_id');
ProductSize::where('product_id', $product->id)
->whereNotNull('shopify_variant_id')
->get()
->each(function (ProductSize $size) use ($variantMap) {
$shopifyVariant = $variantMap->get($size->shopify_variant_id);
if (! $shopifyVariant) {
return;
}
$size->update([
'price' => $shopifyVariant['price'],
'sku' => $shopifyVariant['sku'] ?: $size->sku,
'gtin' => $shopifyVariant['gtin'] ?: $size->gtin,
]);
});
Notification::make()->title('Aus Shopify aktualisiert')->success()->send();
$this->redirect(ProductResource::getUrl('edit', ['record' => $product]));
} catch (\Throwable $e) {
Notification::make()->title('Shopify-Fehler')->body($e->getMessage())->danger()->send();
}
}
public function syncVariantWithShopify(int $sizeId): void
{
$size = ProductSize::find($sizeId);