Reporting and batch management
This commit is contained in:
+79
-2
@@ -5,6 +5,7 @@ let currentUser = null;
|
||||
let allDrugs = [];
|
||||
let auditTrailRows = [];
|
||||
let dispensingRows = [];
|
||||
let globalInventoryRows = [];
|
||||
let batchAttentionRows = [];
|
||||
let activeReportType = 'dispensing';
|
||||
const batchLookupById = new Map();
|
||||
@@ -200,24 +201,28 @@ function detailsContainsText(details, searchText) {
|
||||
|
||||
function getActiveRows() {
|
||||
if (activeReportType === 'dispensing') return dispensingRows;
|
||||
if (activeReportType === 'global_inventory') return globalInventoryRows;
|
||||
if (activeReportType === 'batch_attention') return batchAttentionRows;
|
||||
return auditTrailRows;
|
||||
}
|
||||
|
||||
function getRowUser(row) {
|
||||
if (activeReportType === 'dispensing') return row.user_name || 'unknown';
|
||||
if (activeReportType === 'global_inventory') return '';
|
||||
if (activeReportType === 'batch_attention') return '';
|
||||
return row.actor_username || 'system';
|
||||
}
|
||||
|
||||
function getRowDrug(row) {
|
||||
if (activeReportType === 'dispensing') return extractDrugLabelFromDispenseRow(row);
|
||||
if (activeReportType === 'global_inventory') return `${row.drug_name || 'Unknown Drug'}${row.strength ? ` ${row.strength}` : ''}`;
|
||||
if (activeReportType === 'batch_attention') return `${row.drug_name || 'Unknown Drug'}${row.strength ? ` ${row.strength}` : ''}`;
|
||||
return extractDrugLabelFromAuditRow(row);
|
||||
}
|
||||
|
||||
function getRowDate(row) {
|
||||
if (activeReportType === 'dispensing') return new Date(row.dispensed_at);
|
||||
if (activeReportType === 'global_inventory') return row.expiry_date ? new Date(row.expiry_date) : null;
|
||||
if (activeReportType === 'batch_attention') return new Date(row.expiry_date);
|
||||
return new Date(row.created_at);
|
||||
}
|
||||
@@ -348,6 +353,50 @@ function renderDispensingTable(rows) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderGlobalInventoryTable(rows) {
|
||||
const container = document.getElementById('reportsTableContainer');
|
||||
if (!container) return;
|
||||
|
||||
if (!rows.length) {
|
||||
container.innerHTML = '<p class="empty" style="padding: 14px;">No inventory lines match the selected filters.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const rowsHtml = rows.map(row => {
|
||||
const expiryText = row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : '-';
|
||||
const quantityText = `${row.quantity} ${row.unit || 'units'}`;
|
||||
const batchText = row.inventory_source === 'legacy' ? 'Legacy stock' : (row.batch_number || '');
|
||||
const locationText = row.location_name || '-';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(row.drug_name || '')}</td>
|
||||
<td>${escapeHtml(row.strength || '-')}</td>
|
||||
<td>${escapeHtml(batchText)}</td>
|
||||
<td>${escapeHtml(quantityText)}</td>
|
||||
<td>${escapeHtml(locationText)}</td>
|
||||
<td>${escapeHtml(expiryText)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = `
|
||||
<table class="reports-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Drug</th>
|
||||
<th>Variant</th>
|
||||
<th>Batch</th>
|
||||
<th>Quantity</th>
|
||||
<th>Location</th>
|
||||
<th>Expiry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rowsHtml}</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderBatchAttentionTable(rows) {
|
||||
const container = document.getElementById('reportsTableContainer');
|
||||
if (!container) return;
|
||||
@@ -469,8 +518,8 @@ function applyCurrentFilters() {
|
||||
const userMatch = !selectedUser || getRowUser(row) === selectedUser;
|
||||
const drugMatch = !selectedDrug || getRowDrug(row) === selectedDrug;
|
||||
const rowDate = getRowDate(row);
|
||||
const fromMatch = !fromDate || rowDate >= fromDate;
|
||||
const toMatch = !toDate || rowDate <= toDate;
|
||||
const fromMatch = !fromDate || !rowDate || rowDate >= fromDate;
|
||||
const toMatch = !toDate || !rowDate || rowDate <= toDate;
|
||||
|
||||
let textMatch = true;
|
||||
if (searchText) {
|
||||
@@ -485,6 +534,16 @@ function applyCurrentFilters() {
|
||||
formatDispenseAllocation(row)
|
||||
].join(' ').toLowerCase();
|
||||
textMatch = haystack.includes(searchText);
|
||||
} else if (activeReportType === 'global_inventory') {
|
||||
const haystack = [
|
||||
row.drug_name || '',
|
||||
row.strength || '',
|
||||
row.batch_number || '',
|
||||
row.inventory_source || '',
|
||||
row.location_name || '',
|
||||
row.unit || ''
|
||||
].join(' ').toLowerCase();
|
||||
textMatch = haystack.includes(searchText);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
const haystack = [
|
||||
row.drug_name || '',
|
||||
@@ -512,6 +571,8 @@ function applyCurrentFilters() {
|
||||
if (reportsSummary) {
|
||||
const reportName = activeReportType === 'dispensing'
|
||||
? 'dispensing records'
|
||||
: activeReportType === 'global_inventory'
|
||||
? 'inventory lines'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'expired batches'
|
||||
: 'audit events';
|
||||
@@ -520,6 +581,8 @@ function applyCurrentFilters() {
|
||||
|
||||
if (activeReportType === 'dispensing') {
|
||||
renderDispensingTable(filteredRows);
|
||||
} else if (activeReportType === 'global_inventory') {
|
||||
renderGlobalInventoryTable(filteredRows);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
renderBatchAttentionTable(filteredRows);
|
||||
} else {
|
||||
@@ -537,6 +600,10 @@ function updateReportHeading() {
|
||||
heading.textContent = 'Dispensing History';
|
||||
searchInput.placeholder = 'Search user, drug, animal, notes, batch allocation...';
|
||||
if (userFilter) userFilter.style.display = '';
|
||||
} else if (activeReportType === 'global_inventory') {
|
||||
heading.textContent = 'Global Inventory';
|
||||
searchInput.placeholder = 'Search drug, variant, batch, location...';
|
||||
if (userFilter) userFilter.style.display = 'none';
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
heading.textContent = 'Expired Batches';
|
||||
searchInput.placeholder = 'Search drug, batch, location...';
|
||||
@@ -587,6 +654,8 @@ async function loadActiveReport() {
|
||||
if (container) {
|
||||
const loadingText = activeReportType === 'dispensing'
|
||||
? 'Loading dispensing history...'
|
||||
: activeReportType === 'global_inventory'
|
||||
? 'Loading global inventory...'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'Loading expired batches...'
|
||||
: 'Loading audit trail...';
|
||||
@@ -604,6 +673,14 @@ async function loadActiveReport() {
|
||||
dispensingRows = await response.json();
|
||||
await ensureBatchLookupForDispensing(dispensingRows);
|
||||
populateCommonFilters(dispensingRows);
|
||||
} else if (activeReportType === 'global_inventory') {
|
||||
const response = await apiCall('/reports/global-inventory');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to load global inventory');
|
||||
}
|
||||
globalInventoryRows = await response.json();
|
||||
populateCommonFilters(globalInventoryRows);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
const response = await apiCall('/reports/batch-attention');
|
||||
if (!response.ok) {
|
||||
|
||||
Reference in New Issue
Block a user