209 lines
9.7 KiB
PHP
209 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Requests\Product;
|
|
|
|
use App\Enums\Product\ProductBillingCycle;
|
|
use App\Models\Product;
|
|
use Illuminate\Foundation\Http\FormRequest;
|
|
use Illuminate\Support\Str;
|
|
|
|
class ProductRequest 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.
|
|
*
|
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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<string, string>
|
|
*/
|
|
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.',
|
|
];
|
|
}
|
|
}
|