Add lot_prefix to products; compose LOT number from prefix + date suffix
Deploy / deploy (push) Successful in 14s

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>
This commit is contained in:
Malek TellissiandClaude Sonnet 4.6 committed 2026-09-12 20:35:37 +02:00
1 parent e9734c2590
commit 5c034ec895
6 files changed
+87 -12

No files matched your search

+2 -1
View File
@@ -110,7 +110,7 @@ GET https://api.sms.spices.blue/api/v1/{lot_number}
```json ```json
{ {
"data": { "data": {
"lot_number": "LOT-2024-001", "lot_number": "112-260912",
"produced_at": "2024-10-01", "produced_at": "2024-10-01",
"expires_at": "2026-10-01", "expires_at": "2026-10-01",
"units_produced": 50, "units_produced": 50,
@@ -220,6 +220,7 @@ Table packings {
Table products { Table products {
id int [pk] id int [pk]
title text title text
lot_prefix varchar -- Präfix für Chargennummer, z.B. "112"
images json images json
description text description text
recipes_id int [ref: > recipes.id] recipes_id int [ref: > recipes.id]
+34 -7
View File
@@ -31,12 +31,6 @@ class BatchResource extends Resource
{ {
return $form->schema([ return $form->schema([
Forms\Components\Section::make('Charge')->schema([ Forms\Components\Section::make('Charge')->schema([
Forms\Components\TextInput::make('lot_number')
->label('LOT-Nummer')
->required()
->unique(ignoreRecord: true)
->maxLength(255),
Forms\Components\Select::make('product_id') Forms\Components\Select::make('product_id')
->label('Produkt') ->label('Produkt')
->relationship('product', 'title') ->relationship('product', 'title')
@@ -44,7 +38,7 @@ class BatchResource extends Resource
->preload() ->preload()
->required() ->required()
->live() ->live()
->afterStateUpdated(function ($state, $set) { ->afterStateUpdated(function ($state, $set, $get) {
$set('size_id', null); $set('size_id', null);
$set('recipe_id', null); $set('recipe_id', null);
$set('origins', []); $set('origins', []);
@@ -62,8 +56,41 @@ class BatchResource extends Resource
])->toArray()); ])->toArray());
} }
} }
$suffix = $get('lot_suffix');
if ($suffix && $product?->lot_prefix) {
$set('lot_number', $product->lot_prefix . '-' . $suffix);
}
}), }),
Forms\Components\TextInput::make('lot_suffix')
->label('LOT-Nummer')
->prefix(fn (Get $get): ?string => Product::find($get('product_id'))?->lot_prefix
? Product::find($get('product_id'))->lot_prefix . ' -'
: null
)
->placeholder('JJMMTT')
->maxLength(50)
->required()
->dehydrated(false)
->live()
->afterStateHydrated(function ($component, $record) {
if (! $record?->lot_number) return;
$pos = strrpos($record->lot_number, '-');
$component->state($pos !== false
? substr($record->lot_number, $pos + 1)
: $record->lot_number
);
})
->afterStateUpdated(function ($state, $set, $get) {
$product = Product::find($get('product_id'));
$set('lot_number', $product?->lot_prefix
? $product->lot_prefix . '-' . $state
: $state
);
}),
Forms\Components\Hidden::make('lot_number'),
Forms\Components\Select::make('size_id') Forms\Components\Select::make('size_id')
->label('Variante (Verpackung)') ->label('Variante (Verpackung)')
->options(fn (Get $get) => ProductSize::with('packing') ->options(fn (Get $get) => ProductSize::with('packing')
@@ -41,6 +41,11 @@ class ProductResource extends Resource {
Forms\Components\TextInput::make('title') Forms\Components\TextInput::make('title')
->label('Name') ->label('Name')
->required(), ->required(),
Forms\Components\TextInput::make('lot_prefix')
->label('Chargen-Präfix')
->placeholder('z.B. 112')
->unique(ignoreRecord: true)
->maxLength(20),
Placeholder::make('shopify_product_id') Placeholder::make('shopify_product_id')
->label('Shopify ID') ->label('Shopify ID')
->content(fn ($record) => $record?->shopify_product_id ->content(fn ($record) => $record?->shopify_product_id
@@ -24,11 +24,31 @@ class BatchesRelationManager extends RelationManager
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form->schema([ return $form->schema([
Forms\Components\TextInput::make('lot_number') Forms\Components\TextInput::make('lot_suffix')
->label('LOT-Nummer') ->label('LOT-Nummer')
->prefix(fn ($livewire): ?string => $livewire->getOwnerRecord()->lot_prefix
? $livewire->getOwnerRecord()->lot_prefix . ' -'
: null
)
->placeholder('JJMMTT')
->maxLength(50)
->required() ->required()
->unique(ignoreRecord: true) ->dehydrated(false)
->maxLength(255), ->live()
->afterStateHydrated(function ($component, $record) {
if (! $record?->lot_number) return;
$pos = strrpos($record->lot_number, '-');
$component->state($pos !== false
? substr($record->lot_number, $pos + 1)
: $record->lot_number
);
})
->afterStateUpdated(function ($state, $set, $livewire) {
$prefix = $livewire->getOwnerRecord()->lot_prefix;
$set('lot_number', $prefix ? "{$prefix}-{$state}" : $state);
}),
Forms\Components\Hidden::make('lot_number'),
Forms\Components\Select::make('size_id') Forms\Components\Select::make('size_id')
->label('Variante (Verpackung)') ->label('Variante (Verpackung)')
+1 -1
View File
@@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model;
class Product extends Model class Product extends Model
{ {
protected $fillable = ['title', 'description', 'images', 'package_content', 'recipes_id', 'shopify_product_id']; protected $fillable = ['title', 'lot_prefix', 'description', 'images', 'package_content', 'recipes_id', 'shopify_product_id'];
protected $casts = [ protected $casts = [
'images' => 'array', 'images' => 'array',
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->string('lot_prefix')->nullable()->unique()->after('title');
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('lot_prefix');
});
}
};