38960-vm/api/pharmacy.php
2026-03-21 12:43:58 +00:00

469 lines
22 KiB
PHP

<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
$pdo = db();
try {
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
$offset = ($page - 1) * $limit;
switch ($action) {
case 'get_stock':
// List all drugs with total stock quantity
$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
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()]);
}