diff --git a/app/Console/Commands/ShopifyImport.php b/app/Console/Commands/ShopifyImport.php
index ce15150..1288162 100644
--- a/app/Console/Commands/ShopifyImport.php
+++ b/app/Console/Commands/ShopifyImport.php
@@ -58,7 +58,7 @@ class ShopifyImport extends Command
$product = Product::firstOrCreate(
['title' => $variant['product_title']],
- ['recipes_id' => null],
+ ['recipes_id' => null, 'description' => $variant['product_description']],
);
if ($product->wasRecentlyCreated) {
$stats['products']++;
@@ -67,6 +67,8 @@ class ShopifyImport extends Command
'product' => $product,
'image_urls' => $variant['image_urls'],
];
+ } elseif (empty($product->description) && ! empty($variant['product_description'])) {
+ $product->update(['description' => $variant['product_description']]);
}
$size = ProductSize::firstOrCreate(
diff --git a/app/Filament/User/Resources/ProductResource.php b/app/Filament/User/Resources/ProductResource.php
index 785f9bd..7e110b6 100644
--- a/app/Filament/User/Resources/ProductResource.php
+++ b/app/Filament/User/Resources/ProductResource.php
@@ -18,6 +18,7 @@ use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\HtmlString;
use App\Models\Packing;
+use App\Models\Recipe;
use Picqer\Barcode\BarcodeGeneratorSVG;
class ProductResource extends Resource {
@@ -45,21 +46,32 @@ class ProductResource extends Resource {
->columnSpanFull(),
Forms\Components\Textarea::make('description')
->label('Beschreibung'),
- Forms\Components\Textarea::make('package_content')
- ->label('Inhaltsangabe'),
Forms\Components\Select::make('recipes_id')
->label('Rezept')
->native(false)
->searchable()
+ ->live()
->relationship('recipe', 'name')
+ ->getOptionLabelFromRecordUsing(fn (Recipe $r) => "{$r->name} (v{$r->version})")
->required()
- ->createOptionForm([
+ ->createOptionForm(fn () => [
Forms\Components\TextInput::make('name')
->label('Name')
- ->required(),
+ ->required()
+ ->default(fn ($livewire) => $livewire->data['title'] ?? ''),
Forms\Components\Textarea::make('description')
->label('Beschreibung'),
])
+ ->createOptionUsing(function (array $data, $livewire) {
+ $productId = $livewire->record?->id;
+ $nextVersion = Recipe::where('product_id', $productId)->max('version') + 1;
+ return Recipe::create([
+ 'name' => $data['name'],
+ 'description' => $data['description'] ?? null,
+ 'product_id' => $productId,
+ 'version' => $nextVersion,
+ ])->getKey();
+ })
->editOptionForm([
Forms\Components\TextInput::make('name')
->label('Name')
@@ -67,6 +79,20 @@ class ProductResource extends Resource {
Forms\Components\Textarea::make('description')
->label('Beschreibung'),
]),
+ Placeholder::make('recipe_summary')
+ ->label('Rezept-Inhalt')
+ ->content(function (Forms\Get $get, $record): HtmlString {
+ $recipeId = $get('recipes_id');
+ $recipe = $recipeId ? Recipe::with('spices')->find($recipeId) : $record?->recipe?->load('spices');
+ if (! $recipe || $recipe->spices->isEmpty()) {
+ return new HtmlString('Keine Zutaten hinterlegt');
+ }
+ $items = $recipe->spices->map(fn ($s) => "
{$s->name} ({$s->pivot->amount_in_percent}%)")->implode('');
+ $meta = "v{$recipe->version} ยท erstellt " . $recipe->created_at->format('d.m.Y');
+ return new HtmlString("{$meta}
");
+ })
+ ->columnSpanFull()
+ ->visible(fn (Forms\Get $get, $record) => filled($get('recipes_id')) || $record?->recipes_id),
Repeater::make('sizes')
->schema([
Select::make('packing_id')
diff --git a/app/Models/Product.php b/app/Models/Product.php
index f07bfe5..b9740c6 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -19,6 +19,11 @@ class Product extends Model
return $this->belongsTo(Recipe::class, 'recipes_id');
}
+ public function recipeVersions()
+ {
+ return $this->hasMany(Recipe::class)->orderByDesc('version');
+ }
+
public function sizes()
{
return $this->belongsToMany(Packing::class, 'products_sizes')
diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php
index caf1514..a244ad5 100644
--- a/app/Models/Recipe.php
+++ b/app/Models/Recipe.php
@@ -9,7 +9,12 @@ class Recipe extends Model
{
use HasFactory;
- protected $fillable = ['name', 'description'];
+ protected $fillable = ['name', 'description', 'product_id', 'version'];
+
+ public function product()
+ {
+ return $this->belongsTo(Product::class);
+ }
public function spices()
{
diff --git a/app/Services/ShopifyService.php b/app/Services/ShopifyService.php
index b57734d..9bc2dc6 100644
--- a/app/Services/ShopifyService.php
+++ b/app/Services/ShopifyService.php
@@ -25,7 +25,7 @@ class ShopifyService
public function getAllVariants(): array
{
$variants = [];
- $url = "{$this->baseUrl}/products.json?limit=250&fields=id,title,variants,images";
+ $url = "{$this->baseUrl}/products.json?limit=250&fields=id,title,body_html,variants,images";
while ($url) {
$response = Http::withHeader('X-Shopify-Access-Token', $this->token)->get($url);
@@ -55,9 +55,10 @@ class ShopifyService
'sku' => $variant['sku'] ?? '',
'price' => (float) ($variant['price'] ?? 0),
'inventory_quantity' => (int) ($variant['inventory_quantity'] ?? 0),
- 'product_title' => $product['title'],
- 'option1' => $variant['option1'] ?? '',
- 'image_urls' => $imageUrls,
+ 'product_title' => $product['title'],
+ 'product_description' => strip_tags($product['body_html'] ?? ''),
+ 'option1' => $variant['option1'] ?? '',
+ 'image_urls' => $imageUrls,
];
}
}
diff --git a/database/migrations/2026_09_12_031510_add_version_and_product_id_to_recipes.php b/database/migrations/2026_09_12_031510_add_version_and_product_id_to_recipes.php
new file mode 100644
index 0000000..eb21393
--- /dev/null
+++ b/database/migrations/2026_09_12_031510_add_version_and_product_id_to_recipes.php
@@ -0,0 +1,25 @@
+unsignedBigInteger('product_id')->nullable()->after('id');
+ $table->unsignedInteger('version')->default(1)->after('product_id');
+ $table->foreign('product_id')->references('id')->on('products')->nullOnDelete();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('recipes', function (Blueprint $table) {
+ $table->dropForeign(['product_id']);
+ $table->dropColumn(['product_id', 'version']);
+ });
+ }
+};