'required|integer|exists:models,id', 'name' => 'required|string|max:100', 'condition_expression' => 'required|string|max:1000', 'action' => 'required|string|in:' . implode(',', BomConditionRule::ACTIONS), 'target_items' => 'required|array|min:1', 'target_items.*.product_id' => 'nullable|integer|exists:products,id', 'target_items.*.material_id' => 'nullable|integer|exists:materials,id', 'target_items.*.quantity' => 'nullable|numeric|min:0', 'target_items.*.waste_rate' => 'nullable|numeric|min:0|max:100', 'target_items.*.unit' => 'nullable|string|max:20', 'target_items.*.memo' => 'nullable|string|max:200', 'priority' => 'nullable|integer|min:1', 'description' => 'nullable|string|max:500', 'is_active' => 'boolean', ]; } /** * Get custom messages for validator errors. */ public function messages(): array { return [ 'condition_expression.required' => __('error.condition_expression_required'), 'target_items.required' => __('error.target_items_required'), 'target_items.min' => __('error.target_items_required'), 'target_items.*.quantity.min' => __('error.quantity_must_be_positive'), 'target_items.*.waste_rate.max' => __('error.waste_rate_too_high'), ]; } /** * Configure the validator instance. */ public function withValidator($validator): void { $validator->after(function ($validator) { $this->validateRuleNameUnique($validator); $this->validateConditionExpression($validator); $this->validateTargetItems($validator); }); } /** * 규칙명 중복 검증 */ private function validateRuleNameUnique($validator): void { if (!$this->input('model_id') || !$this->input('name')) { return; } $query = BomConditionRule::where('model_id', $this->input('model_id')) ->where('name', $this->input('name')); // 수정 시 자기 자신 제외 if ($this->route('id')) { $query->where('id', '!=', $this->route('id')); } if ($query->exists()) { $validator->errors()->add('name', __('error.rule_name_duplicate')); } } /** * 조건식 검증 */ private function validateConditionExpression($validator): void { if (!$this->input('condition_expression')) { return; } $tempRule = new BomConditionRule([ 'condition_expression' => $this->input('condition_expression'), 'model_id' => $this->input('model_id'), ]); $conditionErrors = $tempRule->validateConditionExpression(); if (!empty($conditionErrors)) { foreach ($conditionErrors as $error) { $validator->errors()->add('condition_expression', $error); } } } /** * 대상 아이템 검증 */ private function validateTargetItems($validator): void { $targetItems = $this->input('target_items', []); $action = $this->input('action'); foreach ($targetItems as $index => $item) { // 제품 또는 자재 참조 필수 if (empty($item['product_id']) && empty($item['material_id'])) { $validator->errors()->add( "target_items.{$index}", __('error.target_item_missing_reference') ); } // 제품과 자재 동시 참조 불가 if (!empty($item['product_id']) && !empty($item['material_id'])) { $validator->errors()->add( "target_items.{$index}", __('error.target_item_multiple_reference') ); } // REPLACE 액션의 경우 replace_from 필수 if ($action === BomConditionRule::ACTION_REPLACE && empty($item['replace_from'])) { $validator->errors()->add( "target_items.{$index}.replace_from", __('error.replace_from_required') ); } // replace_from 검증 if (!empty($item['replace_from'])) { if (empty($item['replace_from']['product_id']) && empty($item['replace_from']['material_id'])) { $validator->errors()->add( "target_items.{$index}.replace_from", __('error.replace_from_missing_reference') ); } } } } /** * Prepare the data for validation. */ protected function prepareForValidation(): void { // JSON 문자열인 경우 배열로 변환 if ($this->has('target_items') && is_string($this->input('target_items'))) { $this->merge([ 'target_items' => json_decode($this->input('target_items'), true) ?? [] ]); } } }