342 lines
14 KiB
PHP
342 lines
14 KiB
PHP
<div class="container-fluid h-100">
|
|
<div class="row h-100">
|
|
<!-- Left Panel: Product Search -->
|
|
<div class="col-md-7 d-flex flex-column h-100">
|
|
<div class="card shadow-sm flex-grow-1">
|
|
<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'); ?> / 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">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Panel: Cart -->
|
|
<div class="col-md-5 d-flex flex-column h-100">
|
|
<div class="card shadow-sm flex-grow-1 border-primary">
|
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="bi bi-cart3 me-2"></i> <?php echo __('cart'); ?></h5>
|
|
<span id="cartCount" class="badge bg-white text-primary rounded-pill">0</span>
|
|
</div>
|
|
<div class="card-body p-0 d-flex flex-column">
|
|
<div class="table-responsive flex-grow-1" style="max-height: calc(100vh - 350px); overflow-y: auto;">
|
|
<table class="table table-striped mb-0">
|
|
<thead class="sticky-top bg-light">
|
|
<tr>
|
|
<th><?php echo __('item'); ?></th>
|
|
<th width="80"><?php echo __('qty'); ?></th>
|
|
<th width="100" class="text-end"><?php echo __('total'); ?></th>
|
|
<th width="50"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="cartTableBody">
|
|
<!-- Cart Items -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="p-3 bg-light border-top">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span class="fs-5"><?php echo __('total'); ?>:</span>
|
|
<span class="fs-4 fw-bold text-primary" id="cartTotal">0.00</span>
|
|
</div>
|
|
<button class="btn btn-success w-100 btn-lg" onclick="showCheckout()">
|
|
<i class="bi bi-credit-card me-2"></i> <?php echo __('checkout'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Checkout Modal -->
|
|
<div class="modal fade" id="checkoutModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><?php echo __('checkout'); ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="checkoutForm">
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('patient'); ?> (<?php echo __('optional'); ?>)</label>
|
|
<select class="form-select select2-patient" id="patientSelect" style="width: 100%;">
|
|
<option value=""><?php echo __('select_patient'); ?></option>
|
|
<!-- Populated via AJAX or PHP if small list -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Visit ID (Optional linkage) -->
|
|
<input type="hidden" id="visitSelect" value="">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('payment_method'); ?></label>
|
|
<div class="btn-group w-100" role="group">
|
|
<input type="radio" class="btn-check" name="payment_method" id="pay_cash" value="cash" checked>
|
|
<label class="btn btn-outline-primary" for="pay_cash"><i class="bi bi-cash me-1"></i> <?php echo __('cash'); ?></label>
|
|
|
|
<input type="radio" class="btn-check" name="payment_method" id="pay_card" value="card">
|
|
<label class="btn btn-outline-primary" for="pay_card"><i class="bi bi-credit-card me-1"></i> <?php echo __('card'); ?></label>
|
|
|
|
<input type="radio" class="btn-check" name="payment_method" id="pay_insurance" value="insurance">
|
|
<label class="btn btn-outline-primary" for="pay_insurance"><i class="bi bi-shield-check me-1"></i> <?php echo __('insurance'); ?></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info d-flex justify-content-between">
|
|
<span><?php echo __('total_amount'); ?>:</span>
|
|
<strong id="checkoutTotal">0.00</strong>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
|
<button type="button" class="btn btn-primary" onclick="processSale()">
|
|
<i class="bi bi-check-lg me-1"></i> <?php echo __('confirm_payment'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let cart = [];
|
|
let debounceTimer;
|
|
|
|
const searchInput = document.getElementById('drugSearch');
|
|
const drugList = document.getElementById('drugList');
|
|
const cartTableBody = document.getElementById('cartTableBody');
|
|
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();
|
|
fetchDrugs(query);
|
|
}, 300);
|
|
});
|
|
|
|
// Cart Logic
|
|
function addToCart(drug) {
|
|
const existing = cart.find(i => i.id === drug.id);
|
|
if (existing) {
|
|
if (existing.quantity >= drug.stock) {
|
|
alert('<?php echo __('insufficient_stock'); ?>');
|
|
return;
|
|
}
|
|
existing.quantity++;
|
|
} else {
|
|
cart.push({
|
|
id: drug.id,
|
|
name: drug.name_en,
|
|
// Use batch price if available
|
|
price: parseFloat(drug.batch_price || drug.default_price || 0),
|
|
quantity: 1,
|
|
max_stock: parseFloat(drug.stock)
|
|
});
|
|
}
|
|
renderCart();
|
|
}
|
|
|
|
function updateQty(id, change) {
|
|
const item = cart.find(i => i.id === id);
|
|
if (!item) return;
|
|
|
|
const newQty = item.quantity + change;
|
|
if (newQty > item.max_stock) {
|
|
alert('<?php echo __('insufficient_stock'); ?>');
|
|
return;
|
|
}
|
|
|
|
if (newQty <= 0) {
|
|
cart = cart.filter(i => i.id !== id);
|
|
} else {
|
|
item.quantity = newQty;
|
|
}
|
|
renderCart();
|
|
}
|
|
|
|
function renderCart() {
|
|
cartTableBody.innerHTML = '';
|
|
let total = 0;
|
|
|
|
cart.forEach(item => {
|
|
const itemTotal = item.quantity * item.price;
|
|
total += itemTotal;
|
|
|
|
cartTableBody.innerHTML += `
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold text-truncate" style="max-width: 180px;">${item.name}</div>
|
|
<small class="text-muted">${item.price.toFixed(2)}</small>
|
|
</td>
|
|
<td>
|
|
<div class="input-group input-group-sm">
|
|
<button class="btn btn-outline-secondary" onclick="updateQty(${item.id}, -1)">-</button>
|
|
<input type="text" class="form-control text-center p-0" value="${item.quantity}" readonly>
|
|
<button class="btn btn-outline-secondary" onclick="updateQty(${item.id}, 1)">+</button>
|
|
</div>
|
|
</td>
|
|
<td class="text-end fw-bold">${itemTotal.toFixed(2)}</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-link text-danger p-0" onclick="updateQty(${item.id}, -1000)"><i class="bi bi-trash"></i></button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
cartTotalEl.textContent = total.toFixed(2);
|
|
cartCountEl.textContent = cart.length;
|
|
checkoutTotalEl.textContent = total.toFixed(2);
|
|
|
|
if (cart.length === 0) {
|
|
cartTableBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted py-4"><?php echo __('cart_empty'); ?></td></tr>';
|
|
}
|
|
}
|
|
|
|
// Checkout Logic
|
|
function showCheckout() {
|
|
if (cart.length === 0) {
|
|
alert('<?php echo __('cart_is_empty'); ?>');
|
|
return;
|
|
}
|
|
const modal = new bootstrap.Modal(document.getElementById('checkoutModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function processSale() {
|
|
const patientId = $('#patientSelect').val();
|
|
const paymentMethod = document.querySelector('input[name="payment_method"]:checked').value;
|
|
|
|
const payload = {
|
|
patient_id: patientId || null,
|
|
visit_id: null,
|
|
payment_method: paymentMethod,
|
|
total_amount: parseFloat(cartTotalEl.textContent),
|
|
items: cart.map(i => ({
|
|
drug_id: i.id,
|
|
quantity: i.quantity,
|
|
price: i.price
|
|
}))
|
|
};
|
|
|
|
fetch('api/pharmacy.php?action=create_sale', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Sale completed!');
|
|
cart = [];
|
|
renderCart();
|
|
bootstrap.Modal.getInstance(document.getElementById('checkoutModal')).hide();
|
|
// Open receipt in new tab
|
|
window.open('print_pharmacy_receipt.php?sale_id=' + data.sale_id, '_blank');
|
|
} else {
|
|
alert(data.error || 'Transaction failed');
|
|
}
|
|
})
|
|
.catch(e => {
|
|
console.error(e);
|
|
alert('Network error');
|
|
});
|
|
}
|
|
|
|
// Initialize Patient Select2
|
|
$(document).ready(function() {
|
|
$('.select2-patient').select2({
|
|
theme: 'bootstrap-5',
|
|
dropdownParent: $('#checkoutModal'),
|
|
ajax: {
|
|
url: 'api/patients.php?action=search', // Need to check if this endpoint exists or similar
|
|
dataType: 'json',
|
|
delay: 250,
|
|
processResults: function (data) {
|
|
return {
|
|
results: data.map(p => ({id: p.id, text: p.name + ' (' + p.phone + ')'}))
|
|
};
|
|
},
|
|
cache: true
|
|
},
|
|
placeholder: '<?php echo __('search_patient'); ?>',
|
|
minimumInputLength: 1
|
|
});
|
|
});
|
|
</script>
|