0 AND b.expiry_date >= CURDATE() GROUP BY d.id ORDER BY d.name_en ASC"; $stmt = $pdo->query($sql); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); break; case 'get_low_stock': // Count total for pagination $countSql = "SELECT COUNT(*) FROM ( SELECT d.id FROM drugs d LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE() GROUP BY d.id HAVING COALESCE(SUM(b.quantity), 0) <= MAX(d.reorder_level) ) as total"; $total = $pdo->query($countSql)->fetchColumn(); // List drugs where total stock is <= reorder_level $sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit, COALESCE(SUM(b.quantity), 0) as total_stock FROM drugs d LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE() GROUP BY d.id HAVING total_stock <= MAX(d.reorder_level) ORDER BY total_stock ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit ]); break; case 'get_expired': // Count total $countSql = "SELECT COUNT(*) FROM pharmacy_batches b JOIN drugs d ON b.drug_id = d.id LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.expiry_date < CURDATE() AND b.quantity > 0"; $total = $pdo->query($countSql)->fetchColumn(); // List batches that have expired and still have quantity $sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity, d.name_en as drug_name, d.name_ar as drug_name_ar, s.name_en as supplier_name FROM pharmacy_batches b JOIN drugs d ON b.drug_id = d.id LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.expiry_date < CURDATE() AND b.quantity > 0 ORDER BY b.expiry_date ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit ]); break; case 'get_near_expiry': $days = $_GET['days'] ?? 90; // Count total $countSql = "SELECT COUNT(*) FROM pharmacy_batches b JOIN drugs d ON b.drug_id = d.id LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.expiry_date >= CURDATE() AND b.expiry_date <= DATE_ADD(CURDATE(), INTERVAL ? DAY) AND b.quantity > 0"; $countStmt = $pdo->prepare($countSql); $countStmt->execute([$days]); $total = $countStmt->fetchColumn(); // List batches expiring in the next X days $sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity, d.name_en as drug_name, d.name_ar as drug_name_ar, s.name_en as supplier_name, DATEDIFF(b.expiry_date, CURDATE()) as days_remaining FROM pharmacy_batches b JOIN drugs d ON b.drug_id = d.id LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.expiry_date >= CURDATE() AND b.expiry_date <= DATE_ADD(CURDATE(), INTERVAL :days DAY) AND b.quantity > 0 ORDER BY b.expiry_date ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':days', $days, PDO::PARAM_INT); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit ]); break; case 'get_batches': $drug_id = $_GET['drug_id'] ?? 0; if (!$drug_id) throw new Exception("Drug ID required"); $sql = "SELECT b.*, s.name_en as supplier_name FROM pharmacy_batches b LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.drug_id = ? AND b.quantity > 0 ORDER BY b.expiry_date ASC"; $stmt = $pdo->prepare($sql); $stmt->execute([$drug_id]); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); break; case 'add_stock': if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Invalid method"); $drug_id = $_POST['drug_id'] ?? 0; $batch_number = $_POST['batch_number'] ?? ''; $expiry_date = $_POST['expiry_date'] ?? ''; $quantity = $_POST['quantity'] ?? 0; $cost_price = $_POST['cost_price'] ?? 0; $sale_price = $_POST['sale_price'] ?? 0; $supplier_id = !empty($_POST['supplier_id']) ? $_POST['supplier_id'] : null; if (!$drug_id || !$batch_number || !$expiry_date || !$quantity) { throw new Exception("Missing required fields"); } $stmt = $pdo->prepare("INSERT INTO pharmacy_batches (drug_id, batch_number, expiry_date, quantity, cost_price, sale_price, supplier_id, received_date) VALUES (?, ?, ?, ?, ?, ?, ?, CURDATE())"); $stmt->execute([$drug_id, $batch_number, $expiry_date, $quantity, $cost_price, $sale_price, $supplier_id]); echo json_encode(['success' => true, 'message' => 'Stock added successfully']); break; case 'search_drugs': $q = $_GET['q'] ?? ''; $sql = "SELECT d.id, d.name_en, d.name_ar, d.sku, d.price as default_price, (SELECT sale_price FROM pharmacy_batches pb WHERE pb.drug_id = d.id AND pb.quantity > 0 AND pb.expiry_date >= CURDATE() ORDER BY pb.expiry_date ASC LIMIT 1) as batch_price, COALESCE(SUM(b.quantity), 0) as stock FROM drugs d LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE() WHERE (d.name_en LIKE ? OR d.name_ar LIKE ? OR d.sku LIKE ?) GROUP BY d.id LIMIT 20"; $stmt = $pdo->prepare($sql); $term = "%$q%"; $stmt->execute([$term, $term, $term]); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); break; case 'create_sale': if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Invalid method"); $input = json_decode(file_get_contents('php://input'), true); if (empty($input['items'])) throw new Exception("No items in sale"); $pdo->beginTransaction(); try { // Create Sale Record $stmt = $pdo->prepare("INSERT INTO pharmacy_sales (patient_id, visit_id, total_amount, payment_method, status) VALUES (?, ?, ?, ?, 'completed')"); $stmt->execute([ $input['patient_id'] ?? null, $input['visit_id'] ?? null, $input['total_amount'] ?? 0, $input['payment_method'] ?? 'cash' ]); $sale_id = $pdo->lastInsertId(); // Process Items foreach ($input['items'] as $item) { $drug_id = $item['drug_id']; $qty_needed = $item['quantity']; $unit_price = $item['price']; // Or fetch from batch? Use provided price for now. // Fetch available batches (FIFO) $batch_stmt = $pdo->prepare("SELECT id, quantity FROM pharmacy_batches WHERE drug_id = ? AND quantity > 0 ORDER BY expiry_date ASC FOR UPDATE"); $batch_stmt->execute([$drug_id]); $batches = $batch_stmt->fetchAll(PDO::FETCH_ASSOC); $qty_remaining = $qty_needed; foreach ($batches as $batch) { if ($qty_remaining <= 0) break; $take = min($batch['quantity'], $qty_remaining); // Deduct from batch $update = $pdo->prepare("UPDATE pharmacy_batches SET quantity = quantity - ? WHERE id = ?"); $update->execute([$take, $batch['id']]); // Add to sale items $item_stmt = $pdo->prepare("INSERT INTO pharmacy_sale_items (sale_id, drug_id, batch_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?)"); $item_stmt->execute([$sale_id, $drug_id, $batch['id'], $take, $unit_price, $take * $unit_price]); $qty_remaining -= $take; } if ($qty_remaining > 0) { throw new Exception("Insufficient stock for drug ID: $drug_id"); } } $pdo->commit(); echo json_encode(['success' => true, 'sale_id' => $sale_id]); } catch (Exception $e) { $pdo->rollBack(); throw $e; } break; case 'get_sales': // List recent sales $sql = "SELECT s.*, p.name as patient_name FROM pharmacy_sales s LEFT JOIN patients p ON s.patient_id = p.id ORDER BY s.created_at DESC LIMIT 50"; $stmt = $pdo->query($sql); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); break; case 'get_sale_details': $sale_id = $_GET['sale_id'] ?? 0; if (!$sale_id) throw new Exception("Sale ID required"); $stmt = $pdo->prepare("SELECT s.*, p.name as patient_name FROM pharmacy_sales s LEFT JOIN patients p ON s.patient_id = p.id WHERE s.id = ?"); $stmt->execute([$sale_id]); $sale = $stmt->fetch(PDO::FETCH_ASSOC); if (!$sale) throw new Exception("Sale not found"); $items_stmt = $pdo->prepare("SELECT i.*, d.name_en as drug_name FROM pharmacy_sale_items i JOIN drugs d ON i.drug_id = d.id WHERE i.sale_id = ?"); $items_stmt->execute([$sale_id]); $sale['items'] = $items_stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode($sale); break; case 'get_report': $type = $_GET['type'] ?? 'inventory_valuation'; $startDate = $_GET['start_date'] ?? date('Y-m-01'); $endDate = $_GET['end_date'] ?? date('Y-m-d'); if ($type === 'inventory_valuation') { // Count distinct drugs in stock for pagination $countSql = "SELECT COUNT(DISTINCT d.id) FROM drugs d JOIN pharmacy_batches b ON d.id = b.drug_id WHERE b.quantity > 0"; $total = $pdo->query($countSql)->fetchColumn(); $sql = "SELECT d.name_en as drug_name, d.name_ar as drug_name_ar, g.name_en as category_name, SUM(b.quantity) as stock_quantity, SUM(b.quantity * b.cost_price) / SUM(b.quantity) as avg_cost, SUM(b.quantity * b.sale_price) / SUM(b.quantity) as selling_price, SUM(b.quantity * b.cost_price) as total_cost_value, SUM(b.quantity * b.sale_price) as total_sales_value FROM drugs d JOIN pharmacy_batches b ON d.id = b.drug_id LEFT JOIN drugs_groups g ON d.group_id = g.id WHERE b.quantity > 0 GROUP BY d.id ORDER BY d.name_en ASC LIMIT :limit OFFSET :offset"; // Calculate Grand Totals (entire stock) $grandTotalSql = "SELECT SUM(b.quantity * b.cost_price) as total_cost, SUM(b.quantity * b.sale_price) as total_sales FROM pharmacy_batches b WHERE b.quantity > 0"; $grandTotals = $pdo->query($grandTotalSql)->fetch(PDO::FETCH_ASSOC); $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit, 'grand_total_cost' => $grandTotals['total_cost'] ?? 0, 'grand_total_sales' => $grandTotals['total_sales'] ?? 0 ]); } elseif ($type === 'sales') { // Count $countSql = "SELECT COUNT(*) FROM pharmacy_sales WHERE created_at BETWEEN ? AND ? + INTERVAL 1 DAY"; $countStmt = $pdo->prepare($countSql); $countStmt->execute([$startDate, $endDate]); $total = $countStmt->fetchColumn(); $sql = "SELECT s.id, s.created_at, s.total_amount, s.payment_method, p.name as patient_name, (SELECT COUNT(*) FROM pharmacy_sale_items i WHERE i.sale_id = s.id) as item_count FROM pharmacy_sales s LEFT JOIN patients p ON s.patient_id = p.id WHERE s.created_at BETWEEN :start AND :end + INTERVAL 1 DAY ORDER BY s.created_at DESC LIMIT :limit OFFSET :offset"; $grandTotalSql = "SELECT SUM(total_amount) as total FROM pharmacy_sales WHERE created_at BETWEEN ? AND ? + INTERVAL 1 DAY"; $grandTotalStmt = $pdo->prepare($grandTotalSql); $grandTotalStmt->execute([$startDate, $endDate]); $grandTotal = $grandTotalStmt->fetchColumn(); $stmt = $pdo->prepare($sql); $stmt->bindValue(':start', $startDate); $stmt->bindValue(':end', $endDate); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit, 'grand_total_sales' => $grandTotal ?? 0 ]); } elseif ($type === 'expiry') { $countSql = "SELECT COUNT(*) FROM pharmacy_batches WHERE expiry_date BETWEEN ? AND ? AND quantity > 0"; $countStmt = $pdo->prepare($countSql); $countStmt->execute([$startDate, $endDate]); $total = $countStmt->fetchColumn(); $sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity, d.name_en as drug_name, d.name_ar as drug_name_ar, s.name_en as supplier_name, DATEDIFF(b.expiry_date, CURDATE()) as days_remaining FROM pharmacy_batches b JOIN drugs d ON b.drug_id = d.id LEFT JOIN suppliers s ON b.supplier_id = s.id WHERE b.expiry_date BETWEEN :start AND :end AND b.quantity > 0 ORDER BY b.expiry_date ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':start', $startDate); $stmt->bindValue(':end', $endDate); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit ]); } elseif ($type === 'purchase_report') { $countSql = "SELECT COUNT(*) FROM pharmacy_lpos WHERE lpo_date BETWEEN ? AND ?"; $countStmt = $pdo->prepare($countSql); $countStmt->execute([$startDate, $endDate]); $total = $countStmt->fetchColumn(); $sql = "SELECT l.id, l.lpo_date, l.status, l.total_amount, s.name_en as supplier_name, s.name_ar as supplier_name_ar FROM pharmacy_lpos l LEFT JOIN suppliers s ON l.supplier_id = s.id WHERE l.lpo_date BETWEEN :start AND :end ORDER BY l.lpo_date DESC LIMIT :limit OFFSET :offset"; $grandTotalSql = "SELECT SUM(total_amount) as total FROM pharmacy_lpos WHERE lpo_date BETWEEN ? AND ?"; $grandTotalStmt = $pdo->prepare($grandTotalSql); $grandTotalStmt->execute([$startDate, $endDate]); $grandTotal = $grandTotalStmt->fetchColumn(); $stmt = $pdo->prepare($sql); $stmt->bindValue(':start', $startDate); $stmt->bindValue(':end', $endDate); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit, 'grand_total_purchases' => $grandTotal ?? 0 ]); } elseif ($type === 'low_stock') { // Reuse get_low_stock logic $countSql = "SELECT COUNT(*) FROM ( SELECT d.id FROM drugs d LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE() GROUP BY d.id HAVING COALESCE(SUM(b.quantity), 0) <= MAX(d.reorder_level) ) as total"; $total = $pdo->query($countSql)->fetchColumn(); $sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit, COALESCE(SUM(b.quantity), 0) as total_stock FROM drugs d LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE() GROUP BY d.id HAVING total_stock <= MAX(d.reorder_level) ORDER BY total_stock ASC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); echo json_encode([ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit ]); } break; default: throw new Exception("Invalid action"); } } catch (Exception $e) { http_response_code(400); echo json_encode(['error' => $e->getMessage()]); }