349 lines
14 KiB
PHP
349 lines
14 KiB
PHP
<?php
|
|
$section = 'pharmacy_reports';
|
|
?>
|
|
|
|
<div class="container-fluid">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fw-bold text-dark mb-0">
|
|
<i class="bi bi-file-earmark-bar-graph me-2 text-primary"></i> <?php echo __('pharmacy_reports'); ?>
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<form id="reportForm" class="row g-3 align-items-end">
|
|
<div class="col-md-3">
|
|
<label for="reportType" class="form-label"><?php echo __('report_type'); ?></label>
|
|
<select class="form-select" id="reportType" name="type" required>
|
|
<option value="inventory_valuation"><?php echo __('inventory_valuation'); ?></option>
|
|
<option value="sales"><?php echo __('sales_report'); ?></option>
|
|
<option value="purchase_report"><?php echo __('purchase_report'); ?></option>
|
|
<option value="expiry"><?php echo __('expiry_report'); ?></option>
|
|
<option value="low_stock"><?php echo __('low_stock_report'); ?></option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3 date-range-group" style="display: none;">
|
|
<label for="startDate" class="form-label"><?php echo __('start_date'); ?></label>
|
|
<input type="date" class="form-control" id="startDate" name="start_date" value="<?php echo date('Y-m-01'); ?>">
|
|
</div>
|
|
<div class="col-md-3 date-range-group" style="display: none;">
|
|
<label for="endDate" class="form-label"><?php echo __('end_date'); ?></label>
|
|
<input type="date" class="form-control" id="endDate" name="end_date" value="<?php echo date('Y-m-d'); ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button type="button" class="btn btn-primary me-2" onclick="generateReport()">
|
|
<i class="bi bi-table"></i> <?php echo __('generate'); ?>
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="printReport()">
|
|
<i class="bi bi-printer"></i> <?php echo __('print'); ?>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm" id="reportResultCard" style="display: none;">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0" id="reportTitle"></h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-bordered" id="reportTable">
|
|
<thead class="table-light">
|
|
<tr id="tableHeaders"></tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
<tfoot id="tableFooter" class="table-light fw-bold"></tfoot>
|
|
</table>
|
|
</div>
|
|
<!-- Pagination Controls -->
|
|
<div id="paginationContainer" class="mt-3"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const reportType = document.getElementById('reportType');
|
|
const dateRangeGroups = document.querySelectorAll('.date-range-group');
|
|
|
|
function toggleDateInputs() {
|
|
const val = reportType.value;
|
|
if (val === 'sales' || val === 'expiry' || val === 'purchase_report') {
|
|
dateRangeGroups.forEach(el => el.style.display = 'block');
|
|
} else {
|
|
dateRangeGroups.forEach(el => el.style.display = 'none');
|
|
}
|
|
}
|
|
|
|
reportType.addEventListener('change', toggleDateInputs);
|
|
toggleDateInputs(); // Init
|
|
});
|
|
|
|
let currentPage = 1;
|
|
const limit = 20;
|
|
|
|
function generateReport(page = 1) {
|
|
currentPage = page;
|
|
const type = document.getElementById('reportType').value;
|
|
const startDate = document.getElementById('startDate').value;
|
|
const endDate = document.getElementById('endDate').value;
|
|
|
|
document.getElementById('reportResultCard').style.display = 'block';
|
|
document.getElementById('tableBody').innerHTML = '<tr><td colspan="10" class="text-center p-4"><div class="spinner-border text-primary" role="status"></div><p class="mt-2 mb-0"><?php echo __('loading'); ?>...</p></td></tr>';
|
|
|
|
const params = new URLSearchParams({
|
|
action: 'get_report',
|
|
type: type,
|
|
start_date: startDate,
|
|
end_date: endDate,
|
|
page: page,
|
|
limit: limit
|
|
});
|
|
|
|
fetch('api/pharmacy.php?' + params.toString())
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
renderTable(type, data);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('tableBody').innerHTML = `<tr><td colspan="10" class="text-center text-danger p-3">${error.message}</td></tr>`;
|
|
});
|
|
}
|
|
|
|
function renderTable(type, response) {
|
|
const headersRow = document.getElementById('tableHeaders');
|
|
const tbody = document.getElementById('tableBody');
|
|
const tfoot = document.getElementById('tableFooter');
|
|
|
|
headersRow.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
tfoot.innerHTML = '';
|
|
|
|
const data = response.data || [];
|
|
const total = response.total || 0;
|
|
|
|
if (data.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted py-4"><?php echo __('no_records_found'); ?></td></tr>';
|
|
document.getElementById('paginationContainer').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (type === 'inventory_valuation') {
|
|
headersRow.innerHTML = `
|
|
<th><?php echo __('drug_name'); ?></th>
|
|
<th><?php echo __('category'); ?></th>
|
|
<th><?php echo __('current_stock'); ?></th>
|
|
<th><?php echo __('avg_cost'); ?></th>
|
|
<th><?php echo __('selling_price'); ?></th>
|
|
<th><?php echo __('total_cost_value'); ?></th>
|
|
<th><?php echo __('total_sales_value'); ?></th>
|
|
`;
|
|
|
|
data.forEach(row => {
|
|
const costVal = parseFloat(row.total_cost_value || 0);
|
|
const salesVal = parseFloat(row.total_sales_value || 0);
|
|
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${row.drug_name || ''} <small class="text-muted">(${row.drug_name_ar || ''})</small></td>
|
|
<td>${row.category_name || '-'}</td>
|
|
<td>${row.stock_quantity || 0}</td>
|
|
<td>${parseFloat(row.avg_cost || 0).toFixed(2)}</td>
|
|
<td>${parseFloat(row.selling_price || 0).toFixed(2)}</td>
|
|
<td>${costVal.toFixed(2)}</td>
|
|
<td>${salesVal.toFixed(2)}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
if (response.grand_total_cost) {
|
|
tfoot.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="text-end"><?php echo __('grand_total'); ?>:</td>
|
|
<td>${parseFloat(response.grand_total_cost).toFixed(2)}</td>
|
|
<td>${parseFloat(response.grand_total_sales).toFixed(2)}</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
} else if (type === 'sales') {
|
|
headersRow.innerHTML = `
|
|
<th><?php echo __('date'); ?></th>
|
|
<th><?php echo __('receipt_no'); ?></th>
|
|
<th><?php echo __('patient'); ?></th>
|
|
<th><?php echo __('items'); ?></th>
|
|
<th><?php echo __('total_amount'); ?></th>
|
|
<th><?php echo __('payment_method'); ?></th>
|
|
`;
|
|
|
|
data.forEach(row => {
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${row.created_at}</td>
|
|
<td>#${row.id}</td>
|
|
<td>${row.patient_name || 'Guest'}</td>
|
|
<td>${row.item_count || 1}</td>
|
|
<td>${parseFloat(row.total_amount).toFixed(2)}</td>
|
|
<td>${row.payment_method || '-'}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
if (response.grand_total_sales) {
|
|
tfoot.innerHTML = `
|
|
<tr>
|
|
<td colspan="4" class="text-end"><?php echo __('grand_total'); ?>:</td>
|
|
<td colspan="2">${parseFloat(response.grand_total_sales).toFixed(2)}</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
} else if (type === 'purchase_report') {
|
|
headersRow.innerHTML = `
|
|
<th><?php echo __('date'); ?></th>
|
|
<th><?php echo __('lpo'); ?> #</th>
|
|
<th><?php echo __('supplier'); ?></th>
|
|
<th><?php echo __('status'); ?></th>
|
|
<th><?php echo __('total_amount'); ?></th>
|
|
`;
|
|
|
|
data.forEach(row => {
|
|
let statusBadge = '';
|
|
switch(row.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">${row.status}</span>`;
|
|
}
|
|
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${row.lpo_date}</td>
|
|
<td>#${row.id}</td>
|
|
<td>${row.supplier_name || '-'}</td>
|
|
<td>${statusBadge}</td>
|
|
<td>${parseFloat(row.total_amount).toFixed(2)}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
if (response.grand_total_purchases) {
|
|
tfoot.innerHTML = `
|
|
<tr>
|
|
<td colspan="4" class="text-end"><?php echo __('grand_total'); ?>:</td>
|
|
<td>${parseFloat(response.grand_total_purchases).toFixed(2)}</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
} else if (type === 'expiry') {
|
|
headersRow.innerHTML = `
|
|
<th><?php echo __('drug_name'); ?></th>
|
|
<th><?php echo __('batch_number'); ?></th>
|
|
<th><?php echo __('expiry_date'); ?></th>
|
|
<th><?php echo __('quantity'); ?></th>
|
|
<th><?php echo __('supplier'); ?></th>
|
|
<th><?php echo __('days_remaining'); ?></th>
|
|
`;
|
|
|
|
data.forEach(row => {
|
|
const days = parseInt(row.days_remaining);
|
|
const statusClass = days < 0 ? 'text-danger fw-bold' : (days < 90 ? 'text-warning fw-bold' : '');
|
|
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${row.drug_name}</td>
|
|
<td>${row.batch_number}</td>
|
|
<td>${row.expiry_date}</td>
|
|
<td>${row.quantity}</td>
|
|
<td>${row.supplier_name || '-'}</td>
|
|
<td class="${statusClass}">${days} <?php echo __('days'); ?></td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
} else if (type === 'low_stock') {
|
|
headersRow.innerHTML = `
|
|
<th><?php echo __('drug_name'); ?></th>
|
|
<th><?php echo __('current_stock'); ?></th>
|
|
<th><?php echo __('min_stock_level'); ?></th>
|
|
<th><?php echo __('reorder_level'); ?></th>
|
|
<th><?php echo __('unit'); ?></th>
|
|
<th><?php echo __('status'); ?></th>
|
|
`;
|
|
|
|
data.forEach(row => {
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${row.name_en}</td>
|
|
<td>${row.total_stock}</td>
|
|
<td>${row.min_stock_level || 0}</td>
|
|
<td>${row.reorder_level || 0}</td>
|
|
<td>${row.unit || '-'}</td>
|
|
<td><span class="badge bg-danger"><?php echo __('low_stock'); ?></span></td>
|
|
</tr>
|
|
`;
|
|
});
|
|
}
|
|
|
|
renderPagination(total, limit, currentPage);
|
|
}
|
|
|
|
function renderPagination(totalItems, itemsPerPage, currentPage) {
|
|
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
const paginationContainer = document.getElementById('paginationContainer');
|
|
paginationContainer.innerHTML = '';
|
|
|
|
if (totalPages <= 1) return;
|
|
|
|
let paginationHTML = '<nav aria-label="Page navigation"><ul class="pagination justify-content-center">';
|
|
|
|
// Previous Button
|
|
paginationHTML += `
|
|
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="generateReport(${currentPage - 1}); return false;" aria-label="Previous">
|
|
<span aria-hidden="true">«</span>
|
|
</a>
|
|
</li>
|
|
`;
|
|
|
|
// Page Numbers
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
|
|
paginationHTML += `
|
|
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
|
<a class="page-link" href="#" onclick="generateReport(${i}); return false;">${i}</a>
|
|
</li>
|
|
`;
|
|
} else if (i === currentPage - 3 || i === currentPage + 3) {
|
|
paginationHTML += '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
|
}
|
|
}
|
|
|
|
// Next Button
|
|
paginationHTML += `
|
|
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="generateReport(${currentPage + 1}); return false;" aria-label="Next">
|
|
<span aria-hidden="true">»</span>
|
|
</a>
|
|
</li>
|
|
`;
|
|
|
|
paginationHTML += '</ul></nav>';
|
|
paginationContainer.innerHTML = paginationHTML;
|
|
}
|
|
|
|
function printReport() {
|
|
const type = document.getElementById('reportType').value;
|
|
const startDate = document.getElementById('startDate').value;
|
|
const endDate = document.getElementById('endDate').value;
|
|
|
|
const url = `print_pharmacy_report.php?type=${type}&start_date=${startDate}&end_date=${endDate}`;
|
|
|
|
// Open in new window/tab
|
|
window.open(url, '_blank', 'width=1000,height=800');
|
|
}
|
|
</script>
|