fix: permisos de usuario

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-19 12:51:52 -06:00
parent eaad8a57df
commit 2c8189ca59
4 changed files with 103 additions and 29 deletions

View File

@ -90,6 +90,7 @@ public function import(InventoryImportRequest $request)
return ApiResponse::OK->response([
'message' => 'Importación completada exitosamente.',
'imported' => $stats['imported'],
'updated' => $stats['updated'],
'skipped' => $stats['skipped'],
'errors' => $stats['errors'],
]);

View File

@ -44,13 +44,15 @@ public function messages(): array
/**
* Reglas de validación para cada fila del Excel
* Nota: SKU y código de barras no tienen 'unique' porque se permite reimportar
* para agregar stock/seriales a productos existentes
*/
public static function rowRules(): array
{
return [
'nombre' => ['required', 'string', 'max:100'],
'sku' => ['nullable', 'string', 'max:50', 'unique:inventories,sku'],
'codigo_barras' => ['nullable', 'string', 'max:100', 'unique:inventories,barcode'],
'sku' => ['nullable', 'string', 'max:50'],
'codigo_barras' => ['nullable', 'string', 'max:100'],
'categoria' => ['nullable', 'string', 'max:100'],
'stock' => ['required', 'integer', 'min:0'],
'costo' => ['required', 'numeric', 'min:0'],
@ -67,9 +69,7 @@ public static function rowMessages(): array
return [
'nombre.required' => 'El nombre del producto es requerido.',
'nombre.max' => 'El nombre no debe exceder los 100 caracteres.',
'sku.unique' => 'El SKU ya existe en el sistema.',
'sku.max' => 'El SKU no debe exceder los 50 caracteres.',
'codigo_barras.unique' => 'El código de barras ya existe en el sistema.',
'stock.required' => 'El stock es requerido.',
'stock.integer' => 'El stock debe ser un número entero.',
'stock.min' => 'El stock no puede ser negativo.',

View File

@ -33,6 +33,7 @@ class ProductsImport implements ToModel, WithHeadingRow, WithValidation, WithChu
private $errors = [];
private $imported = 0;
private $updated = 0;
private $skipped = 0;
/**
@ -64,7 +65,21 @@ public function model(array $row)
}
try {
// Validar que el precio de venta sea mayor que el costo
// Buscar producto existente por SKU o código de barras
$existingInventory = null;
if (!empty($row['sku'])) {
$existingInventory = Inventory::where('sku', trim($row['sku']))->first();
}
if (!$existingInventory && !empty($row['codigo_barras'])) {
$existingInventory = Inventory::where('barcode', trim($row['codigo_barras']))->first();
}
// Si el producto ya existe, solo agregar stock y seriales
if ($existingInventory) {
return $this->updateExistingProduct($existingInventory, $row);
}
// Producto nuevo: validar precios
$costo = (float) $row['costo'];
$precioVenta = (float) $row['precio_venta'];
@ -90,7 +105,7 @@ public function model(array $row)
$inventory->sku = !empty($row['sku']) ? trim($row['sku']) : null;
$inventory->barcode = !empty($row['codigo_barras']) ? trim($row['codigo_barras']) : null;
$inventory->category_id = $categoryId;
$inventory->stock = 0; // Se calculará automáticamente
$inventory->stock = 0;
$inventory->is_active = true;
$inventory->save();
@ -103,28 +118,7 @@ public function model(array $row)
]);
// Crear números de serie si se proporcionan
if (!empty($row['numeros_serie'])) {
$serials = explode(',', $row['numeros_serie']);
foreach ($serials as $serial) {
$serial = trim($serial);
if (!empty($serial)) {
InventorySerial::create([
'inventory_id' => $inventory->id,
'serial_number' => $serial,
'status' => 'disponible',
]);
}
}
// Sincronizar stock basado en los seriales
$inventory->syncStock();
} else {
// Si no se proporcionan seriales, es un producto no serializado.
// Asignar el stock directamente desde la columna 'stock'.
$inventory->stock = (int) ($row['stock'] ?? 0);
$inventory->save();
}
$this->addSerials($inventory, $row['numeros_serie'] ?? null, $row['stock'] ?? 0);
$this->imported++;
@ -136,6 +130,80 @@ public function model(array $row)
}
}
/**
* Actualiza un producto existente: suma stock y agrega seriales nuevos
*/
private function updateExistingProduct(Inventory $inventory, array $row)
{
$serialsAdded = 0;
$serialsSkipped = 0;
// Agregar seriales nuevos (ignorar duplicados)
if (!empty($row['numeros_serie'])) {
$serials = explode(',', $row['numeros_serie']);
foreach ($serials as $serial) {
$serial = trim($serial);
if (empty($serial)) continue;
// Verificar si el serial ya existe (global, no solo en este producto)
$exists = InventorySerial::where('serial_number', $serial)->exists();
if (!$exists) {
InventorySerial::create([
'inventory_id' => $inventory->id,
'serial_number' => $serial,
'status' => 'disponible',
]);
$serialsAdded++;
} else {
$serialsSkipped++;
}
}
// Sincronizar stock basado en seriales disponibles
$inventory->syncStock();
} else {
// Producto sin seriales: sumar stock
$stockToAdd = (int) ($row['stock'] ?? 0);
$inventory->increment('stock', $stockToAdd);
}
$this->updated++;
if ($serialsSkipped > 0) {
$this->errors[] = "Producto '{$inventory->name}': {$serialsSkipped} seriales ya existían y fueron ignorados";
}
return null; // No retornar modelo para evitar que Maatwebsite intente guardarlo
}
/**
* Agrega seriales a un producto nuevo
*/
private function addSerials(Inventory $inventory, ?string $serialsString, int $stockFromExcel)
{
if (!empty($serialsString)) {
$serials = explode(',', $serialsString);
foreach ($serials as $serial) {
$serial = trim($serial);
if (!empty($serial)) {
InventorySerial::create([
'inventory_id' => $inventory->id,
'serial_number' => $serial,
'status' => 'disponible',
]);
}
}
$inventory->syncStock();
} else {
// Producto sin seriales
$inventory->stock = $stockFromExcel;
$inventory->save();
}
}
/**
* Reglas de validación para cada fila
*/
@ -167,6 +235,7 @@ public function getStats(): array
{
return [
'imported' => $this->imported,
'updated' => $this->updated,
'skipped' => $this->skipped,
'errors' => $this->errors,
];

View File

@ -166,6 +166,7 @@ public function run(): void
$salesCreate,
$salesCancel,
$inventoryIndex,
$inventoryImport,
$inventoryCreate,
$inventoryEdit,
$inventoryDestroy,
@ -190,8 +191,11 @@ public function run(): void
$salesIndex, // Ver historial de ventas
$salesCreate, // Crear ventas
// Inventario (solo lectura)
$inventoryIndex, // Listar productos
$inventoryIndex,
$inventoryImport, // Importar productos
$inventoryCreate,
$inventoryEdit,
$inventoryDestroy,
// Clientes
$clientIndex, // Buscar clientes
);