38960-vm/includes/pages/pharmacy_lpos.php
2026-03-21 09:40:17 +00:00

493 lines
22 KiB
PHP

<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>
<!-- Pagination -->
<nav id="paginationContainer" class="mt-3" aria-label="Page navigation"></nav>
</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 = [];
let currentPage = 1;
document.addEventListener('DOMContentLoaded', function() {
loadLPOs(1);
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(page = 1) {
currentPage = page;
fetch('api/pharmacy_lpo.php?action=get_lpos&page=' + page)
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('lpoTableBody');
const paginationContainer = document.getElementById('paginationContainer');
tbody.innerHTML = '';
// Handle new response structure (paginated) vs old (array)
const lpos = data.data || (Array.isArray(data) ? data : []);
const totalPages = data.pages || 1;
if (lpos.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>';
paginationContainer.innerHTML = '';
return;
}
lpos.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);
});
renderPagination(currentPage, totalPages);
})
.catch(err => {
console.error(err);
document.getElementById('lpoTableBody').innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error loading data</td></tr>';
});
}
function renderPagination(current, total) {
const container = document.getElementById('paginationContainer');
if (total <= 1) {
container.innerHTML = '';
return;
}
let html = '<ul class="pagination justify-content-center">';
// Previous
html += `<li class="page-item ${current <= 1 ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="event.preventDefault(); loadLPOs(${current - 1})"><?php echo __('previous'); ?></a>
</li>`;
// Page Numbers (Show max 5 pages for simplicity)
let start = Math.max(1, current - 2);
let end = Math.min(total, start + 4);
if (end - start < 4) {
start = Math.max(1, end - 4);
}
if (start > 1) {
html += `<li class="page-item"><a class="page-link" href="#" onclick="event.preventDefault(); loadLPOs(1)">1</a></li>`;
if (start > 2) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
}
for (let i = start; i <= end; i++) {
html += `<li class="page-item ${current === i ? 'active' : ''}">
<a class="page-link" href="#" onclick="event.preventDefault(); loadLPOs(${i})">${i}</a>
</li>`;
}
if (end < total) {
if (end < total - 1) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
html += `<li class="page-item"><a class="page-link" href="#" onclick="event.preventDefault(); loadLPOs(${total})">${total}</a></li>`;
}
// Next
html += `<li class="page-item ${current >= total ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="event.preventDefault(); loadLPOs(${current + 1})"><?php echo __('next'); ?></a>
</li>`;
html += '</ul>';
container.innerHTML = html;
}
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>