table} p LEFT JOIN categories c ON p.category_id = c.id"; $params = []; if ($categoryId) { $query .= " WHERE p.category_id = ?"; $params = [$categoryId]; } $query .= " ORDER BY p.name ASC"; return $this->db->fetchAll($query, $params); } /** * @param $id */ public function getById($id) { $query = "SELECT p.*, c.name as category_name FROM {$this->table} p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?"; return $this->db->fetch($query, [$id]); } /** * @param $data */ public function create($data) { // Check if SKU already exists $exists = $this->db->fetchColumn( "SELECT COUNT(*) FROM {$this->table} WHERE sku = ?", [$data['sku']] ); if ($exists) { throw new Exception('SKU already exists'); } // Prepare data for insert $insertData = [ 'sku' => $data['sku'], 'name' => $data['name'], 'price' => $data['price'], 'cost' => $data['cost'], 'status' => $data['status'] ?? 'active' ]; // Optional fields if (isset($data['barcode'])) { $insertData['barcode'] = $data['barcode']; } if (isset($data['description'])) { $insertData['description'] = $data['description']; } if (isset($data['category_id'])) { $insertData['category_id'] = $data['category_id']; } if (isset($data['quantity'])) { $insertData['quantity'] = $data['quantity']; } if (isset($data['low_stock_threshold'])) { $insertData['low_stock_threshold'] = $data['low_stock_threshold']; } $productId = $this->insert($insertData); // If initial stock is added, record inventory transaction if (isset($data['quantity']) && $data['quantity'] > 0) { $inventory = new Inventory(); $inventory->recordTransaction( $productId, 'adjustment', $data['quantity'], null, 'Initial stock', $data['user_id'] ?? null ); } return $productId; } /** * @param $id * @param $data */ public function update($id, $data) { // Check if product exists $product = $this->getById($id); if (!$product) { throw new Exception('Product not found'); } // Check if SKU is unique if (isset($data['sku']) && $data['sku'] !== $product['sku']) { $exists = $this->db->fetchColumn( "SELECT COUNT(*) FROM {$this->table} WHERE sku = ? AND id != ?", [$data['sku'], $id] ); if ($exists) { throw new Exception('SKU already exists'); } } // Handle quantity updates separately $currentQuantity = $product['quantity']; $newQuantity = isset($data['quantity']) ? intval($data['quantity']) : $currentQuantity; $quantityChanged = $newQuantity !== $currentQuantity; // Remove quantity from update data (will be handled separately) unset($data['quantity']); // Update product data if (!empty($data)) { parent::update($id, $data); } // Handle quantity change if needed if ($quantityChanged) { $this->updateStock( $id, $newQuantity, 'Quantity updated via product edit', $data['user_id'] ?? null ); } return true; } /** * @param $id * @param $quantity * @param $notes * @param $userId */ public function updateStock($id, $quantity, $notes = '', $userId = null) { // Start transaction $this->db->beginTransaction(); try { // Get current quantity $currentQty = $this->db->fetchColumn( "SELECT quantity FROM {$this->table} WHERE id = ?", [$id] ); if ($currentQty === false) { throw new Exception('Product not found'); } // Update quantity $this->db->update( $this->table, ['quantity' => $quantity], ['id = ?'], [$id] ); // Record transaction $adjustment = $quantity - $currentQty; // Only record if there's an actual change if ($adjustment != 0) { $inventory = new Inventory(); $inventory->recordTransaction( $id, 'adjustment', abs($adjustment), null, $notes.($adjustment > 0 ? ' (Added)' : ' (Reduced)'), $userId ); } $this->db->commit(); return true; } catch (Exception $e) { $this->db->rollBack(); throw $e; } } /** * @param $id */ public function delete($id) { // Check if product has transactions $txCount = $this->db->fetchColumn( "SELECT COUNT(*) FROM inventory_transactions WHERE product_id = ?", [$id] ); if ($txCount > 0) { // Soft delete - mark as inactive $this->update($id, ['status' => 'inactive']); return 'deactivated'; } else { // Hard delete parent::delete($id); return 'deleted'; } } /** * @return mixed */ public function getLowStock() { return $this->db->fetchAll( "SELECT p.*, c.name as category_name FROM {$this->table} p LEFT JOIN categories c ON p.category_id = c.id WHERE p.quantity <= p.low_stock_threshold AND p.status = 'active' ORDER BY p.quantity ASC" ); } /** * @param $categoryId * @return mixed */ public function countByCategoryId($categoryId) { return $this->db->fetchColumn( "SELECT COUNT(*) FROM {$this->table} WHERE category_id = ?", [$categoryId] ); } }