|string> */ public function rules(): array { $productId = $this->route('id'); $rules = [ 'product_type' => ['required', 'string', 'max:50'], 'product_categories_id' => ['required', 'exists:product_categories,id'], 'name' => [ 'required', 'string', 'max:255', function ($attribute, $value, $fail) use ($productId) { $slug = Str::slug($value); $query = Product::where('slug', $slug); // Exclude current product when updating if ($productId) { $query->where('id', '!=', $productId); } if ($query->exists()) { $fail('Tên sản phẩm đã tồn tại. Vui lòng chọn tên khác.'); } }, ], 'short_description' => ['nullable', 'string', 'max:255'], 'description' => ['nullable', 'string'], 'thumbnail' => ['nullable', 'string', 'max:255'], 'gallery' => ['nullable', 'array'], 'gallery.*' => ['nullable', 'string', 'max:255'], 'stock' => ['nullable', 'integer', 'min:0'], 'is_unlimited' => ['boolean'], 'cpu' => ['nullable', 'string', 'max:255'], 'ram' => ['nullable', 'string', 'max:255'], 'gpu' => ['nullable', 'string', 'max:255'], 'disk' => ['nullable', 'string', 'max:255'], 'bandwidth' => ['nullable', 'string', 'max:255'], 'features' => ['nullable', 'json'], 'metadata' => ['nullable', 'json'], 'setup_fee' => ['nullable', 'numeric', 'min:0'], 'trial_days' => ['nullable', 'integer', 'min:0'], 'currency' => ['nullable', 'string', 'max:10'], 'is_active' => ['boolean'], 'sort_order' => ['nullable', 'integer'], 'published_at' => ['nullable', 'date'], // OS versions (pivot table os_product) - for VPS 'os_ids' => ['nullable', 'array'], 'os_ids.*' => ['exists:os,id'], // Control Panels (pivot table product_control_panel) - for Hosting 'control_panel_ids' => ['nullable', 'array'], 'control_panel_ids.*' => ['exists:control_panels,id'], 'control_panel_price_modifiers' => ['nullable', 'array'], 'control_panel_price_modifiers.*' => ['nullable', 'numeric', 'min:0'], 'control_panel_default' => ['nullable', 'exists:control_panels,id'], // Images 'thumbnail' => ['nullable', 'string', 'max:255'], 'gallery' => ['nullable', 'array'], 'gallery.*' => ['nullable', 'string', 'max:255'], // Pricing cycles (table product_pricings) 'pricing' => ['nullable', 'array'], ]; // Add dynamic validation for each billing cycle foreach (ProductBillingCycle::cases() as $cycle) { $cycleKey = $cycle->value; $rules["pricing.{$cycleKey}.enabled"] = ['nullable', 'boolean']; $rules["pricing.{$cycleKey}.price"] = ['nullable', 'numeric', 'min:0']; $rules["pricing.{$cycleKey}.price_sale"] = [ 'nullable', 'numeric', 'min:0', "required_with:pricing.{$cycleKey}.price_sale_check", function ($attribute, $value, $fail) use ($cycleKey) { $price = $this->input("pricing.{$cycleKey}.price"); // price_sale requires price to be set first if ($value !== null && $value !== '' && (empty($price) || $price <= 0)) { $fail("Bạn phải nhập giá gốc trước khi nhập giá khuyến mãi cho {$cycleKey}."); return; } // price_sale must be less than price if ($value !== null && $value !== '' && $price !== null && $value >= $price) { $fail("Giá khuyến mãi phải nhỏ hơn giá gốc cho {$cycleKey}."); } }, ]; } return $rules; } /** * Get the error messages for the defined validation rules. * * @return array */ public function messages(): array { return [ 'product_type.required' => 'Vui lòng chọn loại sản phẩm.', 'product_type.string' => 'Loại sản phẩm phải là chuỗi ký tự.', 'product_type.max' => 'Loại sản phẩm không được vượt quá 50 ký tự.', 'product_categories_id.required' => 'Vui lòng chọn danh mục sản phẩm.', 'product_categories_id.exists' => 'Danh mục sản phẩm không hợp lệ.', 'name.required' => 'Vui lòng nhập tên sản phẩm.', 'name.string' => 'Tên sản phẩm phải là chuỗi ký tự.', 'name.max' => 'Tên sản phẩm không được vượt quá 255 ký tự.', 'short_description.string' => 'Mô tả ngắn phải là chuỗi ký tự.', 'short_description.max' => 'Mô tả ngắn không được vượt quá 255 ký tự.', 'description.string' => 'Mô tả phải là chuỗi ký tự.', 'thumbnail.string' => 'Đường dẫn ảnh đại diện phải là chuỗi ký tự.', 'thumbnail.max' => 'Đường dẫn ảnh đại diện không được vượt quá 255 ký tự.', 'gallery.array' => 'Thư viện ảnh không hợp lệ.', 'gallery.*.string' => 'Đường dẫn ảnh trong thư viện phải là chuỗi ký tự.', 'gallery.*.max' => 'Đường dẫn ảnh trong thư viện không được vượt quá 255 ký tự.', 'stock.integer' => 'Số lượng tồn kho phải là số nguyên.', 'stock.min' => 'Số lượng tồn kho không được âm.', 'is_unlimited.boolean' => 'Giá trị không giới hạn không hợp lệ.', 'cpu.string' => 'CPU phải là chuỗi ký tự.', 'cpu.max' => 'CPU không được vượt quá 255 ký tự.', 'ram.string' => 'RAM phải là chuỗi ký tự.', 'ram.max' => 'RAM không được vượt quá 255 ký tự.', 'gpu.string' => 'GPU phải là chuỗi ký tự.', 'gpu.max' => 'GPU không được vượt quá 255 ký tự.', 'disk.string' => 'Dung lượng ổ cứng phải là chuỗi ký tự.', 'disk.max' => 'Dung lượng ổ cứng không được vượt quá 255 ký tự.', 'bandwidth.string' => 'Băng thông phải là chuỗi ký tự.', 'bandwidth.max' => 'Băng thông không được vượt quá 255 ký tự.', 'features.json' => 'Tính năng phải là định dạng JSON hợp lệ.', 'metadata.json' => 'Metadata phải là định dạng JSON hợp lệ.', 'setup_fee.numeric' => 'Phí cài đặt phải là số.', 'setup_fee.min' => 'Phí cài đặt không được âm.', 'trial_days.integer' => 'Số ngày dùng thử phải là số nguyên.', 'trial_days.min' => 'Số ngày dùng thử không được âm.', 'currency.string' => 'Đơn vị tiền tệ phải là chuỗi ký tự.', 'currency.max' => 'Đơn vị tiền tệ không được vượt quá 10 ký tự.', 'is_active.boolean' => 'Trạng thái kích hoạt không hợp lệ.', 'sort_order.integer' => 'Thứ tự sắp xếp phải là số nguyên.', 'published_at.date' => 'Ngày xuất bản không hợp lệ.', 'os_ids.array' => 'Danh sách hệ điều hành không hợp lệ.', 'os_ids.*.exists' => 'Hệ điều hành được chọn không tồn tại.', 'control_panel_ids.array' => 'Danh sách Control Panel không hợp lệ.', 'control_panel_ids.*.exists' => 'Control Panel được chọn không tồn tại.', 'control_panel_price_modifiers.array' => 'Danh sách phụ phí Control Panel không hợp lệ.', 'control_panel_price_modifiers.*.numeric' => 'Phụ phí Control Panel phải là số.', 'control_panel_price_modifiers.*.min' => 'Phụ phí Control Panel không được âm.', 'control_panel_default.exists' => 'Control Panel mặc định không tồn tại.', 'pricing.array' => 'Thông tin giá không hợp lệ.', 'pricing.*.price.numeric' => 'Giá phải là số.', 'pricing.*.price.min' => 'Giá không được âm.', 'pricing.*.price_sale.numeric' => 'Giá khuyến mãi phải là số.', 'pricing.*.price_sale.min' => 'Giá khuyến mãi không được âm.', ]; } }