['required', 'string', 'max:100'], 'key_sat' => ['nullable', 'string', 'max:20'], 'sku' => ['nullable', 'string', 'max:50', 'unique:inventories,sku'], 'barcode' => ['nullable', 'string', 'unique:inventories,barcode'], 'category_id' => ['nullable', 'exists:categories,id'], 'subcategory_id' => ['nullable', 'required_with:category_id', 'exists:subcategories,id'], 'unit_of_measure_id' => ['required', 'exists:units_of_measurement,id'], 'track_serials' => ['nullable', 'boolean'], // Campos de Price 'cost' => ['nullable', 'numeric', 'min:0'], 'retail_price' => ['required', 'numeric', 'min:0'], 'tax' => ['nullable', 'numeric', 'min:0', 'max:100'], ]; } public function messages(): array { return [ // Mensajes de Inventory 'name.required' => 'El nombre es obligatorio.', 'name.string' => 'El nombre debe ser una cadena de texto.', 'name.max' => 'El nombre no debe exceder los 100 caracteres.', 'sku.string' => 'El SKU debe ser una cadena de texto.', 'sku.max' => 'El SKU no debe exceder los 50 caracteres.', 'sku.unique' => 'El SKU ya está en uso.', 'barcode.string' => 'El código de barras debe ser una cadena de texto.', 'barcode.unique' => 'El código de barras ya está registrado en otro producto.', 'category_id.exists' => 'La clasificación seleccionada no es válida.', 'subcategory_id.required_with' => 'La subclasificación es obligatoria cuando se asigna una clasificación.', 'subcategory_id.exists' => 'La subclasificación seleccionada no es válida.', // Mensajes de Price 'retail_price.required' => 'El precio de venta es obligatorio.', 'retail_price.numeric' => 'El precio de venta debe ser un número.', 'retail_price.min' => 'El precio de venta no puede ser negativo.', 'retail_price.gt' => 'El precio de venta debe ser mayor que el costo.', 'tax.numeric' => 'El impuesto debe ser un número.', 'tax.min' => 'El impuesto no puede ser negativo.', 'tax.max' => 'El impuesto no puede exceder el 100%.', ]; } /** * Validación condicional: retail_price > cost solo si cost > 0 * Y validación track_serials con unidades decimales */ public function withValidator($validator) { $validator->after(function ($validator) { // Validar que la subcategoría pertenezca a la categoría seleccionada $categoryId = $this->input('category_id'); $subcategoryId = $this->input('subcategory_id'); if ($categoryId && $subcategoryId) { $subcategory = \App\Models\Subcategory::find($subcategoryId); if ($subcategory && (int) $subcategory->category_id !== (int) $categoryId) { $validator->errors()->add( 'subcategory_id', 'La subclasificación no pertenece a la clasificación seleccionada.' ); } } $cost = $this->input('cost'); $retailPrice = $this->input('retail_price'); if ($cost !== null && $cost > 0 && $retailPrice !== null && $retailPrice <= $cost) { $validator->errors()->add( 'retail_price', 'El precio de venta debe ser mayor que el costo.' ); } // Validar incompatibilidad track_serials + unidades decimales if ($this->input('track_serials')) { $unit = \App\Models\UnitOfMeasurement::find($this->input('unit_of_measure_id')); if ($unit && $unit->allows_decimals) { $validator->errors()->add( 'track_serials', 'No se pueden usar seriales con unidades fraccionarias (kg, L, m). Cambia a Unidad/Pieza/Caja o desactiva los seriales.' ); } } }); } }