= 7). return (bool) $this->user()?->isAdmin(); } /** * @return array> */ public function rules(): array { $recipeRequired = $this->recipeRequired(); return [ 'product_id' => ['required', 'integer', 'exists:products,id'], 'location_id' => ['required', 'integer', 'exists:locations,id'], 'produced_at' => ['required', 'date'], 'quantity' => ['required', 'integer', 'min:1'], 'notes' => ['nullable', 'string', 'max:2000'], 'ingredient_lines' => [$recipeRequired ? 'required' : 'nullable', 'array', $recipeRequired ? 'min:1' : 'min:0'], 'ingredient_lines.*.ingredient_id' => ['required', 'integer', 'exists:ingredients,id'], // AP-27: Das Formular übergibt Chargen (batch_key); Einzel-Einträge (stock_entry_id) bleiben unterstützt. 'ingredient_lines.*.batch_key' => ['required_without:ingredient_lines.*.stock_entry_id', 'nullable', 'string', 'max:120'], 'ingredient_lines.*.stock_entry_id' => ['required_without:ingredient_lines.*.batch_key', 'nullable', 'integer', 'exists:stock_entries,id'], // Audit H-2: Vorzeichen-/Zahlprüfung — negative Zeilen konnten sich // in der Summenprüfung gegenseitig aufheben und Bestand aufblähen. 'ingredient_lines.*.quantity_used' => ['required', 'string', function (string $attribute, mixed $value, \Closure $fail) { $n = reFormatNumber((string) $value); if (! is_numeric($n) || (float) $n <= 0) { $fail(__('Die Menge je Chargen-Zeile muss größer als 0 sein.')); } }], // AP-28: Produktionsergebnis — bucht den Produktbestand; Planung oben ist unverbindlich. 'outputs' => ['required', 'array', 'min:1'], 'outputs.*.product_id' => ['required', 'integer', 'exists:products,id'], 'outputs.*.quantity' => ['required', 'integer', 'min:1'], 'outputs.*.batch_number' => ['nullable', 'string', 'max:100'], ]; } private function recipeRequired(): bool { $product = Product::query()->find($this->input('product_id')); return $product === null ? true : ! (bool) $product->no_recipe_required; } /** * @return array */ public function validatedPayload(): array { $data = $this->validated(); return [ 'product_id' => (int) $data['product_id'], 'location_id' => (int) $data['location_id'], 'produced_at' => $data['produced_at'], 'quantity' => (int) $data['quantity'], 'notes' => $data['notes'] ?? null, 'ingredient_lines' => array_map(function (array $line): array { $out = [ 'ingredient_id' => (int) $line['ingredient_id'], 'quantity_used' => $line['quantity_used'], ]; if (! empty($line['batch_key'])) { $out['batch_key'] = (string) $line['batch_key']; } else { $out['stock_entry_id'] = (int) $line['stock_entry_id']; } return $out; }, $data['ingredient_lines'] ?? []), 'outputs' => array_map(function (array $output): array { return [ 'product_id' => (int) $output['product_id'], 'quantity' => (int) $output['quantity'], 'batch_number' => $output['batch_number'] ?? null, ]; }, $data['outputs'] ?? []), ]; } }