Filament checks view before edit for row URL. Added a shared
ViewRecord base that redirects to edit if canEdit() is true,
so admins and managers get the editable form directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nav entry was fully hidden; now visible only to admins for management.
Delete action added to table rows (bulk delete was already there).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matching users by email alone allows an attacker with a self-registered
Nextcloud account to hijack any Filament user. Now we look up by
nextcloud_id first; email is only used when creating a brand-new account,
and nextcloud_id is immediately written to the row so future logins
use the stable Nextcloud user ID.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- canView() + canViewAny() in EnforcesRoles: all SMS roles can view
- View pages for all 7 resources so viewers can open records read-only
- Custom ActivityResource restricts activity log to admin + manager only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Import was present but use statement inside class body was missing,
so role checks had no effect on these two resources.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Overriding mount() conflicts with parent void return type; intercepting
the URL in routes/web.php (registered before Filament) is the clean fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
send()+exit breaks in Livewire's mount() context; returning the
redirect response is the correct Livewire v3 approach.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accidentally removed when cleaning up login render hook; still needed
for the sidebar footer build-info hook.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes email/password fields entirely; custom login page shows only
the blue "Mit Blue Spices Zanzibar Cloud anmelden" button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every delivery and batch now writes a signed movement entry instead of
directly incrementing/decrementing spice.quantity. The balance is always
the sum of all movements. Existing data is backfilled in the migration.
Rohstoff edit page shows the full movement history (Lagerbewegungen tab).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows a warning in the confirmation dialog when stock would go below zero.
Deletion proceeds and sets stock to 0 rather than blocking the action.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Products now have a configurable lot_prefix (e.g. "112"). In the batch form
the prefix is shown read-only next to the suffix input (JJMMTT), and the full
lot_number (e.g. "112-260912") is composed automatically and stored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the recipe is changed in a batch form, keep origin data for spices
that exist in both the old and new recipe. Only add empty rows for
spices that are new to the selected recipe version.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each spice now returns an `origins` array (was single `origin` object) with
supplier and farmer resolved from the new entries relation, not flat columns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each BatchOrigin (spice) now has multiple BatchOriginEntry rows, replacing the flat
single-supplier/farmer fields. Lieferant and Landwirt are Select fields backed by
dedicated tables with inline create support. Migration preserves existing data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each batch origin row now shows a searchable Rohstoff Select so the same
ingredient can appear in multiple rows with different suppliers or farmers.
Pre-fill from recipe still works (one row per ingredient); user clicks
'Eintrag hinzufügen' to add a second entry for the same ingredient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Selecting a Produkt auto-sets the current Rezeptur and pre-fills one
Herkunft row per Rohstoff. Changing the Rezeptur resets the rows.
Rohstoff is shown read-only (Hidden + Placeholder); user fills in
supplier, origin country, farmer and notes only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When creating a new batch, the Herkunft-Repeater is automatically populated
with one row per Rohstoff from the product's current recipe. The Rohstoff
field is shown as a read-only label (Hidden spice_id + Placeholder name)
so the user only fills in supplier, origin country, farmer and notes.
Changing the recipe field updates the origin rows accordingly.
On edit, existing origin data is preserved unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Filament table boot lifecycle (bootedInteractsWithTable) fires before
mount(), causing issues with typed $record properties on custom resource
pages. Replaced with a plain Page + Blade view rendering recipes directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bootedInteractsWithTable() calls table() before mount() sets $this->record.
Accessing $this->record eagerly causes a TypeError on the uninitialized typed
property, which Filament/Livewire converts to a 404.
Deferred all $this->record accesses into closures so they only execute at
render time (after mount() has run).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix root cause: recipes_spices.amount_in_percent was decimal(4,2) which
overflows on 100% — widened to decimal(6,2) via migration
- Wrap afterSave() in try-catch so DB/logic errors show as a persistent
danger notification instead of a blank 500 page
- Same for CreateProduct: errors show as notification; also reads
recipe_spices from raw Livewire $this->data (not $data param which
excludes dehydrated(false) fields) so recipes actually save on create
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
In Filament v3, $arguments is only injected when an action is executed
(clicked), not when it's rendered. Using $arguments in label(), icon(),
color() and visible() closures causes ErrorException: Undefined variable
$arguments on every Livewire update, including on the create page.
Simplified extraItemAction to static label/icon/color; all dynamic
logic (publish vs sync, guard for unsaved items) stays in action().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>