feat: permitir categorías nulas en inventarios y actualizar validaciones de subcategorías en solicitudes
This commit is contained in:
parent
f184e4d444
commit
48fe26899a
@ -50,6 +50,12 @@ public function update(CategoryUpdateRequest $request, Category $categoria)
|
|||||||
|
|
||||||
public function destroy(Category $categoria)
|
public function destroy(Category $categoria)
|
||||||
{
|
{
|
||||||
|
if ($categoria->inventories()->exists()) {
|
||||||
|
return ApiResponse::BAD_REQUEST->response([
|
||||||
|
'message' => 'No se puede eliminar la clasificación porque tiene productos asociados.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$categoria->delete();
|
$categoria->delete();
|
||||||
|
|
||||||
return ApiResponse::OK->response();
|
return ApiResponse::OK->response();
|
||||||
|
|||||||
@ -24,8 +24,8 @@ public function rules(): array
|
|||||||
'key_sat' => ['nullable', 'string', 'max:20'],
|
'key_sat' => ['nullable', 'string', 'max:20'],
|
||||||
'sku' => ['nullable', 'string', 'max:50', 'unique:inventories,sku'],
|
'sku' => ['nullable', 'string', 'max:50', 'unique:inventories,sku'],
|
||||||
'barcode' => ['nullable', 'string', 'unique:inventories,barcode'],
|
'barcode' => ['nullable', 'string', 'unique:inventories,barcode'],
|
||||||
'category_id' => ['required', 'exists:categories,id'],
|
'category_id' => ['nullable', 'exists:categories,id'],
|
||||||
'subcategory_id' => ['nullable', 'exists:subcategories,id'],
|
'subcategory_id' => ['nullable', 'required_with:category_id', 'exists:subcategories,id'],
|
||||||
'unit_of_measure_id' => ['required', 'exists:units_of_measurement,id'],
|
'unit_of_measure_id' => ['required', 'exists:units_of_measurement,id'],
|
||||||
'track_serials' => ['nullable', 'boolean'],
|
'track_serials' => ['nullable', 'boolean'],
|
||||||
|
|
||||||
@ -48,8 +48,9 @@ public function messages(): array
|
|||||||
'sku.unique' => 'El SKU ya está en uso.',
|
'sku.unique' => 'El SKU ya está en uso.',
|
||||||
'barcode.string' => 'El código de barras debe ser una cadena de texto.',
|
'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.',
|
'barcode.unique' => 'El código de barras ya está registrado en otro producto.',
|
||||||
'category_id.required' => 'La categoría es obligatoria.',
|
'category_id.exists' => 'La clasificación seleccionada no es válida.',
|
||||||
'category_id.exists' => 'La categoría 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
|
// Mensajes de Price
|
||||||
'retail_price.required' => 'El precio de venta es obligatorio.',
|
'retail_price.required' => 'El precio de venta es obligatorio.',
|
||||||
'retail_price.numeric' => 'El precio de venta debe ser un número.',
|
'retail_price.numeric' => 'El precio de venta debe ser un número.',
|
||||||
@ -68,6 +69,20 @@ public function messages(): array
|
|||||||
public function withValidator($validator)
|
public function withValidator($validator)
|
||||||
{
|
{
|
||||||
$validator->after(function ($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');
|
$cost = $this->input('cost');
|
||||||
$retailPrice = $this->input('retail_price');
|
$retailPrice = $this->input('retail_price');
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ public function rules(): array
|
|||||||
'sku' => ['nullable', 'string', 'max:50'],
|
'sku' => ['nullable', 'string', 'max:50'],
|
||||||
'barcode' => ['nullable', 'string', 'unique:inventories,barcode,' . $inventoryId],
|
'barcode' => ['nullable', 'string', 'unique:inventories,barcode,' . $inventoryId],
|
||||||
'category_id' => ['nullable', 'exists:categories,id'],
|
'category_id' => ['nullable', 'exists:categories,id'],
|
||||||
'subcategory_id' => ['nullable', 'exists:subcategories,id'],
|
'subcategory_id' => ['nullable', 'required_with:category_id', 'exists:subcategories,id'],
|
||||||
'unit_of_measure_id' => ['nullable', 'exists:units_of_measurement,id'],
|
'unit_of_measure_id' => ['nullable', 'exists:units_of_measurement,id'],
|
||||||
'track_serials' => ['nullable', 'boolean'],
|
'track_serials' => ['nullable', 'boolean'],
|
||||||
|
|
||||||
@ -49,7 +49,9 @@ public function messages(): array
|
|||||||
'sku.unique' => 'El SKU ya está en uso.',
|
'sku.unique' => 'El SKU ya está en uso.',
|
||||||
'barcode.string' => 'El código de barras debe ser una cadena de texto.',
|
'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.',
|
'barcode.unique' => 'El código de barras ya está registrado en otro producto.',
|
||||||
'category_id.exists' => 'La categoría seleccionada no es válida.',
|
'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
|
// Mensajes de Price
|
||||||
'cost.numeric' => 'El costo debe ser un número.',
|
'cost.numeric' => 'El costo debe ser un número.',
|
||||||
'cost.min' => 'El costo no puede ser negativo.',
|
'cost.min' => 'El costo no puede ser negativo.',
|
||||||
@ -69,6 +71,39 @@ public function messages(): array
|
|||||||
public function withValidator($validator)
|
public function withValidator($validator)
|
||||||
{
|
{
|
||||||
$validator->after(function ($validator) {
|
$validator->after(function ($validator) {
|
||||||
|
/** @var \App\Models\Inventory $inventory */
|
||||||
|
$inventory = $this->route('inventario');
|
||||||
|
|
||||||
|
// Validar que la subcategoría pertenezca a la categoría seleccionada
|
||||||
|
$categoryId = $this->input('category_id', $inventory?->category_id);
|
||||||
|
$subcategoryId = $this->input('subcategory_id');
|
||||||
|
|
||||||
|
if ($subcategoryId && $categoryId) {
|
||||||
|
$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.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bloquear cambio de unidad de medida si el producto ya tiene movimientos de inventario
|
||||||
|
if ($this->has('unit_of_measure_id') && $inventory) {
|
||||||
|
$newUnitId = $this->input('unit_of_measure_id');
|
||||||
|
$currentUnitId = $inventory->unit_of_measure_id;
|
||||||
|
|
||||||
|
if ((int) $newUnitId !== (int) $currentUnitId) {
|
||||||
|
$hasMovements = \App\Models\InventoryMovement::where('inventory_id', $inventory->id)->exists();
|
||||||
|
if ($hasMovements) {
|
||||||
|
$validator->errors()->add(
|
||||||
|
'unit_of_measure_id',
|
||||||
|
'No se puede cambiar la unidad de medida porque el producto ya tiene existencias registradas.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$cost = $this->input('cost');
|
$cost = $this->input('cost');
|
||||||
$retailPrice = $this->input('retail_price');
|
$retailPrice = $this->input('retail_price');
|
||||||
|
|
||||||
@ -80,8 +115,8 @@ public function withValidator($validator)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validar incompatibilidad track_serials + unidades decimales
|
// Validar incompatibilidad track_serials + unidades decimales
|
||||||
$trackSerials = $this->input('track_serials', $this->route('inventario')?->track_serials);
|
$trackSerials = $this->input('track_serials', $inventory?->track_serials);
|
||||||
$unitId = $this->input('unit_of_measure_id', $this->route('inventario')?->unit_of_measure_id);
|
$unitId = $this->input('unit_of_measure_id', $inventory?->unit_of_measure_id);
|
||||||
|
|
||||||
if ($trackSerials && $unitId) {
|
if ($trackSerials && $unitId) {
|
||||||
$unit = \App\Models\UnitOfMeasurement::find($unitId);
|
$unit = \App\Models\UnitOfMeasurement::find($unitId);
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public function rules(): array
|
|||||||
'items.*.subtotal' => ['required_if:items.*.type,product', 'numeric', 'min:0'],
|
'items.*.subtotal' => ['required_if:items.*.type,product', 'numeric', 'min:0'],
|
||||||
|
|
||||||
// Comunes a ambos
|
// Comunes a ambos
|
||||||
'items.*.quantity' => ['required', 'integer', 'min:1'],
|
'items.*.quantity' => ['required', 'numeric', 'min:0.001'],
|
||||||
'items.*.warehouse_id' => ['nullable', 'exists:warehouses,id'],
|
'items.*.warehouse_id' => ['nullable', 'exists:warehouses,id'],
|
||||||
|
|
||||||
// Seriales
|
// Seriales
|
||||||
@ -93,8 +93,8 @@ public function messages(): array
|
|||||||
'items.*.inventory_id.exists' => 'El producto seleccionado no existe.',
|
'items.*.inventory_id.exists' => 'El producto seleccionado no existe.',
|
||||||
'items.*.product_name.required' => 'El nombre del producto es obligatorio.',
|
'items.*.product_name.required' => 'El nombre del producto es obligatorio.',
|
||||||
'items.*.quantity.required' => 'La cantidad es obligatoria.',
|
'items.*.quantity.required' => 'La cantidad es obligatoria.',
|
||||||
'items.*.quantity.integer' => 'La cantidad debe ser un número entero.',
|
'items.*.quantity.numeric' => 'La cantidad debe ser un número.',
|
||||||
'items.*.quantity.min' => 'La cantidad debe ser al menos 1.',
|
'items.*.quantity.min' => 'La cantidad debe ser mayor a 0.',
|
||||||
'items.*.unit_price.required' => 'El precio unitario es obligatorio.',
|
'items.*.unit_price.required' => 'El precio unitario es obligatorio.',
|
||||||
'items.*.unit_price.numeric' => 'El precio unitario debe ser un número.',
|
'items.*.unit_price.numeric' => 'El precio unitario debe ser un número.',
|
||||||
'items.*.unit_price.min' => 'El precio unitario no puede ser negativo.',
|
'items.*.unit_price.min' => 'El precio unitario no puede ser negativo.',
|
||||||
|
|||||||
@ -42,26 +42,18 @@ public function price()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stock disponible del kit = mínimo(stock_componente / cantidad_requerida)
|
* Stock disponible del kit en el almacén principal (único para venta)
|
||||||
|
* = mínimo(stock_almacén_principal_componente / cantidad_requerida)
|
||||||
*/
|
*/
|
||||||
public function getAvailableStockAttribute(): int
|
public function getAvailableStockAttribute(): int
|
||||||
{
|
{
|
||||||
if ($this->items->isEmpty()) {
|
$mainWarehouseId = Warehouse::where('is_main', true)->value('id');
|
||||||
|
|
||||||
|
if (! $mainWarehouseId) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$minStock = PHP_INT_MAX;
|
return $this->stockInWarehouse($mainWarehouseId);
|
||||||
|
|
||||||
foreach ($this->items as $item) {
|
|
||||||
$inventory = $item->inventory;
|
|
||||||
$availableStock = $inventory->stock;
|
|
||||||
|
|
||||||
// Cuántos kits puedo hacer con este componente
|
|
||||||
$possibleKits = $availableStock > 0 ? floor($availableStock / $item->quantity) : 0;
|
|
||||||
$minStock = min($minStock, $possibleKits);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $minStock === PHP_INT_MAX ? 0 : (int) $minStock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -926,17 +926,29 @@ protected function revertMovement(InventoryMovement $movement): void
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'transfer':
|
case 'transfer':
|
||||||
// Revertir traspaso: devolver al origen, quitar del destino
|
$inventory = $movement->inventory;
|
||||||
$this->updateWarehouseStock(
|
|
||||||
$movement->inventory_id,
|
if ($inventory->track_serials) {
|
||||||
$movement->warehouse_from_id,
|
// Revertir seriales: mover de vuelta al almacén origen
|
||||||
$movement->quantity
|
InventorySerial::where('transfer_movement_id', $movement->id)
|
||||||
);
|
->update([
|
||||||
$this->updateWarehouseStock(
|
'warehouse_id' => $movement->warehouse_from_id,
|
||||||
$movement->inventory_id,
|
'transfer_movement_id' => null,
|
||||||
$movement->warehouse_to_id,
|
]);
|
||||||
-$movement->quantity
|
$inventory->syncStock();
|
||||||
);
|
} else {
|
||||||
|
// Revertir traspaso sin seriales: devolver al origen, quitar del destino
|
||||||
|
$this->updateWarehouseStock(
|
||||||
|
$movement->inventory_id,
|
||||||
|
$movement->warehouse_from_id,
|
||||||
|
$movement->quantity
|
||||||
|
);
|
||||||
|
$this->updateWarehouseStock(
|
||||||
|
$movement->inventory_id,
|
||||||
|
$movement->warehouse_to_id,
|
||||||
|
-$movement->quantity
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1095,18 +1107,54 @@ protected function applyTransferUpdate(InventoryMovement $movement, array $data)
|
|||||||
$warehouseFromId = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
$warehouseFromId = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
||||||
$warehouseToId = $data['warehouse_to_id'] ?? $movement->warehouse_to_id;
|
$warehouseToId = $data['warehouse_to_id'] ?? $movement->warehouse_to_id;
|
||||||
$quantity = $data['quantity'] ?? $movement->quantity;
|
$quantity = $data['quantity'] ?? $movement->quantity;
|
||||||
|
$inventory = $movement->inventory;
|
||||||
|
|
||||||
// Validar que no sea el mismo almacén
|
// Validar que no sea el mismo almacén
|
||||||
if ($warehouseFromId === $warehouseToId) {
|
if ($warehouseFromId === $warehouseToId) {
|
||||||
throw new \Exception('No se puede traspasar al mismo almacén.');
|
throw new \Exception('No se puede traspasar al mismo almacén.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validar stock disponible en origen
|
if ($inventory->track_serials) {
|
||||||
$this->validateStock($movement->inventory_id, $warehouseFromId, $quantity);
|
$serialNumbers = $data['serial_numbers'] ?? [];
|
||||||
|
|
||||||
// Aplicar nuevo traspaso
|
if (empty($serialNumbers)) {
|
||||||
$this->updateWarehouseStock($movement->inventory_id, $warehouseFromId, -$quantity);
|
throw new \Exception("Se requieren los números de serie para editar el traspaso de '{$inventory->name}'.");
|
||||||
$this->updateWarehouseStock($movement->inventory_id, $warehouseToId, $quantity);
|
}
|
||||||
|
|
||||||
|
if (count($serialNumbers) !== (int) $quantity) {
|
||||||
|
throw new \Exception('La cantidad no coincide con el número de seriales proporcionados.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que cada serial esté disponible en el almacén origen
|
||||||
|
foreach ($serialNumbers as $serialNumber) {
|
||||||
|
$serial = InventorySerial::where('inventory_id', $inventory->id)
|
||||||
|
->where('serial_number', $serialNumber)
|
||||||
|
->where('warehouse_id', $warehouseFromId)
|
||||||
|
->where('status', 'disponible')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $serial) {
|
||||||
|
throw new \Exception("Serial {$serialNumber} no disponible en el almacén origen.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mover seriales al almacén destino
|
||||||
|
InventorySerial::where('inventory_id', $inventory->id)
|
||||||
|
->whereIn('serial_number', $serialNumbers)
|
||||||
|
->update([
|
||||||
|
'warehouse_id' => $warehouseToId,
|
||||||
|
'transfer_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$inventory->syncStock();
|
||||||
|
} else {
|
||||||
|
// Validar stock disponible en origen
|
||||||
|
$this->validateStock($movement->inventory_id, $warehouseFromId, $quantity);
|
||||||
|
|
||||||
|
// Aplicar nuevo traspaso
|
||||||
|
$this->updateWarehouseStock($movement->inventory_id, $warehouseFromId, -$quantity);
|
||||||
|
$this->updateWarehouseStock($movement->inventory_id, $warehouseToId, $quantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -122,23 +122,27 @@ public function createSale(array $data)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($inventory->track_serials) {
|
if ($inventory->track_serials) {
|
||||||
|
$serialWarehouseId = $item['warehouse_id'] ?? $this->movementService->getMainWarehouseId();
|
||||||
|
|
||||||
// Si se proporcionaron números de serie específicos
|
// Si se proporcionaron números de serie específicos
|
||||||
if (isset($item['serial_numbers']) && is_array($item['serial_numbers'])) {
|
if (isset($item['serial_numbers']) && is_array($item['serial_numbers'])) {
|
||||||
foreach ($item['serial_numbers'] as $serialNumber) {
|
foreach ($item['serial_numbers'] as $serialNumber) {
|
||||||
$serial = InventorySerial::where('inventory_id', $inventory->id)
|
$serial = InventorySerial::where('inventory_id', $inventory->id)
|
||||||
->where('serial_number', $serialNumber)
|
->where('serial_number', $serialNumber)
|
||||||
|
->where('warehouse_id', $serialWarehouseId)
|
||||||
->where('status', 'disponible')
|
->where('status', 'disponible')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($serial) {
|
if ($serial) {
|
||||||
$serial->markAsSold($saleDetail->id);
|
$serial->markAsSold($saleDetail->id);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception("Serial {$serialNumber} no disponible");
|
throw new \Exception("Serial {$serialNumber} no disponible en el almacén");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Asignar automáticamente los primeros N seriales disponibles
|
// Asignar automáticamente los primeros N seriales disponibles en el almacén
|
||||||
$serials = InventorySerial::where('inventory_id', $inventory->id)
|
$serials = InventorySerial::where('inventory_id', $inventory->id)
|
||||||
|
->where('warehouse_id', $serialWarehouseId)
|
||||||
->where('status', 'disponible')
|
->where('status', 'disponible')
|
||||||
->limit($baseQuantity)
|
->limit($baseQuantity)
|
||||||
->get();
|
->get();
|
||||||
@ -369,22 +373,24 @@ private function validateStockForAllItems(array $items): void
|
|||||||
|
|
||||||
if ($inventory->track_serials) {
|
if ($inventory->track_serials) {
|
||||||
if (! empty($serialsByProduct[$entry['inventory_id']])) {
|
if (! empty($serialsByProduct[$entry['inventory_id']])) {
|
||||||
// Validar que los seriales específicos existan y estén disponibles
|
// Validar que los seriales específicos existan, estén disponibles y en el almacén correcto
|
||||||
foreach ($serialsByProduct[$entry['inventory_id']] as $serialNumber) {
|
foreach ($serialsByProduct[$entry['inventory_id']] as $serialNumber) {
|
||||||
$serial = InventorySerial::where('inventory_id', $inventory->id)
|
$serial = InventorySerial::where('inventory_id', $inventory->id)
|
||||||
->where('serial_number', $serialNumber)
|
->where('serial_number', $serialNumber)
|
||||||
|
->where('warehouse_id', $entry['warehouse_id'])
|
||||||
->where('status', 'disponible')
|
->where('status', 'disponible')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $serial) {
|
if (! $serial) {
|
||||||
throw new \Exception(
|
throw new \Exception(
|
||||||
"Serial {$serialNumber} no disponible para {$inventory->name}"
|
"Serial {$serialNumber} no disponible en el almacén para {$inventory->name}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Validar que haya suficientes seriales disponibles para la cantidad TOTAL
|
// Validar que haya suficientes seriales disponibles en el almacén
|
||||||
$availableSerials = InventorySerial::where('inventory_id', $inventory->id)
|
$availableSerials = InventorySerial::where('inventory_id', $inventory->id)
|
||||||
|
->where('warehouse_id', $entry['warehouse_id'])
|
||||||
->where('status', 'disponible')
|
->where('status', 'disponible')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventories', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['category_id']);
|
||||||
|
$table->foreignId('category_id')->nullable()->change();
|
||||||
|
$table->foreign('category_id')->references('id')->on('categories')->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventories', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['category_id']);
|
||||||
|
$table->foreignId('category_id')->nullable(false)->change();
|
||||||
|
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user