updateWarehouseStock($inventory->id, $warehouse->id, $quantity); // Sincronizar stock global $inventory->syncGlobalStock(); // Registrar movimiento return InventoryMovement::create([ 'inventory_id' => $inventory->id, 'warehouse_from_id' => null, // Entrada externa 'warehouse_to_id' => $warehouse->id, 'movement_type' => $data['movement_type'] ?? 'entry', 'quantity' => $quantity, 'user_id' => auth()->id(), 'notes' => $data['notes'] ?? null, 'reference_type' => $data['reference_type'] ?? null, 'reference_id' => $data['reference_id'] ?? null, ]); }); } /** * Salida de inventario (merma, ajuste negativo, robo) */ public function exit(array $data): InventoryMovement { return DB::transaction(function () use ($data) { $inventory = Inventory::findOrFail($data['inventory_id']); $warehouse = Warehouse::findOrFail($data['warehouse_id']); $quantity = $data['quantity']; // Validar stock disponible $this->validateStock($inventory->id, $warehouse->id, $quantity); // Decrementar stock en inventory_warehouse $this->updateWarehouseStock($inventory->id, $warehouse->id, -$quantity); // Sincronizar stock global $inventory->syncGlobalStock(); // Registrar movimiento return InventoryMovement::create([ 'inventory_id' => $inventory->id, 'warehouse_from_id' => $warehouse->id, 'warehouse_to_id' => null, // Salida externa 'movement_type' => $data['movement_type'] ?? 'exit', 'quantity' => $quantity, 'user_id' => auth()->id(), 'notes' => $data['notes'] ?? null, 'reference_type' => $data['reference_type'] ?? null, 'reference_id' => $data['reference_id'] ?? null, ]); }); } /** * Traspaso entre almacenes */ public function transfer(array $data): InventoryMovement { return DB::transaction(function () use ($data) { $inventory = Inventory::findOrFail($data['inventory_id']); $warehouseFrom = Warehouse::findOrFail($data['warehouse_from_id']); $warehouseTo = Warehouse::findOrFail($data['warehouse_to_id']); $quantity = $data['quantity']; // Validar que no sea el mismo almacén if ($warehouseFrom->id === $warehouseTo->id) { throw new \Exception('No se puede traspasar al mismo almacén.'); } // Validar stock disponible en almacén origen $this->validateStock($inventory->id, $warehouseFrom->id, $quantity); // Decrementar en origen $this->updateWarehouseStock($inventory->id, $warehouseFrom->id, -$quantity); // Incrementar en destino $this->updateWarehouseStock($inventory->id, $warehouseTo->id, $quantity); // Stock global no cambia, no es necesario sincronizar // Registrar movimiento return InventoryMovement::create([ 'inventory_id' => $inventory->id, 'warehouse_from_id' => $warehouseFrom->id, 'warehouse_to_id' => $warehouseTo->id, 'movement_type' => 'transfer', 'quantity' => $quantity, 'user_id' => auth()->id(), 'notes' => $data['notes'] ?? null, ]); }); } /** * Actualizar stock en inventory_warehouse */ protected function updateWarehouseStock(int $inventoryId, int $warehouseId, int $quantityChange): void { $record = InventoryWarehouse::firstOrCreate( [ 'inventory_id' => $inventoryId, 'warehouse_id' => $warehouseId, ], ['stock' => 0] ); $newStock = $record->stock + $quantityChange; // No permitir stock negativo if ($newStock < 0) { throw new \Exception('Stock insuficiente en el almacén.'); } $record->update(['stock' => $newStock]); } /** * Validar stock disponible */ protected function validateStock(int $inventoryId, int $warehouseId, int $requiredQuantity): void { $record = InventoryWarehouse::where('inventory_id', $inventoryId) ->where('warehouse_id', $warehouseId) ->first(); $availableStock = $record?->stock ?? 0; if ($availableStock < $requiredQuantity) { throw new \Exception("Stock insuficiente. Disponible: {$availableStock}, Requerido: {$requiredQuantity}"); } } /** * Registrar movimiento de venta */ public function recordSale(int $inventoryId, int $warehouseId, int $quantity, int $saleId): InventoryMovement { return InventoryMovement::create([ 'inventory_id' => $inventoryId, 'warehouse_from_id' => $warehouseId, 'warehouse_to_id' => null, 'movement_type' => 'sale', 'quantity' => $quantity, 'reference_type' => 'App\Models\Sale', 'reference_id' => $saleId, 'user_id' => auth()->id(), ]); } /** * Registrar movimiento de devolución */ public function recordReturn(int $inventoryId, int $warehouseId, int $quantity, int $returnId): InventoryMovement { return InventoryMovement::create([ 'inventory_id' => $inventoryId, 'warehouse_from_id' => null, 'warehouse_to_id' => $warehouseId, 'movement_type' => 'return', 'quantity' => $quantity, 'reference_type' => 'App\Models\Returns', 'reference_id' => $returnId, 'user_id' => auth()->id(), ]); } }