updating pharmacy
This commit is contained in:
parent
641316f659
commit
bab61e3570
@ -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;
|
||||
|
||||
@ -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
93
api/pharmacy_lpo.php
Normal 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()]);
|
||||
}
|
||||
2
db/migrations/20260321_add_sku_to_drugs.sql
Normal file
2
db/migrations/20260321_add_sku_to_drugs.sql
Normal 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);
|
||||
21
db/migrations/20260321_create_pharmacy_lpo.sql
Normal file
21
db/migrations/20260321_create_pharmacy_lpo.sql
Normal 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;
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -312,5 +350,3 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
|
||||
|
||||
431
includes/pages/pharmacy_lpos.php
Normal file
431
includes/pages/pharmacy_lpos.php
Normal 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>
|
||||
@ -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');
|
||||
}
|
||||
@ -324,5 +340,3 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -213,5 +212,3 @@ function printReceiptContent() {
|
||||
setTimeout(() => { win.print(); win.close(); }, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/../layout/footer.php'; ?>
|
||||
40
lang.php
40
lang.php
@ -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
22
pharmacy_inventory.php
Normal 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
20
pharmacy_lpos.php
Normal 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
22
pharmacy_pos.php
Normal 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
22
pharmacy_sales.php
Normal 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
225
print_pharmacy_receipt.php
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user