diff --git a/app/Http/Controllers/ShoppingListController.php b/app/Http/Controllers/ShoppingListController.php index f3ccd97..51ecd1a 100644 --- a/app/Http/Controllers/ShoppingListController.php +++ b/app/Http/Controllers/ShoppingListController.php @@ -111,14 +111,16 @@ class ShoppingListController extends Controller $photoPath = $request->file('photo')?->store('price-photos', 'public'); - if (! $request->filled('price_decimal') && $photoPath === null) { + if (! $request->filled('price_decimal') && ! $request->filled('tier_price_decimal') && $photoPath === null) { return; } ItemPriceLog::query()->create([ 'shopping_item_id' => $shoppingItem->id, 'store_id' => $storeId, - 'price_decimal' => $request->input('price_decimal') ?? 0, + 'price_decimal' => $request->filled('price_decimal') ? $request->input('price_decimal') : null, + 'tier_min_qty' => $request->filled('tier_min_qty') ? (int) $request->input('tier_min_qty') : null, + 'tier_price_decimal' => $request->filled('tier_price_decimal') ? $request->input('tier_price_decimal') : null, 'currency' => 'EUR', 'logged_at' => Carbon::now(), 'photo_path' => $photoPath, @@ -159,14 +161,16 @@ class ShoppingListController extends Controller $photoPath = $request->file('photo')?->store('price-photos', 'public'); - if (! $request->filled('price_decimal') && $photoPath === null) { + if (! $request->filled('price_decimal') && ! $request->filled('tier_price_decimal') && $photoPath === null) { return; } ItemPriceLog::query()->create([ 'shopping_item_id' => $shoppingItem->id, 'store_id' => $storeId, - 'price_decimal' => $request->input('price_decimal') ?? 0, + 'price_decimal' => $request->filled('price_decimal') ? $request->input('price_decimal') : null, + 'tier_min_qty' => $request->filled('tier_min_qty') ? (int) $request->input('tier_min_qty') : null, + 'tier_price_decimal' => $request->filled('tier_price_decimal') ? $request->input('tier_price_decimal') : null, 'currency' => 'EUR', 'logged_at' => Carbon::now(), 'photo_path' => $photoPath, diff --git a/app/Http/Requests/ToggleShoppingItemRequest.php b/app/Http/Requests/ToggleShoppingItemRequest.php index cef1202..4061cab 100644 --- a/app/Http/Requests/ToggleShoppingItemRequest.php +++ b/app/Http/Requests/ToggleShoppingItemRequest.php @@ -35,6 +35,8 @@ class ToggleShoppingItemRequest extends FormRequest 'store_id' => ['nullable', 'integer', 'exists:stores,id'], 'new_store_name' => ['nullable', 'string', 'max:255'], 'price_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'], + 'tier_min_qty' => ['nullable', 'integer', 'min:2', 'max:999999'], + 'tier_price_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'], 'photo' => [ 'nullable', File::types(['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif']) @@ -54,6 +56,12 @@ class ToggleShoppingItemRequest extends FormRequest 'price_decimal.numeric' => 'Der Preis muss eine Zahl sein.', 'price_decimal.min' => 'Der Preis darf nicht negativ sein.', 'price_decimal.max' => 'Der Preis ist zu gross.', + 'tier_min_qty.integer' => 'Die Mindestmenge (Staffel) muss eine ganze Zahl sein.', + 'tier_min_qty.min' => 'Die Mindestmenge (Staffel) muss mindestens 2 sein.', + 'tier_min_qty.max' => 'Die Mindestmenge (Staffel) ist zu gross.', + 'tier_price_decimal.numeric' => 'Der Staffelpreis muss eine Zahl sein.', + 'tier_price_decimal.min' => 'Der Staffelpreis darf nicht negativ sein.', + 'tier_price_decimal.max' => 'Der Staffelpreis ist zu gross.', 'photo.max' => 'Das Foto darf maximal 15 MB gross sein.', 'photo.uploaded' => 'Das Foto konnte nicht hochgeladen werden. Haeufig: Datei zu gross fuer die PHP-Grenze auf dem Server (upload_max_filesize / post_max_size). Bitte ein kleineres Bild waehlen oder die Server-Limits erhoehen (siehe scripts/apache-einkauf-debian-setup.sh, Abschnitt PHP-Uploads).', ]; @@ -66,6 +74,8 @@ class ToggleShoppingItemRequest extends FormRequest 'store_id' => 'Geschaeft', 'new_store_name' => 'neues Geschaeft', 'price_decimal' => 'Preis', + 'tier_min_qty' => 'Mindestmenge (Staffel)', + 'tier_price_decimal' => 'Staffelpreis', 'photo' => 'Foto', ]; } diff --git a/app/Http/Requests/UpdateShoppingItemRequest.php b/app/Http/Requests/UpdateShoppingItemRequest.php index 10dbc64..1249a3f 100644 --- a/app/Http/Requests/UpdateShoppingItemRequest.php +++ b/app/Http/Requests/UpdateShoppingItemRequest.php @@ -31,6 +31,8 @@ class UpdateShoppingItemRequest extends FormRequest 'new_store_name' => ['nullable', 'string', 'max:255'], 'is_done' => ['sometimes', 'boolean'], 'price_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'], + 'tier_min_qty' => ['nullable', 'integer', 'min:2', 'max:999999'], + 'tier_price_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'], 'photo' => [ 'nullable', File::types(['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif']) @@ -50,6 +52,12 @@ class UpdateShoppingItemRequest extends FormRequest 'price_decimal.numeric' => 'Der Preis muss eine Zahl sein.', 'price_decimal.min' => 'Der Preis darf nicht negativ sein.', 'price_decimal.max' => 'Der Preis ist zu gross.', + 'tier_min_qty.integer' => 'Die Mindestmenge (Staffel) muss eine ganze Zahl sein.', + 'tier_min_qty.min' => 'Die Mindestmenge (Staffel) muss mindestens 2 sein.', + 'tier_min_qty.max' => 'Die Mindestmenge (Staffel) ist zu gross.', + 'tier_price_decimal.numeric' => 'Der Staffelpreis muss eine Zahl sein.', + 'tier_price_decimal.min' => 'Der Staffelpreis darf nicht negativ sein.', + 'tier_price_decimal.max' => 'Der Staffelpreis ist zu gross.', 'photo.max' => 'Das Foto darf maximal 15 MB gross sein.', 'photo.uploaded' => 'Das Foto konnte nicht hochgeladen werden. Haeufig: Datei zu gross fuer die PHP-Grenze auf dem Server (upload_max_filesize / post_max_size). Bitte ein kleineres Bild waehlen oder die Server-Limits erhoehen (siehe scripts/apache-einkauf-debian-setup.sh, Abschnitt PHP-Uploads).', ]; @@ -63,6 +71,8 @@ class UpdateShoppingItemRequest extends FormRequest 'store_id' => 'Geschaeft', 'new_store_name' => 'neues Geschaeft', 'price_decimal' => 'Preis', + 'tier_min_qty' => 'Mindestmenge (Staffel)', + 'tier_price_decimal' => 'Staffelpreis', 'photo' => 'Foto', ]; } diff --git a/app/Models/ItemPriceLog.php b/app/Models/ItemPriceLog.php index 49315db..ee26dc5 100644 --- a/app/Models/ItemPriceLog.php +++ b/app/Models/ItemPriceLog.php @@ -11,6 +11,8 @@ class ItemPriceLog extends Model 'shopping_item_id', 'store_id', 'price_decimal', + 'tier_min_qty', + 'tier_price_decimal', 'currency', 'logged_at', 'photo_path', @@ -21,6 +23,7 @@ class ItemPriceLog extends Model { return [ 'price_decimal' => 'decimal:2', + 'tier_price_decimal' => 'decimal:2', 'logged_at' => 'datetime', ]; } diff --git a/database/migrations/2026_03_30_000400_add_price_text_to_item_price_logs.php b/database/migrations/2026_03_30_000400_add_price_text_to_item_price_logs.php new file mode 100644 index 0000000..8be57ec --- /dev/null +++ b/database/migrations/2026_03_30_000400_add_price_text_to_item_price_logs.php @@ -0,0 +1,30 @@ +unsignedInteger('tier_min_qty')->nullable()->after('price_decimal'); + $table->decimal('tier_price_decimal', 10, 2)->nullable()->after('tier_min_qty'); + }); + + // MySQL: Spaltenaenderung ohne doctrine/dbal. + DB::statement('ALTER TABLE item_price_logs MODIFY price_decimal DECIMAL(10,2) NULL'); + } + + public function down(): void + { + Schema::table('item_price_logs', function (Blueprint $table) { + $table->dropColumn(['tier_min_qty', 'tier_price_decimal']); + }); + + DB::statement('ALTER TABLE item_price_logs MODIFY price_decimal DECIMAL(10,2) NOT NULL'); + } +}; + diff --git a/resources/views/shopping-list/index.blade.php b/resources/views/shopping-list/index.blade.php index ef4ebe4..95c1000 100644 --- a/resources/views/shopping-list/index.blade.php +++ b/resources/views/shopping-list/index.blade.php @@ -136,7 +136,16 @@ Erledigt: {{ optional($item->done_at)->format('d.m.Y H:i') ?: '-' }}