Revert "feat: DB 연결 오버라이딩 및 대시보드 통계 위젯 추가"
This reverts commit bf8036a64b.
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateBomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'ref_type' => ['required', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'ref_id' => ['required', 'integer', 'min:1'],
|
||||
'condition_expression' => ['required', 'string', 'max:1000'],
|
||||
'quantity_expression' => ['nullable', 'string', 'max:500'],
|
||||
'waste_rate_expression' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'priority' => ['integer', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.rule_name'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'ref_id' => __('validation.attributes.ref_id'),
|
||||
'condition_expression' => __('validation.attributes.condition_expression'),
|
||||
'quantity_expression' => __('validation.attributes.quantity_expression'),
|
||||
'waste_rate_expression' => __('validation.attributes.waste_rate_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'priority' => __('validation.attributes.priority'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'priority' => $this->integer('priority', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexBomConditionRuleRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'ref_type' => ['sometimes', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
parent::prepareForValidation();
|
||||
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateBomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'ref_type' => ['sometimes', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'ref_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'condition_expression' => ['sometimes', 'string', 'max:1000'],
|
||||
'quantity_expression' => ['nullable', 'string', 'max:500'],
|
||||
'waste_rate_expression' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'priority' => ['sometimes', 'integer', 'min:0'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.rule_name'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'ref_id' => __('validation.attributes.ref_id'),
|
||||
'condition_expression' => __('validation.attributes.condition_expression'),
|
||||
'quantity_expression' => __('validation.attributes.quantity_expression'),
|
||||
'waste_rate_expression' => __('validation.attributes.waste_rate_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'priority' => __('validation.attributes.priority'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
|
||||
if ($this->has('priority')) {
|
||||
$this->merge(['priority' => $this->integer('priority')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomResolver;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateProductFromModelRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => ['required', 'integer', 'min:1'],
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
|
||||
// Product data
|
||||
'product_code' => ['required', 'string', 'max:50', 'regex:/^[A-Z0-9_-]+$/'],
|
||||
'product_name' => ['required', 'string', 'max:100'],
|
||||
'category_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
|
||||
// Product attributes
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'min_order_qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'lead_time_days' => ['nullable', 'integer', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
|
||||
// Additional options
|
||||
'create_bom_items' => ['boolean'],
|
||||
'validate_bom' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'product_code.regex' => __('validation.product.code_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => __('validation.attributes.model_id'),
|
||||
'input_parameters' => __('validation.attributes.input_parameters'),
|
||||
'bom_template_id' => __('validation.attributes.bom_template_id'),
|
||||
'product_code' => __('validation.attributes.product_code'),
|
||||
'product_name' => __('validation.attributes.product_name'),
|
||||
'category_id' => __('validation.attributes.category_id'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'min_order_qty' => __('validation.attributes.min_order_qty'),
|
||||
'lead_time_days' => __('validation.attributes.lead_time_days'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'create_bom_items' => __('validation.attributes.create_bom_items'),
|
||||
'validate_bom' => __('validation.attributes.validate_bom'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'create_bom_items' => $this->boolean('create_bom_items', true),
|
||||
'validate_bom' => $this->boolean('validate_bom', true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomResolver;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ResolvePreviewRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'include_calculated_values' => ['boolean'],
|
||||
'include_bom_items' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => __('validation.attributes.input_parameters'),
|
||||
'bom_template_id' => __('validation.attributes.bom_template_id'),
|
||||
'include_calculated_values' => __('validation.attributes.include_calculated_values'),
|
||||
'include_bom_items' => __('validation.attributes.include_bom_items'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Product;
|
||||
use App\Models\Material;
|
||||
|
||||
class BomConditionRuleFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$ruleId = $this->route('ruleId');
|
||||
|
||||
$rules = [
|
||||
'rule_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:100',
|
||||
Rule::unique('bom_condition_rules')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'condition_expression' => ['required', 'string', 'max:1000'],
|
||||
'action_type' => ['required', 'string', 'in:INCLUDE,EXCLUDE,MODIFY_QUANTITY'],
|
||||
'target_type' => ['required', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'target_id' => ['required', 'integer', 'min:1'],
|
||||
'quantity_multiplier' => ['nullable', 'numeric', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
'priority' => ['integer', 'min:0'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($ruleId) {
|
||||
$rules['rule_name'][3] = $rules['rule_name'][3]->ignore($ruleId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'rule_name.required' => '규칙 이름은 필수입니다.',
|
||||
'rule_name.unique' => '해당 모델에 이미 동일한 규칙 이름이 존재합니다.',
|
||||
'condition_expression.required' => '조건 표현식은 필수입니다.',
|
||||
'condition_expression.max' => '조건 표현식은 1000자를 초과할 수 없습니다.',
|
||||
'action_type.required' => '액션 타입은 필수입니다.',
|
||||
'action_type.in' => '액션 타입은 INCLUDE, EXCLUDE, MODIFY_QUANTITY 중 하나여야 합니다.',
|
||||
'target_type.required' => '대상 타입은 필수입니다.',
|
||||
'target_type.in' => '대상 타입은 MATERIAL 또는 PRODUCT여야 합니다.',
|
||||
'target_id.required' => '대상 ID는 필수입니다.',
|
||||
'target_id.min' => '대상 ID는 1 이상이어야 합니다.',
|
||||
'quantity_multiplier.numeric' => '수량 배수는 숫자여야 합니다.',
|
||||
'quantity_multiplier.min' => '수량 배수는 0 이상이어야 합니다.',
|
||||
'priority.min' => '우선순위는 0 이상이어야 합니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'rule_name' => '규칙 이름',
|
||||
'condition_expression' => '조건 표현식',
|
||||
'action_type' => '액션 타입',
|
||||
'target_type' => '대상 타입',
|
||||
'target_id' => '대상 ID',
|
||||
'quantity_multiplier' => '수량 배수',
|
||||
'is_active' => '활성 상태',
|
||||
'priority' => '우선순위',
|
||||
'description' => '설명',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'priority' => $this->integer('priority', 0),
|
||||
]);
|
||||
|
||||
// Set default quantity_multiplier for actions that require it
|
||||
if ($this->input('action_type') === 'MODIFY_QUANTITY' && !$this->has('quantity_multiplier')) {
|
||||
$this->merge(['quantity_multiplier' => 1.0]);
|
||||
}
|
||||
|
||||
// Clean up condition expression
|
||||
if ($this->has('condition_expression')) {
|
||||
$expression = preg_replace('/\s+/', ' ', trim($this->input('condition_expression')));
|
||||
$this->merge(['condition_expression' => $expression]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateConditionExpression($validator);
|
||||
$this->validateTargetExists($validator);
|
||||
$this->validateActionRequirements($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate condition expression syntax and variables.
|
||||
*/
|
||||
private function validateConditionExpression($validator): void
|
||||
{
|
||||
$expression = $this->input('condition_expression');
|
||||
|
||||
if (!$expression) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for potentially dangerous characters or functions
|
||||
$dangerousPatterns = [
|
||||
'/\b(eval|exec|system|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite)\b/i',
|
||||
'/[;{}]/', // Semicolons and braces
|
||||
'/\$[a-zA-Z_]/', // PHP variables
|
||||
'/\bfunction\s*\(/i', // Function definitions
|
||||
];
|
||||
|
||||
foreach ($dangerousPatterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
$validator->errors()->add('condition_expression', '조건 표현식에 허용되지 않는 문자나 함수가 포함되어 있습니다.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate condition expression format
|
||||
if (!$this->isValidConditionExpression($expression)) {
|
||||
$validator->errors()->add('condition_expression', '조건 표현식의 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate variables in expression exist as parameters
|
||||
$this->validateConditionVariables($validator, $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if condition expression has valid syntax.
|
||||
*/
|
||||
private function isValidConditionExpression(string $expression): bool
|
||||
{
|
||||
// Allow comparison operators, logical operators, variables, numbers, strings
|
||||
$patterns = [
|
||||
'/^.*(==|!=|>=|<=|>|<|\sIN\s|\sNOT\sIN\s|\sAND\s|\sOR\s).*$/i',
|
||||
'/^(true|false|[0-9]+)$/i', // Simple boolean or number
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that variables in condition exist as model parameters.
|
||||
*/
|
||||
private function validateConditionVariables($validator, string $expression): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
// Extract variable names from expression (exclude operators and values)
|
||||
preg_match_all('/\b[a-zA-Z][a-zA-Z0-9_]*\b/', $expression, $matches);
|
||||
$variables = $matches[0];
|
||||
|
||||
// Remove logical operators and reserved words
|
||||
$reservedWords = ['AND', 'OR', 'IN', 'NOT', 'TRUE', 'FALSE', 'true', 'false'];
|
||||
$variables = array_diff($variables, $reservedWords);
|
||||
|
||||
if (empty($variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check for undefined variables
|
||||
$undefinedVariables = array_diff($variables, $existingParameters);
|
||||
|
||||
if (!empty($undefinedVariables)) {
|
||||
$validator->errors()->add('condition_expression',
|
||||
'조건식에 정의되지 않은 변수가 사용되었습니다: ' . implode(', ', $undefinedVariables)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the target (MATERIAL or PRODUCT) exists.
|
||||
*/
|
||||
private function validateTargetExists($validator): void
|
||||
{
|
||||
$targetType = $this->input('target_type');
|
||||
$targetId = $this->input('target_id');
|
||||
|
||||
if (!$targetType || !$targetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tenantId = auth()->user()?->currentTenant?->id;
|
||||
|
||||
switch ($targetType) {
|
||||
case 'MATERIAL':
|
||||
$exists = Material::where('id', $targetId)
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
$validator->errors()->add('target_id', '지정된 자재가 존재하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PRODUCT':
|
||||
$exists = Product::where('id', $targetId)
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
$validator->errors()->add('target_id', '지정된 제품이 존재하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate action-specific requirements.
|
||||
*/
|
||||
private function validateActionRequirements($validator): void
|
||||
{
|
||||
$actionType = $this->input('action_type');
|
||||
$quantityMultiplier = $this->input('quantity_multiplier');
|
||||
|
||||
switch ($actionType) {
|
||||
case 'MODIFY_QUANTITY':
|
||||
// MODIFY_QUANTITY action requires quantity_multiplier
|
||||
if ($quantityMultiplier === null || $quantityMultiplier === '') {
|
||||
$validator->errors()->add('quantity_multiplier', 'MODIFY_QUANTITY 액션에는 수량 배수가 필요합니다.');
|
||||
} elseif ($quantityMultiplier <= 0) {
|
||||
$validator->errors()->add('quantity_multiplier', 'MODIFY_QUANTITY 액션의 수량 배수는 0보다 커야 합니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'INCLUDE':
|
||||
// INCLUDE action can optionally have quantity_multiplier (default to 1)
|
||||
if ($quantityMultiplier !== null && $quantityMultiplier <= 0) {
|
||||
$validator->errors()->add('quantity_multiplier', 'INCLUDE 액션의 수량 배수는 0보다 커야 합니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EXCLUDE':
|
||||
// EXCLUDE action doesn't need quantity_multiplier
|
||||
if ($quantityMultiplier !== null) {
|
||||
$validator->errors()->add('quantity_multiplier', 'EXCLUDE 액션에는 수량 배수가 필요하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Design\BomTemplate;
|
||||
|
||||
class BomResolverFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => ['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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
|
||||
class ModelFormulaFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$formulaId = $this->route('formulaId');
|
||||
|
||||
$rules = [
|
||||
'formula_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:100',
|
||||
'regex:/^[a-zA-Z][a-zA-Z0-9_\s]*$/',
|
||||
Rule::unique('model_formulas')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'formula_expression' => ['required', 'string', 'max:1000'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'calculation_order' => ['integer', 'min:0'],
|
||||
'dependencies' => ['nullable', 'array'],
|
||||
'dependencies.*' => ['string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($formulaId) {
|
||||
$rules['formula_name'][4] = $rules['formula_name'][4]->ignore($formulaId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'formula_name.required' => '공식 이름은 필수입니다.',
|
||||
'formula_name.regex' => '공식 이름은 영문자로 시작하고 영문자, 숫자, 언더스코어, 공백만 사용할 수 있습니다.',
|
||||
'formula_name.unique' => '해당 모델에 이미 동일한 공식 이름이 존재합니다.',
|
||||
'formula_expression.required' => '공식 표현식은 필수입니다.',
|
||||
'formula_expression.max' => '공식 표현식은 1000자를 초과할 수 없습니다.',
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
'calculation_order.min' => '계산 순서는 0 이상이어야 합니다.',
|
||||
'dependencies.array' => '의존성은 배열 형태여야 합니다.',
|
||||
'dependencies.*.regex' => '의존성 변수명은 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용할 수 있습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'formula_name' => '공식 이름',
|
||||
'formula_expression' => '공식 표현식',
|
||||
'unit' => '단위',
|
||||
'description' => '설명',
|
||||
'calculation_order' => '계산 순서',
|
||||
'dependencies' => '의존성',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'calculation_order' => $this->integer('calculation_order', 0),
|
||||
]);
|
||||
|
||||
// Convert dependencies to array if it's a string
|
||||
if ($this->has('dependencies') && is_string($this->input('dependencies'))) {
|
||||
$dependencies = json_decode($this->input('dependencies'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['dependencies' => $dependencies]);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up formula expression - remove extra whitespace
|
||||
if ($this->has('formula_expression')) {
|
||||
$expression = preg_replace('/\s+/', ' ', trim($this->input('formula_expression')));
|
||||
$this->merge(['formula_expression' => $expression]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateFormulaExpression($validator);
|
||||
$this->validateDependencies($validator);
|
||||
$this->validateNoCircularDependency($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate formula expression syntax.
|
||||
*/
|
||||
private function validateFormulaExpression($validator): void
|
||||
{
|
||||
$expression = $this->input('formula_expression');
|
||||
|
||||
if (!$expression) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for potentially dangerous characters or functions
|
||||
$dangerousPatterns = [
|
||||
'/\b(eval|exec|system|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite)\b/i',
|
||||
'/[;{}]/', // Semicolons and braces
|
||||
'/\$[a-zA-Z_]/', // PHP variables
|
||||
'/\bfunction\s*\(/i', // Function definitions
|
||||
];
|
||||
|
||||
foreach ($dangerousPatterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
$validator->errors()->add('formula_expression', '공식 표현식에 허용되지 않는 문자나 함수가 포함되어 있습니다.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate mathematical expression format
|
||||
if (!$this->isValidMathExpression($expression)) {
|
||||
$validator->errors()->add('formula_expression', '공식 표현식의 형식이 올바르지 않습니다. 수학 연산자와 변수명만 사용할 수 있습니다.');
|
||||
}
|
||||
|
||||
// Extract variables from expression and validate they exist as parameters
|
||||
$this->validateExpressionVariables($validator, $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if expression contains valid mathematical operations.
|
||||
*/
|
||||
private function isValidMathExpression(string $expression): bool
|
||||
{
|
||||
// Allow: numbers, variables, basic math operators, parentheses, math functions
|
||||
$allowedPattern = '/^[a-zA-Z0-9_\s\+\-\*\/\(\)\.\,]+$/';
|
||||
|
||||
// Allow common math functions
|
||||
$mathFunctions = ['sin', 'cos', 'tan', 'log', 'exp', 'sqrt', 'pow', 'abs', 'ceil', 'floor', 'round', 'max', 'min'];
|
||||
$functionPattern = '/\b(' . implode('|', $mathFunctions) . ')\s*\(/i';
|
||||
|
||||
// Remove math functions for basic pattern check
|
||||
$cleanExpression = preg_replace($functionPattern, '', $expression);
|
||||
|
||||
return preg_match($allowedPattern, $cleanExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that variables in expression exist as model parameters.
|
||||
*/
|
||||
private function validateExpressionVariables($validator, string $expression): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
// Extract variable names from expression
|
||||
preg_match_all('/\b[a-zA-Z][a-zA-Z0-9_]*\b/', $expression, $matches);
|
||||
$variables = $matches[0];
|
||||
|
||||
// Remove math functions from variables
|
||||
$mathFunctions = ['sin', 'cos', 'tan', 'log', 'exp', 'sqrt', 'pow', 'abs', 'ceil', 'floor', 'round', 'max', 'min'];
|
||||
$variables = array_diff($variables, $mathFunctions);
|
||||
|
||||
if (empty($variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check for undefined variables
|
||||
$undefinedVariables = array_diff($variables, $existingParameters);
|
||||
|
||||
if (!empty($undefinedVariables)) {
|
||||
$validator->errors()->add('formula_expression',
|
||||
'공식에 정의되지 않은 변수가 사용되었습니다: ' . implode(', ', $undefinedVariables)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate dependencies array.
|
||||
*/
|
||||
private function validateDependencies($validator): void
|
||||
{
|
||||
$dependencies = $this->input('dependencies', []);
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
if (empty($dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check that all dependencies exist as parameters
|
||||
$invalidDependencies = array_diff($dependencies, $existingParameters);
|
||||
|
||||
if (!empty($invalidDependencies)) {
|
||||
$validator->errors()->add('dependencies',
|
||||
'존재하지 않는 매개변수가 의존성에 포함되어 있습니다: ' . implode(', ', $invalidDependencies)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate there's no circular dependency.
|
||||
*/
|
||||
private function validateNoCircularDependency($validator): void
|
||||
{
|
||||
$dependencies = $this->input('dependencies', []);
|
||||
$formulaName = $this->input('formula_name');
|
||||
|
||||
if (empty($dependencies) || !$formulaName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for direct self-reference
|
||||
if (in_array($formulaName, $dependencies)) {
|
||||
$validator->errors()->add('dependencies', '공식이 자기 자신을 참조할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// For more complex circular dependency check, this would require
|
||||
// analyzing all formulas in the model - simplified version here
|
||||
// In production, implement full dependency graph analysis
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ModelParameterFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$parameterId = $this->route('parameterId');
|
||||
|
||||
$rules = [
|
||||
'parameter_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:50',
|
||||
'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
Rule::unique('model_parameters')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'parameter_type' => ['required', 'string', 'in:INPUT,OUTPUT'],
|
||||
'is_required' => ['boolean'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.*' => ['string', 'max:100'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'sort_order' => ['integer', 'min:0'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($parameterId) {
|
||||
$rules['parameter_name'][4] = $rules['parameter_name'][4]->ignore($parameterId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'parameter_name.required' => '매개변수 이름은 필수입니다.',
|
||||
'parameter_name.regex' => '매개변수 이름은 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용할 수 있습니다.',
|
||||
'parameter_name.unique' => '해당 모델에 이미 동일한 매개변수 이름이 존재합니다.',
|
||||
'parameter_type.required' => '매개변수 타입은 필수입니다.',
|
||||
'parameter_type.in' => '매개변수 타입은 INPUT 또는 OUTPUT이어야 합니다.',
|
||||
'min_value.numeric' => '최소값은 숫자여야 합니다.',
|
||||
'max_value.numeric' => '최대값은 숫자여야 합니다.',
|
||||
'max_value.gte' => '최대값은 최소값보다 크거나 같아야 합니다.',
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
'sort_order.min' => '정렬 순서는 0 이상이어야 합니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'parameter_name' => '매개변수 이름',
|
||||
'parameter_type' => '매개변수 타입',
|
||||
'is_required' => '필수 여부',
|
||||
'default_value' => '기본값',
|
||||
'min_value' => '최소값',
|
||||
'max_value' => '최대값',
|
||||
'unit' => '단위',
|
||||
'options' => '옵션 목록',
|
||||
'description' => '설명',
|
||||
'sort_order' => '정렬 순서',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_required' => $this->boolean('is_required', false),
|
||||
'sort_order' => $this->integer('sort_order', 0),
|
||||
]);
|
||||
|
||||
// Convert options to array if it's a string
|
||||
if ($this->has('options') && is_string($this->input('options'))) {
|
||||
$options = json_decode($this->input('options'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['options' => $options]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// Validate that INPUT parameters can have default values and constraints
|
||||
if ($this->input('parameter_type') === 'INPUT') {
|
||||
$this->validateInputParameterConstraints($validator);
|
||||
}
|
||||
|
||||
// Validate that OUTPUT parameters don't have input-specific fields
|
||||
if ($this->input('parameter_type') === 'OUTPUT') {
|
||||
$this->validateOutputParameterConstraints($validator);
|
||||
}
|
||||
|
||||
// Validate min/max value relationship
|
||||
$this->validateMinMaxValues($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate INPUT parameter specific constraints.
|
||||
*/
|
||||
private function validateInputParameterConstraints($validator): void
|
||||
{
|
||||
// INPUT parameters can have all constraints
|
||||
// No additional validation needed for INPUT type
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate OUTPUT parameter specific constraints.
|
||||
*/
|
||||
private function validateOutputParameterConstraints($validator): void
|
||||
{
|
||||
// OUTPUT parameters should not have certain input-specific fields
|
||||
if ($this->filled('is_required') && $this->input('is_required')) {
|
||||
$validator->errors()->add('is_required', 'OUTPUT 매개변수는 필수 항목이 될 수 없습니다.');
|
||||
}
|
||||
|
||||
if ($this->filled('default_value')) {
|
||||
$validator->errors()->add('default_value', 'OUTPUT 매개변수는 기본값을 가질 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate min/max value relationship.
|
||||
*/
|
||||
private function validateMinMaxValues($validator): void
|
||||
{
|
||||
$minValue = $this->input('min_value');
|
||||
$maxValue = $this->input('max_value');
|
||||
|
||||
if ($minValue !== null && $maxValue !== null && $minValue > $maxValue) {
|
||||
$validator->errors()->add('max_value', '최대값은 최소값보다 크거나 같아야 합니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Design\BomTemplate;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
|
||||
class ProductFromModelFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$tenantId = auth()->user()?->currentTenant?->id;
|
||||
|
||||
return [
|
||||
// Model and BOM configuration
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
|
||||
// Product basic information
|
||||
'product_code' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:50',
|
||||
'regex:/^[A-Z0-9_-]+$/',
|
||||
Rule::unique('products')
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'product_name' => ['required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
|
||||
// Product categorization
|
||||
'category_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'product_type' => ['nullable', 'string', 'in:PRODUCT,PART,SUBASSEMBLY'],
|
||||
|
||||
// Product specifications
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'min_order_qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'lead_time_days' => ['nullable', 'integer', 'min:0', 'max:365'],
|
||||
'is_active' => ['boolean'],
|
||||
|
||||
// Product creation options
|
||||
'create_bom_items' => ['boolean'],
|
||||
'validate_bom' => ['boolean'],
|
||||
'save_parameters' => ['boolean'],
|
||||
'auto_generate_variants' => ['boolean'],
|
||||
|
||||
// Pricing and cost
|
||||
'base_cost' => ['nullable', 'numeric', 'min:0'],
|
||||
'markup_percentage' => ['nullable', 'numeric', 'min:0', 'max:1000'],
|
||||
|
||||
// Additional attributes (dynamic based on category)
|
||||
'attributes' => ['sometimes', 'array'],
|
||||
'attributes.*' => ['nullable'],
|
||||
|
||||
// Tags and classification
|
||||
'tags' => ['sometimes', 'array'],
|
||||
'tags.*' => ['string', 'max:50'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters.required' => '입력 매개변수는 필수입니다.',
|
||||
'input_parameters.array' => '입력 매개변수는 배열 형태여야 합니다.',
|
||||
'input_parameters.min' => '최소 하나 이상의 입력 매개변수가 필요합니다.',
|
||||
'input_parameters.*.required' => '모든 입력 매개변수 값은 필수입니다.',
|
||||
|
||||
'product_code.required' => '제품 코드는 필수입니다.',
|
||||
'product_code.regex' => '제품 코드는 대문자, 숫자, 언더스코어, 하이픈만 사용할 수 있습니다.',
|
||||
'product_code.unique' => '이미 존재하는 제품 코드입니다.',
|
||||
|
||||
'product_name.required' => '제품명은 필수입니다.',
|
||||
'product_name.max' => '제품명은 100자를 초과할 수 없습니다.',
|
||||
|
||||
'description.max' => '설명은 1000자를 초과할 수 없습니다.',
|
||||
|
||||
'category_id.integer' => '카테고리 ID는 정수여야 합니다.',
|
||||
'category_id.min' => '카테고리 ID는 1 이상이어야 합니다.',
|
||||
|
||||
'product_type.in' => '제품 타입은 PRODUCT, PART, SUBASSEMBLY 중 하나여야 합니다.',
|
||||
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'min_order_qty.numeric' => '최소 주문 수량은 숫자여야 합니다.',
|
||||
'min_order_qty.min' => '최소 주문 수량은 0 이상이어야 합니다.',
|
||||
|
||||
'lead_time_days.integer' => '리드타임은 정수여야 합니다.',
|
||||
'lead_time_days.min' => '리드타임은 0 이상이어야 합니다.',
|
||||
'lead_time_days.max' => '리드타임은 365일을 초과할 수 없습니다.',
|
||||
|
||||
'base_cost.numeric' => '기본 원가는 숫자여야 합니다.',
|
||||
'base_cost.min' => '기본 원가는 0 이상이어야 합니다.',
|
||||
|
||||
'markup_percentage.numeric' => '마크업 비율은 숫자여야 합니다.',
|
||||
'markup_percentage.min' => '마크업 비율은 0 이상이어야 합니다.',
|
||||
'markup_percentage.max' => '마크업 비율은 1000%를 초과할 수 없습니다.',
|
||||
|
||||
'bom_template_id.integer' => 'BOM 템플릿 ID는 정수여야 합니다.',
|
||||
'bom_template_id.min' => 'BOM 템플릿 ID는 1 이상이어야 합니다.',
|
||||
|
||||
'tags.array' => '태그는 배열 형태여야 합니다.',
|
||||
'tags.*.max' => '각 태그는 50자를 초과할 수 없습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => '입력 매개변수',
|
||||
'bom_template_id' => 'BOM 템플릿 ID',
|
||||
'product_code' => '제품 코드',
|
||||
'product_name' => '제품명',
|
||||
'description' => '설명',
|
||||
'category_id' => '카테고리 ID',
|
||||
'product_type' => '제품 타입',
|
||||
'unit' => '단위',
|
||||
'min_order_qty' => '최소 주문 수량',
|
||||
'lead_time_days' => '리드타임',
|
||||
'is_active' => '활성 상태',
|
||||
'create_bom_items' => 'BOM 아이템 생성',
|
||||
'validate_bom' => 'BOM 유효성 검사',
|
||||
'save_parameters' => '매개변수 저장',
|
||||
'auto_generate_variants' => '자동 변형 생성',
|
||||
'base_cost' => '기본 원가',
|
||||
'markup_percentage' => '마크업 비율',
|
||||
'attributes' => '추가 속성',
|
||||
'tags' => '태그',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'create_bom_items' => $this->boolean('create_bom_items', true),
|
||||
'validate_bom' => $this->boolean('validate_bom', true),
|
||||
'save_parameters' => $this->boolean('save_parameters', true),
|
||||
'auto_generate_variants' => $this->boolean('auto_generate_variants', false),
|
||||
]);
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure attributes is an array
|
||||
if ($this->has('attributes') && !is_array($this->input('attributes'))) {
|
||||
$attributes = json_decode($this->input('attributes'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['attributes' => $attributes]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure tags is an array
|
||||
if ($this->has('tags') && !is_array($this->input('tags'))) {
|
||||
$tags = json_decode($this->input('tags'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['tags' => $tags]);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default product_type if not provided
|
||||
if (!$this->has('product_type')) {
|
||||
$this->merge(['product_type' => 'PRODUCT']);
|
||||
}
|
||||
|
||||
// Convert product_code to uppercase
|
||||
if ($this->has('product_code')) {
|
||||
$this->merge(['product_code' => strtoupper($this->input('product_code'))]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateInputParameters($validator);
|
||||
$this->validateBomTemplate($validator);
|
||||
$this->validateCategory($validator);
|
||||
$this->validateParameterValues($validator);
|
||||
$this->validateBusinessRules($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 category exists and is accessible.
|
||||
*/
|
||||
private function validateCategory($validator): void
|
||||
{
|
||||
$categoryId = $this->input('category_id');
|
||||
|
||||
if (!$categoryId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$category = Category::where('id', $categoryId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
if (!$category) {
|
||||
$validator->errors()->add('category_id', '지정된 카테고리가 존재하지 않습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Skip validation for empty optional parameters
|
||||
if (!$parameter->is_required && ($value === null || $value === '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use model's validation method if available
|
||||
if (method_exists($parameter, 'validateValue') && !$parameter->validateValue($value)) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}의 값이 유효하지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate business rules specific to product creation.
|
||||
*/
|
||||
private function validateBusinessRules($validator): void
|
||||
{
|
||||
// If validate_bom is true, ensure we have enough data for BOM validation
|
||||
if ($this->input('validate_bom', true) && !$this->input('bom_template_id')) {
|
||||
// Could check if model has default BOM template or condition rules
|
||||
// For now, just warn
|
||||
}
|
||||
|
||||
// If auto_generate_variants is true, validate variant generation is possible
|
||||
if ($this->input('auto_generate_variants', false)) {
|
||||
// Check if model has variant-generating parameters
|
||||
// This would require checking parameter configurations
|
||||
}
|
||||
|
||||
// Validate pricing logic
|
||||
$baseCost = $this->input('base_cost');
|
||||
$markupPercentage = $this->input('markup_percentage');
|
||||
|
||||
if ($baseCost !== null && $markupPercentage !== null) {
|
||||
if ($baseCost == 0 && $markupPercentage > 0) {
|
||||
$validator->errors()->add('markup_percentage', '기본 원가가 0인 경우 마크업을 설정할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'target_parameter' => ['required', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'expression' => ['required', 'string', 'max:1000'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_active' => ['boolean'],
|
||||
'execution_order' => ['integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'target_parameter.regex' => __('validation.model_formula.target_parameter_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.formula_name'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
'expression' => __('validation.attributes.formula_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'execution_order' => __('validation.attributes.execution_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'execution_order' => $this->integer('execution_order', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexModelFormulaRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'target_parameter' => ['sometimes', 'string', 'max:50'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'target_parameter' => ['sometimes', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'expression' => ['sometimes', 'string', 'max:1000'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
'execution_order' => ['sometimes', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'target_parameter.regex' => __('validation.model_formula.target_parameter_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.formula_name'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
'expression' => __('validation.attributes.formula_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'execution_order' => __('validation.attributes.execution_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
|
||||
if ($this->has('execution_order')) {
|
||||
$this->merge(['execution_order' => $this->integer('execution_order')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'label' => ['required', 'string', 'max:100'],
|
||||
'type' => ['required', 'string', 'in:INPUT,OUTPUT'],
|
||||
'data_type' => ['required', 'string', 'in:INTEGER,DECIMAL,STRING,BOOLEAN'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'enum_values' => ['nullable', 'array'],
|
||||
'enum_values.*' => ['string', 'max:100'],
|
||||
'validation_rules' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_required' => ['boolean'],
|
||||
'display_order' => ['integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('validation.model_parameter.name_format'),
|
||||
'max_value.gte' => __('validation.model_parameter.max_value_gte_min'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.parameter_name'),
|
||||
'label' => __('validation.attributes.parameter_label'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
'data_type' => __('validation.attributes.data_type'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'default_value' => __('validation.attributes.default_value'),
|
||||
'min_value' => __('validation.attributes.min_value'),
|
||||
'max_value' => __('validation.attributes.max_value'),
|
||||
'enum_values' => __('validation.attributes.enum_values'),
|
||||
'validation_rules' => __('validation.attributes.validation_rules'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_required' => __('validation.attributes.is_required'),
|
||||
'display_order' => __('validation.attributes.display_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_required' => $this->boolean('is_required'),
|
||||
'display_order' => $this->integer('display_order', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexModelParameterRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'type' => ['sometimes', 'string', 'in:INPUT,OUTPUT'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'label' => ['sometimes', 'string', 'max:100'],
|
||||
'type' => ['sometimes', 'string', 'in:INPUT,OUTPUT'],
|
||||
'data_type' => ['sometimes', 'string', 'in:INTEGER,DECIMAL,STRING,BOOLEAN'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'enum_values' => ['nullable', 'array'],
|
||||
'enum_values.*' => ['string', 'max:100'],
|
||||
'validation_rules' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'display_order' => ['sometimes', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('validation.model_parameter.name_format'),
|
||||
'max_value.gte' => __('validation.model_parameter.max_value_gte_min'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.parameter_name'),
|
||||
'label' => __('validation.attributes.parameter_label'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
'data_type' => __('validation.attributes.data_type'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'default_value' => __('validation.attributes.default_value'),
|
||||
'min_value' => __('validation.attributes.min_value'),
|
||||
'max_value' => __('validation.attributes.max_value'),
|
||||
'enum_values' => __('validation.attributes.enum_values'),
|
||||
'validation_rules' => __('validation.attributes.validation_rules'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_required' => __('validation.attributes.is_required'),
|
||||
'display_order' => __('validation.attributes.display_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_required')) {
|
||||
$this->merge(['is_required' => $this->boolean('is_required')]);
|
||||
}
|
||||
|
||||
if ($this->has('display_order')) {
|
||||
$this->merge(['display_order' => $this->integer('display_order')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\BomConditionRule;
|
||||
|
||||
class BomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => '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) ?? []
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BomResolveRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'parameters' => 'required|array',
|
||||
'parameters.*' => 'required',
|
||||
'preview_only' => 'boolean',
|
||||
'use_cache' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'model_id.required' => __('error.model_id_required'),
|
||||
'model_id.exists' => __('error.model_not_found'),
|
||||
'parameters.required' => __('error.parameters_required'),
|
||||
'parameters.array' => __('error.parameters_must_be_array'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateParameters($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수 검증
|
||||
*/
|
||||
private function validateParameters($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('parameters')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$parameterService = new \App\Services\ModelParameterService();
|
||||
$errors = $parameterService->validateParameterValues(
|
||||
$this->input('model_id'),
|
||||
$this->input('parameters')
|
||||
);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $paramName => $paramErrors) {
|
||||
foreach ($paramErrors as $error) {
|
||||
$validator->errors()->add("parameters.{$paramName}", $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$validator->errors()->add('parameters', __('error.parameter_validation_failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\ModelFormula;
|
||||
|
||||
class ModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'name' => 'required|string|max:50|regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
'label' => 'required|string|max:100',
|
||||
'expression' => 'required|string|max:1000',
|
||||
'unit' => 'nullable|string|max:20',
|
||||
'order' => 'nullable|integer|min:1',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('error.formula_name_format'),
|
||||
'expression.required' => __('error.formula_expression_required'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateFormulaNameUnique($validator);
|
||||
$this->validateFormulaExpression($validator);
|
||||
$this->validateDependencies($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 공식명 중복 검증
|
||||
*/
|
||||
private function validateFormulaNameUnique($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = ModelFormula::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.formula_name_duplicate'));
|
||||
}
|
||||
|
||||
// 매개변수명과 중복 검증
|
||||
$parameterExists = \Shared\Models\Products\ModelParameter::where('model_id', $this->input('model_id'))
|
||||
->where('name', $this->input('name'))
|
||||
->exists();
|
||||
|
||||
if ($parameterExists) {
|
||||
$validator->errors()->add('name', __('error.formula_name_conflicts_with_parameter'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공식 표현식 검증
|
||||
*/
|
||||
private function validateFormulaExpression($validator): void
|
||||
{
|
||||
if (!$this->input('expression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempFormula = new ModelFormula([
|
||||
'expression' => $this->input('expression'),
|
||||
'model_id' => $this->input('model_id'),
|
||||
]);
|
||||
|
||||
$expressionErrors = $tempFormula->validateExpression();
|
||||
|
||||
if (!empty($expressionErrors)) {
|
||||
foreach ($expressionErrors as $error) {
|
||||
$validator->errors()->add('expression', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 의존성 검증
|
||||
*/
|
||||
private function validateDependencies($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('expression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempFormula = new ModelFormula([
|
||||
'expression' => $this->input('expression'),
|
||||
'model_id' => $this->input('model_id'),
|
||||
]);
|
||||
|
||||
$dependencies = $tempFormula->extractVariables();
|
||||
|
||||
if (empty($dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 매개변수 목록 가져오기
|
||||
$parameters = \Shared\Models\Products\ModelParameter::where('model_id', $this->input('model_id'))
|
||||
->active()
|
||||
->pluck('name')
|
||||
->toArray();
|
||||
|
||||
// 기존 공식 목록 가져오기 (자기 자신 제외)
|
||||
$formulasQuery = ModelFormula::where('model_id', $this->input('model_id'))
|
||||
->active();
|
||||
|
||||
if ($this->route('id')) {
|
||||
$formulasQuery->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
$formulas = $formulasQuery->pluck('name')->toArray();
|
||||
|
||||
$validNames = array_merge($parameters, $formulas);
|
||||
|
||||
foreach ($dependencies as $dep) {
|
||||
if (!in_array($dep, $validNames)) {
|
||||
$validator->errors()->add('expression', __('error.dependency_not_found', ['name' => $dep]));
|
||||
}
|
||||
}
|
||||
|
||||
// 순환 의존성 검증
|
||||
$this->validateCircularDependency($validator, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* 순환 의존성 검증
|
||||
*/
|
||||
private function validateCircularDependency($validator, array $dependencies): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allFormulasQuery = ModelFormula::where('model_id', $this->input('model_id'))
|
||||
->active();
|
||||
|
||||
if ($this->route('id')) {
|
||||
$allFormulasQuery->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
$allFormulas = $allFormulasQuery->get();
|
||||
|
||||
// 현재 공식을 임시로 추가
|
||||
$tempFormula = new ModelFormula([
|
||||
'name' => $this->input('name'),
|
||||
'dependencies' => $dependencies,
|
||||
]);
|
||||
$allFormulas->push($tempFormula);
|
||||
|
||||
if ($this->hasCircularDependency($tempFormula, $allFormulas->toArray())) {
|
||||
$validator->errors()->add('expression', __('error.circular_dependency_detected'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 순환 의존성 검사
|
||||
*/
|
||||
private function hasCircularDependency(ModelFormula $formula, array $allFormulas, array $visited = []): bool
|
||||
{
|
||||
if (in_array($formula->name, $visited)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$visited[] = $formula->name;
|
||||
|
||||
foreach ($formula->dependencies ?? [] as $dep) {
|
||||
foreach ($allFormulas as $depFormula) {
|
||||
if ($depFormula->name === $dep) {
|
||||
if ($this->hasCircularDependency($depFormula, $allFormulas, $visited)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\ModelParameter;
|
||||
|
||||
class ModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'name' => 'required|string|max:50|regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
'label' => 'required|string|max:100',
|
||||
'type' => 'required|string|in:' . implode(',', ModelParameter::TYPES),
|
||||
'unit' => 'nullable|string|max:20',
|
||||
'validation_rules' => 'nullable|array',
|
||||
'options' => 'nullable|array',
|
||||
'default_value' => 'nullable',
|
||||
'order' => 'nullable|integer|min:1',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'is_required' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// 타입별 세부 검증
|
||||
if ($this->input('type') === ModelParameter::TYPE_NUMBER) {
|
||||
$rules['validation_rules.min'] = 'nullable|numeric';
|
||||
$rules['validation_rules.max'] = 'nullable|numeric|gte:validation_rules.min';
|
||||
$rules['default_value'] = 'nullable|numeric';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_SELECT) {
|
||||
$rules['options'] = 'required|array|min:1';
|
||||
$rules['options.*'] = 'required|string|max:100';
|
||||
$rules['default_value'] = 'nullable|string|in_array:options';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_BOOLEAN) {
|
||||
$rules['default_value'] = 'nullable|boolean';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_TEXT) {
|
||||
$rules['validation_rules.max_length'] = 'nullable|integer|min:1|max:1000';
|
||||
$rules['validation_rules.pattern'] = 'nullable|string|max:200';
|
||||
$rules['default_value'] = 'nullable|string';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('error.parameter_name_format'),
|
||||
'validation_rules.max.gte' => __('error.max_must_be_greater_than_min'),
|
||||
'options.required' => __('error.select_type_requires_options'),
|
||||
'options.min' => __('error.select_type_requires_options'),
|
||||
'default_value.in_array' => __('error.default_value_not_in_options'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// 추가 검증 로직
|
||||
$this->validateParameterNameUnique($validator);
|
||||
$this->validateValidationRules($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수명 중복 검증
|
||||
*/
|
||||
private function validateParameterNameUnique($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = ModelParameter::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.parameter_name_duplicate'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙 유효성 검증
|
||||
*/
|
||||
private function validateValidationRules($validator): void
|
||||
{
|
||||
$type = $this->input('type');
|
||||
$validationRules = $this->input('validation_rules', []);
|
||||
|
||||
if ($type === ModelParameter::TYPE_TEXT && isset($validationRules['pattern'])) {
|
||||
// 정규식 패턴 검증
|
||||
if (@preg_match($validationRules['pattern'], '') === false) {
|
||||
$validator->errors()->add('validation_rules.pattern', __('error.invalid_regex_pattern'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductFromModelRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'parameters' => 'required|array',
|
||||
'parameters.*' => 'required',
|
||||
'product_data' => 'nullable|array',
|
||||
'product_data.name' => 'nullable|string|max:200',
|
||||
'product_data.code' => 'nullable|string|max:100|unique:products,code',
|
||||
'product_data.description' => 'nullable|string|max:1000',
|
||||
'product_data.category_id' => 'nullable|integer|exists:categories,id',
|
||||
'product_data.memo' => 'nullable|string|max:500',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'model_id.required' => __('error.model_id_required'),
|
||||
'model_id.exists' => __('error.model_not_found'),
|
||||
'parameters.required' => __('error.parameters_required'),
|
||||
'parameters.array' => __('error.parameters_must_be_array'),
|
||||
'product_data.code.unique' => __('error.product_code_duplicate'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateParameters($validator);
|
||||
$this->validateProductData($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수 검증
|
||||
*/
|
||||
private function validateParameters($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('parameters')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$parameterService = new \App\Services\ModelParameterService();
|
||||
$errors = $parameterService->validateParameterValues(
|
||||
$this->input('model_id'),
|
||||
$this->input('parameters')
|
||||
);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $paramName => $paramErrors) {
|
||||
foreach ($paramErrors as $error) {
|
||||
$validator->errors()->add("parameters.{$paramName}", $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$validator->errors()->add('parameters', __('error.parameter_validation_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 제품 데이터 검증
|
||||
*/
|
||||
private function validateProductData($validator): void
|
||||
{
|
||||
$productData = $this->input('product_data', []);
|
||||
|
||||
// 제품 코드 중복 검증 (수정 시 제외)
|
||||
if (!empty($productData['code'])) {
|
||||
$query = \Shared\Models\Products\Product::where('code', $productData['code']);
|
||||
|
||||
if ($this->route('id')) {
|
||||
$query->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
$validator->errors()->add('product_data.code', __('error.product_code_duplicate'));
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 존재 확인
|
||||
if (!empty($productData['category_id'])) {
|
||||
$categoryExists = \Shared\Models\Products\Category::where('id', $productData['category_id'])
|
||||
->where('is_active', true)
|
||||
->exists();
|
||||
|
||||
if (!$categoryExists) {
|
||||
$validator->errors()->add('product_data.category_id', __('error.category_not_found'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// JSON 문자열인 경우 배열로 변환
|
||||
if ($this->has('parameters') && is_string($this->input('parameters'))) {
|
||||
$this->merge([
|
||||
'parameters' => json_decode($this->input('parameters'), true) ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->has('product_data') && is_string($this->input('product_data'))) {
|
||||
$this->merge([
|
||||
'product_data' => json_decode($this->input('product_data'), true) ?? []
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user