updating pharmacy

This commit is contained in:
Flatlogic Bot 2026-03-21 09:33:13 +00:00
parent 641316f659
commit bab61e3570
16 changed files with 1042 additions and 94 deletions

View File

@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../includes/common_data.php';
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
@ -9,13 +9,18 @@ try {
switch ($action) {
case 'search':
$q = $_GET['q'] ?? '';
if (strlen($q) < 1) {
// Allow empty search to return nothing or some default?
// Select2 usually sends a query.
if (empty($q)) {
echo json_encode([]);
exit;
}
$stmt = $pdo->prepare("SELECT id, name, phone FROM patients WHERE name LIKE ? OR phone LIKE ? LIMIT 20");
// Search by name or phone
$sql = "SELECT id, name_en as name, phone FROM patients WHERE name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ? LIMIT 20";
$stmt = $pdo->prepare($sql);
$term = "%$q%";
$stmt->execute([$term, $term]);
$stmt->execute([$term, $term, $term]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($results);
break;
@ -27,4 +32,4 @@ try {
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}

View File

@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/../includes/common_data.php'; // Includes db/config.php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
@ -58,16 +58,17 @@ try {
case 'search_drugs':
$q = $_GET['q'] ?? '';
$sql = "SELECT d.id, d.name_en, d.name_ar, d.price as default_price,
$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 ?
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]);
$stmt->execute([$term, $term, $term]);
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
break;
@ -135,7 +136,7 @@ try {
case 'get_sales':
// List recent sales
$sql = "SELECT s.*, p.name_en as patient_name
$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";
@ -147,7 +148,7 @@ try {
$sale_id = $_GET['sale_id'] ?? 0;
if (!$sale_id) throw new Exception("Sale ID required");
$stmt = $pdo->prepare("SELECT s.*, p.name_en as patient_name FROM pharmacy_sales s LEFT JOIN patients p ON s.patient_id = p.id WHERE s.id = ?");
$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);

93
api/pharmacy_lpo.php Normal file
View File

@ -0,0 +1,93 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
try {
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($action === 'create_lpo') {
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data['supplier_id']) || empty($data['items'])) {
throw new Exception("Supplier and items are required.");
}
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO pharmacy_lpos (supplier_id, lpo_date, status, total_amount, notes) VALUES (?, ?, 'Draft', ?, ?)");
$stmt->execute([
$data['supplier_id'],
$data['lpo_date'] ?? date('Y-m-d'),
$data['total_amount'] ?? 0,
$data['notes'] ?? ''
]);
$lpoId = $pdo->lastInsertId();
$stmtItem = $pdo->prepare("INSERT INTO pharmacy_lpo_items (lpo_id, drug_id, quantity, cost_price, total_cost) VALUES (?, ?, ?, ?, ?)");
foreach ($data['items'] as $item) {
$stmtItem->execute([
$lpoId,
$item['drug_id'],
$item['quantity'],
$item['cost_price'],
$item['total_cost']
]);
}
$pdo->commit();
echo json_encode(['success' => true, 'message' => 'LPO created successfully']);
} elseif ($action === 'update_status') {
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data['id']) || empty($data['status'])) {
throw new Exception("ID and Status are required");
}
$stmt = $pdo->prepare("UPDATE pharmacy_lpos SET status = ? WHERE id = ?");
$stmt->execute([$data['status'], $data['id']]);
echo json_encode(['success' => true]);
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($action === 'get_lpos') {
$stmt = $pdo->query("
SELECT l.*, s.name_en as supplier_name
FROM pharmacy_lpos l
LEFT JOIN suppliers s ON l.supplier_id = s.id
ORDER BY l.created_at DESC
");
echo json_encode($stmt->fetchAll());
} elseif ($action === 'get_lpo_details') {
$id = $_GET['id'] ?? 0;
$stmt = $pdo->prepare("
SELECT i.*, d.name_en as drug_name, d.sku
FROM pharmacy_lpo_items i
LEFT JOIN drugs d ON i.drug_id = d.id
WHERE i.lpo_id = ?
");
$stmt->execute([$id]);
echo json_encode($stmt->fetchAll());
} elseif ($action === 'get_suppliers') {
$stmt = $pdo->query("SELECT id, name_en, name_ar FROM suppliers ORDER BY name_en ASC");
echo json_encode($stmt->fetchAll());
} elseif ($action === 'get_drugs') {
$stmt = $pdo->query("SELECT id, name_en, name_ar, sku, price FROM drugs ORDER BY name_en ASC");
echo json_encode($stmt->fetchAll());
}
}
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}

View File

@ -0,0 +1,2 @@
ALTER TABLE drugs ADD COLUMN sku VARCHAR(50) DEFAULT NULL AFTER id;
CREATE INDEX idx_drugs_sku ON drugs(sku);

View File

@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS `pharmacy_lpos` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`supplier_id` INT NOT NULL,
`lpo_date` DATE NOT NULL,
`status` ENUM('Draft', 'Sent', 'Received', 'Cancelled') DEFAULT 'Draft',
`total_amount` DECIMAL(10, 2) DEFAULT 0.00,
`notes` TEXT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`supplier_id`) REFERENCES `suppliers`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `pharmacy_lpo_items` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`lpo_id` INT NOT NULL,
`drug_id` INT NOT NULL,
`quantity` INT NOT NULL,
`cost_price` DECIMAL(10, 2) NOT NULL,
`total_cost` DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (`lpo_id`) REFERENCES `pharmacy_lpos`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`drug_id`) REFERENCES `drugs`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -131,15 +131,16 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
</div>
<!-- Pharmacy Module -->
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_lpos']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
<span><i class="bi bi-capsule me-2"></i> <?php echo __('pharmacy'); ?></span>
<i class="bi bi-chevron-down small"></i>
</a>
<div class="collapse <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers']) ? 'show' : ''; ?>" id="pharmacySubmenu">
<div class="collapse <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_lpos']) ? 'show' : ''; ?>" id="pharmacySubmenu">
<div class="sidebar-submenu">
<a href="pharmacy_inventory.php" class="sidebar-link py-2 <?php echo $section === 'pharmacy_inventory' ? 'active' : ''; ?>"><i class="bi bi-boxes me-2"></i> <?php echo __('inventory'); ?></a>
<a href="pharmacy_pos.php" class="sidebar-link py-2 <?php echo $section === 'pharmacy_pos' ? 'active' : ''; ?>"><i class="bi bi-cart-check me-2"></i> <?php echo __('pos'); ?></a>
<a href="pharmacy_sales.php" class="sidebar-link py-2 <?php echo $section === 'pharmacy_sales' ? 'active' : ''; ?>"><i class="bi bi-receipt me-2"></i> <?php echo __('sales_history'); ?></a>
<a href="pharmacy_lpos.php" class="sidebar-link py-2 <?php echo $section === 'pharmacy_lpos' ? 'active' : ''; ?>"><i class="bi bi-receipt-cutoff me-2"></i> <?php echo __('lpos'); ?></a>
<div class="border-top my-1 border-secondary" style="border-color: #0d4680 !important;"></div>
<a href="drugs.php" class="sidebar-link py-2 <?php echo $section === 'drugs' ? 'active' : ''; ?>"><i class="bi bi-list-check me-2"></i> <?php echo __('drugs'); ?></a>
<a href="drugs_groups.php" class="sidebar-link py-2 <?php echo $section === 'drugs_groups' ? 'active' : ''; ?>"><i class="bi bi-collection me-2"></i> <?php echo __('groups'); ?></a>

View File

@ -1,7 +1,4 @@
<?php
$section = 'pharmacy_inventory';
require_once __DIR__ . '/../layout/header.php';
$page = $_GET['page'] ?? 1;
$search = $_GET['search'] ?? '';
@ -88,13 +85,13 @@ $drugs = $stmt->fetchAll(PDO::FETCH_ASSOC);
<?php foreach ($drugs as $drug): ?>
<?php
$status_class = 'bg-success';
$status_text = 'OK';
$status_text = __('in_stock');
if ($drug['total_stock'] <= 0) {
$status_class = 'bg-danger';
$status_text = 'Out of Stock';
$status_text = __('out_of_stock');
} elseif ($drug['total_stock'] <= $drug['min_stock_level']) {
$status_class = 'bg-warning text-dark';
$status_text = 'Low Stock';
$status_text = __('low_stock');
}
?>
<tr>
@ -128,15 +125,56 @@ $drugs = $stmt->fetchAll(PDO::FETCH_ASSOC);
<nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center">
<li class="page-item <?php echo $page <= 1 ? 'disabled' : ''; ?>">
<a class="page-link" href="?page=<?php echo $page - 1; ?>&search=<?php echo urlencode($search); ?>"><?php echo __('previous'); ?></a>
<a class="page-link" href="?page=<?php echo max(1, $page - 1); ?>&search=<?php echo urlencode($search); ?>"><?php echo __('previous'); ?></a>
</li>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php echo $page == $i ? 'active' : ''; ?>">
<a class="page-link" href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>"><?php echo $i; ?></a>
</li>
<?php endfor; ?>
<?php
$range = 2;
// First page
if ($page == 1) {
echo '<li class="page-item active"><span class="page-link">1</span></li>';
} else {
echo '<li class="page-item"><a class="page-link" href="?page=1&search='.urlencode($search).'">1</a></li>';
}
// Start of range
$start = max(2, $page - $range);
// End of range
$end = min($total_pages - 1, $page + $range);
// Dots before range
if ($start > 2) {
echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
// Range loop
for ($i = $start; $i <= $end; $i++) {
if ($i == $page) {
echo '<li class="page-item active"><span class="page-link">'.$i.'</span></li>';
} else {
echo '<li class="page-item"><a class="page-link" href="?page='.$i.'&search='.urlencode($search).'">'.$i.'</a></li>';
}
}
// Dots after range
if ($end < $total_pages - 1) {
echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
// Last page (if total > 1)
if ($total_pages > 1) {
if ($page == $total_pages) {
echo '<li class="page-item active"><span class="page-link">'.$total_pages.'</span></li>';
} else {
echo '<li class="page-item"><a class="page-link" href="?page='.$total_pages.'&search='.urlencode($search).'">'.$total_pages.'</a></li>';
}
}
?>
<li class="page-item <?php echo $page >= $total_pages ? 'disabled' : ''; ?>">
<a class="page-link" href="?page=<?php echo $page + 1; ?>&search=<?php echo urlencode($search); ?>"><?php echo __('next'); ?></a>
<a class="page-link" href="?page=<?php echo min($total_pages, $page + 1); ?>&search=<?php echo urlencode($search); ?>"><?php echo __('next'); ?></a>
</li>
</ul>
</nav>
@ -311,6 +349,4 @@ $(document).ready(function() {
dropdownParent: $('#addStockModal')
});
});
</script>
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
</script>

View File

@ -0,0 +1,431 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0 text-primary fw-bold"><i class="bi bi-receipt-cutoff me-2"></i> <?php echo __('lpos'); ?></h4>
<button type="button" class="btn btn-primary" onclick="openCreateLPOModal()">
<i class="bi bi-plus-lg me-2"></i> <?php echo __('create_lpo'); ?>
</button>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle" id="lpoTable">
<thead class="table-light">
<tr>
<th>#</th>
<th><?php echo __('date'); ?></th>
<th><?php echo __('supplier'); ?></th>
<th><?php echo __('total_amount'); ?></th>
<th><?php echo __('status'); ?></th>
<th><?php echo __('actions'); ?></th>
</tr>
</thead>
<tbody id="lpoTableBody">
<tr><td colspan="6" class="text-center py-4"><div class="spinner-border text-primary" role="status"></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Create LPO Modal -->
<div class="modal fade" id="createLPOModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<form id="createLPOForm" onsubmit="submitLPO(event)">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="bi bi-plus-circle me-2"></i> <?php echo __('create_lpo'); ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body bg-light">
<div class="card mb-3 border-0 shadow-sm">
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-bold"><?php echo __('supplier'); ?></label>
<select name="supplier_id" id="lpo_supplier_id" class="form-select select2-modal" required>
<option value=""><?php echo __('select_supplier'); ?>...</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-bold"><?php echo __('lpo_date'); ?></label>
<input type="date" name="lpo_date" id="lpo_date" class="form-control" value="<?php echo date('Y-m-d'); ?>" required>
</div>
<div class="col-md-12">
<label class="form-label"><?php echo __('notes'); ?></label>
<textarea name="notes" id="lpo_notes" class="form-control" rows="2"></textarea>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-primary"><?php echo __('items'); ?></h6>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="addLPOItemRow()">
<i class="bi bi-plus-lg me-1"></i> <?php echo __('add_item'); ?>
</button>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-bordered mb-0" id="lpoItemsTable">
<thead class="table-light">
<tr>
<th style="width: 40%;"><?php echo __('drug_name'); ?></th>
<th style="width: 15%;"><?php echo __('quantity'); ?></th>
<th style="width: 15%;"><?php echo __('cost_price'); ?></th>
<th style="width: 20%;"><?php echo __('total_cost'); ?></th>
<th style="width: 10%;"></th>
</tr>
</thead>
<tbody id="lpoItemsBody">
<!-- Dynamic Rows -->
</tbody>
<tfoot class="table-light">
<tr>
<td colspan="3" class="text-end fw-bold"><?php echo __('total_amount'); ?>:</td>
<td class="fw-bold text-primary fs-5" id="lpoTotalDisplay">0.00</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-white">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
<button type="submit" class="btn btn-primary px-4"><i class="bi bi-save me-2"></i> <?php echo __('save'); ?></button>
</div>
</div>
</form>
</div>
</div>
<!-- View LPO Modal -->
<div class="modal fade" id="viewLPOModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="bi bi-eye me-2"></i> <?php echo __('view_lpo'); ?> #<span id="view_lpo_id"></span></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-4">
<div class="col-md-6">
<p class="mb-1 text-muted small"><?php echo __('supplier'); ?></p>
<h6 class="fw-bold" id="view_lpo_supplier"></h6>
</div>
<div class="col-md-6 text-end">
<p class="mb-1 text-muted small"><?php echo __('date'); ?></p>
<h6 class="fw-bold" id="view_lpo_date"></h6>
<span class="badge bg-secondary" id="view_lpo_status"></span>
</div>
</div>
<h6 class="border-bottom pb-2 mb-3 fw-bold text-primary"><?php echo __('items'); ?></h6>
<div class="table-responsive">
<table class="table table-bordered table-sm">
<thead class="table-light">
<tr>
<th><?php echo __('drug_name'); ?></th>
<th class="text-center"><?php echo __('quantity'); ?></th>
<th class="text-end"><?php echo __('cost_price'); ?></th>
<th class="text-end"><?php echo __('total_cost'); ?></th>
</tr>
</thead>
<tbody id="viewLpoItemsBody"></tbody>
<tfoot class="table-light">
<tr>
<td colspan="3" class="text-end fw-bold"><?php echo __('total_amount'); ?></td>
<td class="text-end fw-bold text-primary" id="view_lpo_total"></td>
</tr>
</tfoot>
</table>
</div>
<div class="mt-3">
<p class="mb-1 text-muted small"><?php echo __('notes'); ?></p>
<p class="bg-light p-2 rounded" id="view_lpo_notes"></p>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('close'); ?></button>
<!-- Add Print button if needed later -->
</div>
</div>
</div>
</div>
<script>
let allDrugs = [];
let allSuppliers = [];
document.addEventListener('DOMContentLoaded', function() {
loadLPOs();
fetchSuppliers();
fetchDrugs();
// Calculate total when inputs change in the table
document.getElementById('lpoItemsBody').addEventListener('input', function(e) {
if (e.target.classList.contains('lpo-qty') || e.target.classList.contains('lpo-price')) {
calculateRowTotal(e.target.closest('tr'));
calculateGrandTotal();
}
});
// Re-initialize Select2 when modal is shown
$('#createLPOModal').on('shown.bs.modal', function () {
$('.select2-modal').select2({
dropdownParent: $('#createLPOModal'),
width: '100%'
});
});
});
function loadLPOs() {
fetch('api/pharmacy_lpo.php?action=get_lpos')
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('lpoTableBody');
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>';
return;
}
data.forEach(lpo => {
const tr = document.createElement('tr');
let statusBadge = '';
switch(lpo.status) {
case 'Draft': statusBadge = '<span class="badge bg-secondary">Draft</span>'; break;
case 'Sent': statusBadge = '<span class="badge bg-primary">Sent</span>'; break;
case 'Received': statusBadge = '<span class="badge bg-success">Received</span>'; break;
case 'Cancelled': statusBadge = '<span class="badge bg-danger">Cancelled</span>'; break;
default: statusBadge = `<span class="badge bg-info">${lpo.status}</span>`;
}
tr.innerHTML = `
<td>${lpo.id}</td>
<td>${lpo.lpo_date}</td>
<td>${lpo.supplier_name || '-'}</td>
<td class="fw-bold text-success">$${parseFloat(lpo.total_amount).toFixed(2)}</td>
<td>${statusBadge}</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="viewLPO(${lpo.id}, '${lpo.supplier_name}', '${lpo.lpo_date}', '${lpo.total_amount}', '${lpo.status}', '${lpo.notes || ''}')">
<i class="bi bi-eye"></i>
</button>
${lpo.status === 'Draft' ? `<button class="btn btn-sm btn-outline-success ms-1" onclick="updateStatus(${lpo.id}, 'Sent')" title="Mark as Sent"><i class="bi bi-send"></i></button>` : ''}
${lpo.status === 'Sent' ? `<button class="btn btn-sm btn-outline-warning ms-1" onclick="updateStatus(${lpo.id}, 'Received')" title="Mark as Received"><i class="bi bi-box-seam"></i></button>` : ''}
</td>
`;
tbody.appendChild(tr);
});
})
.catch(err => console.error(err));
}
function fetchSuppliers() {
fetch('api/pharmacy_lpo.php?action=get_suppliers')
.then(response => response.json())
.then(data => {
allSuppliers = data;
const select = document.getElementById('lpo_supplier_id');
select.innerHTML = '<option value=""><?php echo __('select_supplier'); ?>...</option>';
data.forEach(s => {
select.innerHTML += `<option value="${s.id}">${s.name_en} / ${s.name_ar}</option>`;
});
});
}
function fetchDrugs() {
fetch('api/pharmacy_lpo.php?action=get_drugs')
.then(response => response.json())
.then(data => {
allDrugs = data;
});
}
function openCreateLPOModal() {
document.getElementById('createLPOForm').reset();
$('#lpo_supplier_id').val('').trigger('change');
document.getElementById('lpoItemsBody').innerHTML = '';
document.getElementById('lpoTotalDisplay').innerText = '0.00';
addLPOItemRow();
var modal = new bootstrap.Modal(document.getElementById('createLPOModal'));
modal.show();
}
function addLPOItemRow() {
const tbody = document.getElementById('lpoItemsBody');
const tr = document.createElement('tr');
let drugOptions = '<option value="">Select Drug...</option>';
allDrugs.forEach(d => {
drugOptions += `<option value="${d.id}" data-price="${d.price}">${d.name_en} (${d.sku || '-'})</option>`;
});
tr.innerHTML = `
<td>
<select name="items[drug_id][]" class="form-select select2-modal-row lpo-drug" onchange="drugSelected(this)" required>
${drugOptions}
</select>
</td>
<td><input type="number" name="items[quantity][]" class="form-control lpo-qty" min="1" value="1" required></td>
<td><input type="number" name="items[cost_price][]" class="form-control lpo-price" step="0.01" min="0" value="0.00" required></td>
<td><input type="text" class="form-control lpo-total" readonly value="0.00"></td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeLPORow(this)"><i class="bi bi-trash"></i></button>
</td>
`;
tbody.appendChild(tr);
// Initialize Select2 for the new row
$(tr).find('.select2-modal-row').select2({
dropdownParent: $('#createLPOModal'),
width: '100%'
});
}
function removeLPORow(btn) {
const tbody = document.getElementById('lpoItemsBody');
if (tbody.children.length > 1) {
btn.closest('tr').remove();
calculateGrandTotal();
}
}
function drugSelected(select) {
const option = select.options[select.selectedIndex];
const price = option.getAttribute('data-price') || 0;
const row = select.closest('tr');
// Pre-fill cost price if needed (using selling price as reference? usually cost is different, but let's prefill 0 or logic)
// Here we might not have cost price in drugs table, only selling price.
// So we leave it 0 or user enters it.
}
function calculateRowTotal(row) {
const qty = parseFloat(row.querySelector('.lpo-qty').value) || 0;
const price = parseFloat(row.querySelector('.lpo-price').value) || 0;
const total = qty * price;
row.querySelector('.lpo-total').value = total.toFixed(2);
}
function calculateGrandTotal() {
let total = 0;
document.querySelectorAll('.lpo-total').forEach(input => {
total += parseFloat(input.value) || 0;
});
document.getElementById('lpoTotalDisplay').innerText = total.toFixed(2);
}
function submitLPO(e) {
e.preventDefault();
const supplierId = document.getElementById('lpo_supplier_id').value;
const date = document.getElementById('lpo_date').value;
const notes = document.getElementById('lpo_notes').value;
const totalAmount = document.getElementById('lpoTotalDisplay').innerText;
const items = [];
document.querySelectorAll('#lpoItemsBody tr').forEach(row => {
const drugId = $(row).find('.lpo-drug').val();
const quantity = row.querySelector('.lpo-qty').value;
const costPrice = row.querySelector('.lpo-price').value;
const totalCost = row.querySelector('.lpo-total').value;
if (drugId) {
items.push({
drug_id: drugId,
quantity: quantity,
cost_price: costPrice,
total_cost: totalCost
});
}
});
if (items.length === 0) {
alert('Please add at least one item.');
return;
}
const payload = {
supplier_id: supplierId,
lpo_date: date,
notes: notes,
total_amount: totalAmount,
items: items
};
fetch('api/pharmacy_lpo.php?action=create_lpo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('createLPOModal')).hide();
loadLPOs();
} else {
alert('Error: ' + (data.error || 'Unknown error'));
}
})
.catch(err => {
console.error(err);
alert('Failed to create LPO');
});
}
function viewLPO(id, supplier, date, total, status, notes) {
document.getElementById('view_lpo_id').innerText = id;
document.getElementById('view_lpo_supplier').innerText = supplier;
document.getElementById('view_lpo_date').innerText = date;
document.getElementById('view_lpo_total').innerText = '$' + parseFloat(total).toFixed(2);
document.getElementById('view_lpo_status').innerText = status;
document.getElementById('view_lpo_notes').innerText = notes;
const tbody = document.getElementById('viewLpoItemsBody');
tbody.innerHTML = '<tr><td colspan="4" class="text-center">Loading...</td></tr>';
var modal = new bootstrap.Modal(document.getElementById('viewLPOModal'));
modal.show();
fetch('api/pharmacy_lpo.php?action=get_lpo_details&id=' + id)
.then(response => response.json())
.then(data => {
tbody.innerHTML = '';
data.forEach(item => {
tbody.innerHTML += `
<tr>
<td>${item.drug_name} <small class="text-muted">(${item.sku || '-'})</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">$${parseFloat(item.cost_price).toFixed(2)}</td>
<td class="text-end">$${parseFloat(item.total_cost).toFixed(2)}</td>
</tr>
`;
});
});
}
function updateStatus(id, newStatus) {
if (!confirm('Are you sure you want to update status to ' + newStatus + '?')) return;
fetch('api/pharmacy_lpo.php?action=update_status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: id, status: newStatus })
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadLPOs();
} else {
alert('Error updating status');
}
});
}
</script>

View File

@ -1,8 +1,3 @@
<?php
$section = 'pharmacy_pos';
require_once __DIR__ . '/../layout/header.php';
?>
<div class="container-fluid h-100">
<div class="row h-100">
<!-- Left Panel: Product Search -->
@ -11,15 +6,16 @@ require_once __DIR__ . '/../layout/header.php';
<div class="card-header bg-white py-3">
<div class="input-group input-group-lg">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search"></i></span>
<input type="text" id="drugSearch" class="form-control border-start-0" placeholder="<?php echo __('search_by_name'); ?>..." autocomplete="off">
<input type="text" id="drugSearch" class="form-control border-start-0" placeholder="<?php echo __('search_by_name'); ?> / SKU..." autocomplete="off">
</div>
</div>
<div class="card-body p-0 overflow-auto" style="max-height: calc(100vh - 200px);">
<div id="drugList" class="list-group list-group-flush">
<!-- Populated by JS -->
<div class="text-center py-5 text-muted">
<i class="bi bi-search fs-1"></i>
<p><?php echo __('search_to_add_items'); ?></p>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
@ -127,53 +123,71 @@ const cartTotalEl = document.getElementById('cartTotal');
const cartCountEl = document.getElementById('cartCount');
const checkoutTotalEl = document.getElementById('checkoutTotal');
function fetchDrugs(query = '') {
// Show loading if query is manual, or just keep calm
if (query.length > 0) {
// Optional: show loading indicator
}
fetch('api/pharmacy.php?action=search_drugs&q=' + encodeURIComponent(query))
.then(r => r.json())
.then(data => {
drugList.innerHTML = '';
if (data.length === 0) {
drugList.innerHTML = '<div class="list-group-item text-center text-muted"><?php echo __('no_drugs_found'); ?></div>';
return;
}
data.forEach(drug => {
const stock = parseFloat(drug.stock);
// Use batch price if available (current active price), otherwise default
const price = parseFloat(drug.batch_price || drug.default_price || 0);
const isOutOfStock = stock <= 0;
const item = document.createElement('a');
item.className = `list-group-item list-group-item-action d-flex justify-content-between align-items-center ${isOutOfStock ? 'disabled bg-light' : ''}`;
let skuHtml = drug.sku ? `<span class="badge bg-secondary me-2">${drug.sku}</span>` : '';
item.innerHTML = `
<div>
<div class="fw-bold">${drug.name_en}</div>
<small class="text-muted">${skuHtml}${drug.name_ar || ''}</small>
</div>
<div class="text-end">
<div class="fw-bold text-primary">${price.toFixed(2)}</div>
<small class="${isOutOfStock ? 'text-danger' : 'text-success'}">
${isOutOfStock ? '<?php echo __('out_of_stock'); ?>' : '<?php echo __('stock'); ?>: ' + stock}
</small>
</div>
`;
if (!isOutOfStock) {
item.onclick = () => addToCart(drug);
item.style.cursor = 'pointer';
}
drugList.appendChild(item);
});
})
.catch(err => {
console.error(err);
drugList.innerHTML = '<div class="list-group-item text-center text-danger">Error loading items</div>';
});
}
// Initial Load
document.addEventListener('DOMContentLoaded', () => {
fetchDrugs();
renderCart(); // Initialize empty cart view
});
// Search Logic
searchInput.addEventListener('input', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const query = this.value.trim();
if (query.length < 2) {
drugList.innerHTML = '<div class="text-center py-5 text-muted"><i class="bi bi-search fs-1"></i><p><?php echo __('search_to_add_items'); ?></p></div>';
return;
}
fetch('api/pharmacy.php?action=search_drugs&q=' + encodeURIComponent(query))
.then(r => r.json())
.then(data => {
drugList.innerHTML = '';
if (data.length === 0) {
drugList.innerHTML = '<div class="list-group-item text-center text-muted"><?php echo __('no_drugs_found'); ?></div>';
return;
}
data.forEach(drug => {
const stock = parseFloat(drug.stock);
const price = parseFloat(drug.default_price || 0);
const isOutOfStock = stock <= 0;
const item = document.createElement('a');
item.className = `list-group-item list-group-item-action d-flex justify-content-between align-items-center ${isOutOfStock ? 'disabled bg-light' : ''}`;
item.innerHTML = `
<div>
<div class="fw-bold">${drug.name_en}</div>
<small class="text-muted">${drug.name_ar || ''}</small>
</div>
<div class="text-end">
<div class="fw-bold text-primary">${price.toFixed(2)}</div>
<small class="${isOutOfStock ? 'text-danger' : 'text-success'}">
${isOutOfStock ? '<?php echo __('out_of_stock'); ?>' : '<?php echo __('stock'); ?>: ' + stock}
</small>
</div>
`;
if (!isOutOfStock) {
item.onclick = () => addToCart(drug);
item.style.cursor = 'pointer';
}
drugList.appendChild(item);
});
});
fetchDrugs(query);
}, 300);
});
@ -190,7 +204,8 @@ function addToCart(drug) {
cart.push({
id: drug.id,
name: drug.name_en,
price: parseFloat(drug.default_price || 0),
// Use batch price if available
price: parseFloat(drug.batch_price || drug.default_price || 0),
quantity: 1,
max_stock: parseFloat(drug.stock)
});
@ -292,7 +307,8 @@ function processSale() {
cart = [];
renderCart();
bootstrap.Modal.getInstance(document.getElementById('checkoutModal')).hide();
// TODO: Open receipt
// Open receipt in new tab
window.open('print_pharmacy_receipt.php?sale_id=' + data.sale_id, '_blank');
} else {
alert(data.error || 'Transaction failed');
}
@ -323,6 +339,4 @@ $(document).ready(function() {
minimumInputLength: 1
});
});
</script>
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
</script>

View File

@ -1,7 +1,4 @@
<?php
$section = 'pharmacy_sales';
require_once __DIR__ . '/../layout/header.php';
$page = $_GET['page'] ?? 1;
$limit = 20;
$offset = ($page - 1) * $limit;
@ -12,7 +9,7 @@ $total_rows = $count_stmt->fetchColumn();
$total_pages = ceil($total_rows / $limit);
// Fetch Sales
$sql = "SELECT s.*, p.name_en as patient_name
$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
@ -130,6 +127,7 @@ $sales = $stmt->fetchAll(PDO::FETCH_ASSOC);
<!-- Content via JS -->
</div>
<div class="modal-footer">
<a href="#" id="formalReceiptBtn" target="_blank" class="btn btn-outline-primary"><i class="bi bi-file-earmark-text"></i> <?php echo __('formal_receipt'); ?></a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('close'); ?></button>
<button type="button" class="btn btn-primary" onclick="printReceiptContent()"><i class="bi bi-printer"></i> <?php echo __('print'); ?></button>
</div>
@ -141,6 +139,7 @@ $sales = $stmt->fetchAll(PDO::FETCH_ASSOC);
function viewReceipt(saleId) {
const modal = new bootstrap.Modal(document.getElementById('receiptModal'));
const body = document.getElementById('receiptBody');
document.getElementById('formalReceiptBtn').href = 'print_pharmacy_receipt.php?sale_id=' + saleId;
body.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
modal.show();
@ -212,6 +211,4 @@ function printReceiptContent() {
win.focus();
setTimeout(() => { win.print(); win.close(); }, 500);
}
</script>
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
</script>

View File

@ -347,7 +347,24 @@ $translations = [
'new_sale' => 'New Sale',
'add_to_cart' => 'Add to Cart',
'cart' => 'Cart',
'insufficient_stock' => 'Insufficient Stock'
'insufficient_stock' => 'Insufficient Stock',
'out_of_stock' => 'Out of Stock',
'low_stock' => 'Low Stock',
'in_stock' => 'In Stock',
'CheckIn' => 'Check In',
'In Progress' => 'In Progress',
'in_progress' => 'In Progress',
'refunded' => 'Refunded',
'formal_receipt' => 'Formal Receipt',
'lpo' => 'LPO',
'lpos' => 'LPOs',
'create_lpo' => 'Create LPO',
'view_lpo' => 'View LPO',
'lpo_date' => 'LPO Date',
'total_cost' => 'Total Cost',
'draft' => 'Draft',
'sent' => 'Sent',
'received' => 'Received'
],
'ar' => [
'attachment' => 'المرفق',
@ -699,6 +716,25 @@ $translations = [
'new_sale' => 'بيع جديد',
'add_to_cart' => 'إضافة إلى السلة',
'cart' => 'السلة',
'insufficient_stock' => 'المخزون غير كاف'
'insufficient_stock' => 'المخزون غير كاف',
'out_of_stock' => 'نفذت الكمية',
'low_stock' => 'كمية منخفضة',
'in_stock' => 'متوفر',
'waiting' => 'قيد الانتظار',
'serving' => 'جاري الخدمة',
'CheckIn' => 'تسجيل دخول',
'In Progress' => 'قيد الإجراء',
'in_progress' => 'قيد الإجراء',
'refunded' => 'تم الاسترجاع',
'formal_receipt' => 'إيصال رسمي',
'lpo' => 'أمر شراء',
'lpos' => 'أوامر الشراء',
'create_lpo' => 'إنشاء أمر شراء',
'view_lpo' => 'عرض أمر الشراء',
'lpo_date' => 'تاريخ الأمر',
'total_cost' => 'التكلفة الإجمالية',
'draft' => 'مسودة',
'sent' => 'تم الإرسال',
'received' => 'تم الاستلام'
]
];
];

22
pharmacy_inventory.php Normal file
View File

@ -0,0 +1,22 @@
<?php
$section = 'pharmacy_inventory';
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/helpers.php';
$db = db();
$lang = $_SESSION['lang'];
require_once __DIR__ . '/includes/actions.php';
require_once __DIR__ . '/includes/common_data.php';
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/header.php';
}
require_once __DIR__ . '/includes/pages/pharmacy_inventory.php';
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/footer.php';
}

20
pharmacy_lpos.php Normal file
View File

@ -0,0 +1,20 @@
<?php
$section = 'pharmacy_lpos';
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/helpers.php';
$db = db();
$lang = $_SESSION['lang'] ?? 'en';
require_once __DIR__ . '/includes/actions.php';
require_once __DIR__ . '/includes/common_data.php';
if (!isset($_GET['ajax_search'])) {
require_once __DIR__ . '/includes/layout/header.php';
}
require_once __DIR__ . '/includes/pages/pharmacy_lpos.php';
if (!isset($_GET['ajax_search'])) {
require_once __DIR__ . '/includes/layout/footer.php';
}

22
pharmacy_pos.php Normal file
View File

@ -0,0 +1,22 @@
<?php
$section = 'pharmacy_pos';
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/helpers.php';
$db = db();
$lang = $_SESSION['lang'];
require_once __DIR__ . '/includes/actions.php';
require_once __DIR__ . '/includes/common_data.php';
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/header.php';
}
require_once __DIR__ . '/includes/pages/pharmacy_pos.php';
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/footer.php';
}

22
pharmacy_sales.php Normal file
View File

@ -0,0 +1,22 @@
<?php
$section = 'pharmacy_sales';
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/helpers.php';
$db = db();
$lang = $_SESSION['lang'];
require_once __DIR__ . '/includes/actions.php';
require_once __DIR__ . '/includes/common_data.php';
$is_ajax = isset($_GET['ajax_search']) || isset($_POST['ajax_search']);
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/header.php';
}
require_once __DIR__ . '/includes/pages/pharmacy_sales.php';
if (!$is_ajax) {
require_once __DIR__ . '/includes/layout/footer.php';
}

225
print_pharmacy_receipt.php Normal file
View File

@ -0,0 +1,225 @@
<?php
require 'db/config.php';
require 'helpers.php';
// Enable error reporting for debugging
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
try {
$db = db();
$sale_id = $_GET['sale_id'] ?? 0;
if (!$sale_id) {
throw new Exception("Invalid Sale ID");
}
// Fetch Sale Details
$stmt = $db->prepare("
SELECT
s.*,
p.name as patient_name,
p.phone as patient_phone,
p.civil_id
FROM pharmacy_sales s
LEFT JOIN patients p ON s.patient_id = p.id
WHERE s.id = ?
");
$stmt->execute([$sale_id]);
$sale = $stmt->fetch();
if (!$sale) {
throw new Exception("Sale not found");
}
// Fetch Sale Items
$stmt = $db->prepare("
SELECT
i.*,
d.name_en as drug_name_en,
d.name_ar as drug_name_ar,
d.sku
FROM pharmacy_sale_items i
JOIN drugs d ON i.drug_id = d.id
WHERE i.sale_id = ?
");
$stmt->execute([$sale_id]);
$items = $stmt->fetchAll();
// Fetch Company Settings (Logo, Address, etc.)
$stmt = $db->query("SELECT * FROM settings WHERE id = 1");
$settings = $stmt->fetch();
} catch (Exception $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Receipt #<?php echo $sale_id; ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #333; }
.receipt-header { border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 20px; }
.receipt-footer { border-top: 1px dashed #333; padding-top: 20px; margin-top: 30px; }
.company-logo { max-height: 80px; }
.receipt-title { font-size: 1.8rem; font-weight: bold; text-transform: uppercase; }
.table thead th { border-bottom: 2px solid #333; background-color: #f8f9fa; font-size: 0.9rem; }
.table-bordered td, .table-bordered th { border-color: #dee2e6; }
.bilingual { display: flex; flex-direction: column; line-height: 1.2; }
.text-ar { font-family: Tahoma, sans-serif; font-size: 0.9em; direction: rtl; }
@media print {
.no-print { display: none !important; }
body { padding: 0; margin: 0; background: white; }
.container { max-width: 100%; width: 100%; padding: 0; }
.card { border: none !important; box-shadow: none !important; }
}
</style>
</head>
<body onload="window.print()">
<div class="container my-4" style="max-width: 800px;">
<div class="no-print mb-4 text-end">
<button onclick="window.print()" class="btn btn-primary"><i class="bi bi-printer"></i> Print</button>
<button onclick="window.close()" class="btn btn-secondary">Close</button>
</div>
<div class="card p-4">
<!-- Header -->
<div class="receipt-header">
<div class="row align-items-center">
<div class="col-8">
<div class="d-flex align-items-center">
<?php if (!empty($settings['company_logo'])): ?>
<img src="<?php echo htmlspecialchars($settings['company_logo']); ?>" alt="Logo" class="company-logo me-3">
<?php endif; ?>
<div>
<h4 class="fw-bold m-0"><?php echo htmlspecialchars($settings['company_name'] ?? 'Pharmacy Name'); ?></h4>
<div class="small text-muted mt-1">
<?php echo htmlspecialchars($settings['company_address'] ?? ''); ?><br>
<?php echo htmlspecialchars($settings['company_phone'] ?? ''); ?>
</div>
</div>
</div>
</div>
<div class="col-4 text-end">
<div class="receipt-title">RECEIPT</div>
<div class="text-ar">إيصال استلام</div>
<div class="mt-2">
<strong>#<?php echo str_pad($sale_id, 6, '0', STR_PAD_LEFT); ?></strong>
</div>
<div class="small text-muted">
<?php echo date('d/m/Y h:i A', strtotime($sale['created_at'])); ?>
</div>
</div>
</div>
</div>
<!-- Patient Info (if applicable) -->
<?php if ($sale['patient_id']): ?>
<div class="row mb-4 border-bottom pb-3">
<div class="col-12">
<span class="fw-bold">Patient / المريض:</span>
<?php echo htmlspecialchars($sale['patient_name']); ?>
<?php if($sale['patient_phone']) echo ' (' . htmlspecialchars($sale['patient_phone']) . ')'; ?>
</div>
</div>
<?php endif; ?>
<!-- Items Table -->
<table class="table table-bordered mb-4">
<thead>
<tr>
<th class="text-center" width="50">#</th>
<th>
<div class="d-flex justify-content-between">
<span>Item Description</span>
<span class="text-ar">الصنف</span>
</div>
</th>
<th class="text-center" width="80">
<div class="d-flex justify-content-between px-1">
<span>Qty</span>
<span class="text-ar">العدد</span>
</div>
</th>
<th class="text-end" width="120">
<div class="d-flex justify-content-between px-1">
<span>Price</span>
<span class="text-ar">السعر</span>
</div>
</th>
<th class="text-end" width="120">
<div class="d-flex justify-content-between px-1">
<span>Total</span>
<span class="text-ar">الإجمالي</span>
</div>
</th>
</tr>
</thead>
<tbody>
<?php $i = 1; foreach ($items as $item): ?>
<tr>
<td class="text-center"><?php echo $i++; ?></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($item['drug_name_en']); ?></div>
<?php if($item['drug_name_ar']): ?>
<div class="text-ar small text-muted"><?php echo htmlspecialchars($item['drug_name_ar']); ?></div>
<?php endif; ?>
<?php if($item['sku']): ?>
<div class="small text-muted" style="font-size: 0.75rem;">SKU: <?php echo htmlspecialchars($item['sku']); ?></div>
<?php endif; ?>
</td>
<td class="text-center"><?php echo $item['quantity']; ?></td>
<td class="text-end"><?php echo number_format($item['unit_price'], 2); ?></td>
<td class="text-end"><?php echo number_format($item['total_price'], 2); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold">
TOTAL / الإجمالي
</td>
<td class="text-end fw-bold fs-5">
<?php echo number_format($sale['total_amount'], 2); ?>
</td>
</tr>
</tfoot>
</table>
<!-- Payment Info -->
<div class="row">
<div class="col-6">
<div class="small">
<strong>Payment Method / طريقة الدفع:</strong><br>
<span class="text-uppercase"><?php echo htmlspecialchars($sale['payment_method']); ?></span>
</div>
</div>
<div class="col-6 text-end">
<!--
<br>
<div class="border-top border-dark d-inline-block pt-1" style="width: 200px; text-align: center;">
<span class="small">Signature / التوقيع</span>
</div>
-->
</div>
</div>
<!-- Footer -->
<div class="receipt-footer text-center small text-muted">
<p class="mb-1">Thank you for your visit! / !شكراً لزيارتكم</p>
<p>Get Well Soon / تمنياتنا بالشفاء العاجل</p>
</div>
</div>
</div>
</body>
</html>