Products imported before shopify_product_id column was added have null
there, but their variants already carry shopify_variant_id — that's
enough to know the product exists in Shopify. Running shopify:import
once will backfill shopify_product_id for all existing products.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
belongsToMany sync() is wrong for products_sizes which is a full model
with its own id and a unique shopify_variant_id. The new approach:
- updateOrCreate per row (safe for repeated saves)
- strips non-digits from gtin, stores null when empty (fixes 500 on
unsignedBigInteger column with empty string in strict SQL mode)
- only deletes local-only sizes (whereNull shopify_variant_id) that
were removed — Shopify-imported sizes are never deleted here
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The recipe name input is dropped from the product form — the version
number alone identifies a recipe. The product title is stored as the
internal name to satisfy the NOT NULL constraint. The history table
no longer shows the name column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each time the recipe name or spices are changed on the product edit
page, a new version is created instead of overwriting the current one.
Unchanged saves are skipped. A 'Rezept-Versionen' button opens a
sub-page (/produkt/{id}/rezept-versionen) listing all versions with
their spices and timestamps; the active version is highlighted in green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the recipe dropdown with an inline Section containing a name
field and a spice-repeater with percentage inputs. On save, the recipe
is created (version 1) or updated in place; spice pivot rows are synced
via sync(). SumEquals100 rule enforces that percentages add up to 100.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>