['required', 'array', 'min:1'], 'input_parameters.*' => ['required'], 'bom_template_id' => ['sometimes', 'integer', 'min:1'], 'include_calculated_values' => ['boolean'], 'include_bom_items' => ['boolean'], 'include_condition_rules' => ['boolean'], 'validate_before_resolve' => ['boolean'], 'calculation_precision' => ['integer', 'min:0', 'max:10'], ]; } /** * Get custom messages for validator errors. */ public function messages(): array { return [ 'input_parameters.required' => '입력 매개변수는 필수입니다.', 'input_parameters.array' => '입력 매개변수는 배열 형태여야 합니다.', 'input_parameters.min' => '최소 하나 이상의 입력 매개변수가 필요합니다.', 'input_parameters.*.required' => '모든 입력 매개변수 값은 필수입니다.', 'bom_template_id.integer' => 'BOM 템플릿 ID는 정수여야 합니다.', 'bom_template_id.min' => 'BOM 템플릿 ID는 1 이상이어야 합니다.', 'calculation_precision.integer' => '계산 정밀도는 정수여야 합니다.', 'calculation_precision.min' => '계산 정밀도는 0 이상이어야 합니다.', 'calculation_precision.max' => '계산 정밀도는 10 이하여야 합니다.', ]; } /** * Get custom attribute names for validator errors. */ public function attributes(): array { return [ 'input_parameters' => '입력 매개변수', 'bom_template_id' => 'BOM 템플릿 ID', 'include_calculated_values' => '계산값 포함 여부', 'include_bom_items' => 'BOM 아이템 포함 여부', 'include_condition_rules' => '조건 규칙 포함 여부', 'validate_before_resolve' => '해결 전 유효성 검사', 'calculation_precision' => '계산 정밀도', ]; } /** * Prepare the data for validation. */ protected function prepareForValidation(): void { $this->merge([ 'include_calculated_values' => $this->boolean('include_calculated_values', true), 'include_bom_items' => $this->boolean('include_bom_items', true), 'include_condition_rules' => $this->boolean('include_condition_rules', true), 'validate_before_resolve' => $this->boolean('validate_before_resolve', true), 'calculation_precision' => $this->integer('calculation_precision', 2), ]); // Ensure input_parameters is an array if ($this->has('input_parameters') && !is_array($this->input('input_parameters'))) { $params = json_decode($this->input('input_parameters'), true); if (json_last_error() === JSON_ERROR_NONE) { $this->merge(['input_parameters' => $params]); } } } /** * Configure the validator instance. */ public function withValidator($validator) { $validator->after(function ($validator) { $this->validateInputParameters($validator); $this->validateBomTemplate($validator); $this->validateParameterValues($validator); }); } /** * Validate input parameters against model parameter definitions. */ private function validateInputParameters($validator): void { $modelId = $this->route('modelId'); $inputParameters = $this->input('input_parameters', []); if (empty($inputParameters)) { return; } // Get model's INPUT parameters $modelParameters = ModelParameter::where('model_id', $modelId) ->where('parameter_type', 'INPUT') ->where('tenant_id', auth()->user()?->currentTenant?->id) ->whereNull('deleted_at') ->get() ->keyBy('parameter_name'); // Check for required parameters $requiredParams = $modelParameters->where('is_required', true)->pluck('parameter_name')->toArray(); $providedParams = array_keys($inputParameters); $missingRequired = array_diff($requiredParams, $providedParams); if (!empty($missingRequired)) { $validator->errors()->add('input_parameters', '다음 필수 매개변수가 누락되었습니다: ' . implode(', ', $missingRequired) ); } // Check for unknown parameters $knownParams = $modelParameters->pluck('parameter_name')->toArray(); $unknownParams = array_diff($providedParams, $knownParams); if (!empty($unknownParams)) { $validator->errors()->add('input_parameters', '알 수 없는 매개변수가 포함되어 있습니다: ' . implode(', ', $unknownParams) ); } } /** * Validate BOM template exists and belongs to the model. */ private function validateBomTemplate($validator): void { $bomTemplateId = $this->input('bom_template_id'); $modelId = $this->route('modelId'); if (!$bomTemplateId) { return; } $template = BomTemplate::where('id', $bomTemplateId) ->where('tenant_id', auth()->user()?->currentTenant?->id) ->whereNull('deleted_at') ->first(); if (!$template) { $validator->errors()->add('bom_template_id', '지정된 BOM 템플릿이 존재하지 않습니다.'); return; } // Check if template belongs to the model (through model_version) if ($template->modelVersion && $template->modelVersion->model_id != $modelId) { $validator->errors()->add('bom_template_id', 'BOM 템플릿이 해당 모델에 속하지 않습니다.'); } } /** * Validate parameter values against their constraints. */ private function validateParameterValues($validator): void { $modelId = $this->route('modelId'); $inputParameters = $this->input('input_parameters', []); if (empty($inputParameters)) { return; } // Get model parameter definitions $modelParameters = ModelParameter::where('model_id', $modelId) ->where('parameter_type', 'INPUT') ->where('tenant_id', auth()->user()?->currentTenant?->id) ->whereNull('deleted_at') ->get() ->keyBy('parameter_name'); foreach ($inputParameters as $paramName => $value) { $parameter = $modelParameters->get($paramName); if (!$parameter) { continue; // Unknown parameter already handled above } // Validate value against parameter constraints $this->validateParameterValue($validator, $parameter, $paramName, $value); } } /** * Validate individual parameter value. */ private function validateParameterValue($validator, $parameter, string $paramName, $value): void { // Check for null/empty required values if ($parameter->is_required && ($value === null || $value === '')) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 필수 매개변수입니다."); return; } // Validate data type switch ($parameter->data_type ?? 'STRING') { case 'INTEGER': if (!is_numeric($value) || (int)$value != $value) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 정수여야 합니다."); return; } $value = (int)$value; break; case 'DECIMAL': if (!is_numeric($value)) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 숫자여야 합니다."); return; } $value = (float)$value; break; case 'BOOLEAN': if (!is_bool($value) && !in_array($value, [0, 1, '0', '1', 'true', 'false'])) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 불린 값이어야 합니다."); return; } break; case 'STRING': if (!is_string($value) && !is_numeric($value)) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 문자열이어야 합니다."); return; } $value = (string)$value; break; } // Validate min/max values for numeric types if (in_array($parameter->data_type, ['INTEGER', 'DECIMAL']) && is_numeric($value)) { if ($parameter->min_value !== null && $value < $parameter->min_value) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) {$parameter->min_value} 이상이어야 합니다." ); } if ($parameter->max_value !== null && $value > $parameter->max_value) { $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) {$parameter->max_value} 이하여야 합니다." ); } } // Validate options for select type parameters if (!empty($parameter->options) && !in_array($value, $parameter->options)) { $validOptions = implode(', ', $parameter->options); $validator->errors()->add("input_parameters.{$paramName}", "{$paramName}의 값은 다음 중 하나여야 합니다: {$validOptions}" ); } } }