Better dispensing
This commit is contained in:
+104
-7
@@ -5,6 +5,7 @@ let currentUser = null;
|
||||
let allDrugs = [];
|
||||
let auditTrailRows = [];
|
||||
let dispensingRows = [];
|
||||
let batchAttentionRows = [];
|
||||
let activeReportType = 'dispensing';
|
||||
const batchLookupById = new Map();
|
||||
const loadedBatchVariants = new Set();
|
||||
@@ -170,19 +171,27 @@ function detailsContainsText(details, searchText) {
|
||||
}
|
||||
|
||||
function getActiveRows() {
|
||||
return activeReportType === 'dispensing' ? dispensingRows : auditTrailRows;
|
||||
if (activeReportType === 'dispensing') return dispensingRows;
|
||||
if (activeReportType === 'batch_attention') return batchAttentionRows;
|
||||
return auditTrailRows;
|
||||
}
|
||||
|
||||
function getRowUser(row) {
|
||||
return activeReportType === 'dispensing' ? (row.user_name || 'unknown') : (row.actor_username || 'system');
|
||||
if (activeReportType === 'dispensing') return row.user_name || 'unknown';
|
||||
if (activeReportType === 'batch_attention') return '';
|
||||
return row.actor_username || 'system';
|
||||
}
|
||||
|
||||
function getRowDrug(row) {
|
||||
return activeReportType === 'dispensing' ? extractDrugLabelFromDispenseRow(row) : extractDrugLabelFromAuditRow(row);
|
||||
if (activeReportType === 'dispensing') return extractDrugLabelFromDispenseRow(row);
|
||||
if (activeReportType === 'batch_attention') return `${row.drug_name || 'Unknown Drug'}${row.strength ? ` ${row.strength}` : ''}`;
|
||||
return extractDrugLabelFromAuditRow(row);
|
||||
}
|
||||
|
||||
function getRowDate(row) {
|
||||
return new Date(activeReportType === 'dispensing' ? row.dispensed_at : row.created_at);
|
||||
if (activeReportType === 'dispensing') return new Date(row.dispensed_at);
|
||||
if (activeReportType === 'batch_attention') return new Date(row.expiry_date);
|
||||
return new Date(row.created_at);
|
||||
}
|
||||
|
||||
function populateCommonFilters(rows) {
|
||||
@@ -193,7 +202,7 @@ function populateCommonFilters(rows) {
|
||||
const previousUser = userFilter.value;
|
||||
const previousDrug = drugFilter.value;
|
||||
|
||||
const users = Array.from(new Set(rows.map(getRowUser))).sort((a, b) => a.localeCompare(b));
|
||||
const users = Array.from(new Set(rows.map(getRowUser).filter(Boolean))).sort((a, b) => a.localeCompare(b));
|
||||
const drugs = Array.from(new Set(rows.map(getRowDrug).filter(label => label && label !== 'N/A'))).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
userFilter.innerHTML = '<option value="">All Users</option>';
|
||||
@@ -311,6 +320,59 @@ function renderDispensingTable(rows) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderBatchAttentionTable(rows) {
|
||||
const container = document.getElementById('reportsTableContainer');
|
||||
if (!container) return;
|
||||
|
||||
if (!rows.length) {
|
||||
container.innerHTML = '<p class="empty" style="padding: 14px;">No expired or partial batches match the selected filters.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const rowsHtml = rows.map(row => {
|
||||
const expiryText = row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : 'Unknown';
|
||||
const quantityText = `${row.quantity} ${row.unit || 'units'}`;
|
||||
let statusText = 'Partial';
|
||||
if (row.status === 'expired') statusText = 'Expired';
|
||||
if (row.status === 'expired_partial') statusText = 'Expired + Partial';
|
||||
|
||||
const packState = row.current_loose_base_units > 0
|
||||
? `${row.current_full_pack_count || 0} full packs + ${row.current_loose_base_units} loose ${row.unit || 'units'}`
|
||||
: `${row.current_full_pack_count || 0} full packs`;
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(row.drug_name || '')}</td>
|
||||
<td>${escapeHtml(row.strength || '-')}</td>
|
||||
<td>${escapeHtml(row.batch_number || '')}</td>
|
||||
<td>${escapeHtml(quantityText)}</td>
|
||||
<td>${escapeHtml(packState)}</td>
|
||||
<td>${escapeHtml(row.location || '-')}</td>
|
||||
<td>${escapeHtml(expiryText)}</td>
|
||||
<td>${escapeHtml(statusText)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = `
|
||||
<table class="reports-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Drug</th>
|
||||
<th>Strength</th>
|
||||
<th>Batch</th>
|
||||
<th>Quantity</th>
|
||||
<th>Pack State</th>
|
||||
<th>Location</th>
|
||||
<th>Expiry</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rowsHtml}</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
function applyCurrentFilters() {
|
||||
const userFilter = document.getElementById('reportUserFilter');
|
||||
const drugFilter = document.getElementById('reportDrugFilter');
|
||||
@@ -346,6 +408,16 @@ function applyCurrentFilters() {
|
||||
formatDispenseAllocation(row)
|
||||
].join(' ').toLowerCase();
|
||||
textMatch = haystack.includes(searchText);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
const haystack = [
|
||||
row.drug_name || '',
|
||||
row.strength || '',
|
||||
row.batch_number || '',
|
||||
row.location || '',
|
||||
row.status || '',
|
||||
row.unit || ''
|
||||
].join(' ').toLowerCase();
|
||||
textMatch = haystack.includes(searchText);
|
||||
} else {
|
||||
const actionText = (row.action || '').toLowerCase();
|
||||
const entityText = (row.entity_type || '').toLowerCase();
|
||||
@@ -361,12 +433,18 @@ function applyCurrentFilters() {
|
||||
});
|
||||
|
||||
if (reportsSummary) {
|
||||
const reportName = activeReportType === 'dispensing' ? 'dispensing records' : 'audit events';
|
||||
const reportName = activeReportType === 'dispensing'
|
||||
? 'dispensing records'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'expired/partial batches'
|
||||
: 'audit events';
|
||||
reportsSummary.textContent = `Showing ${filteredRows.length} of ${sourceRows.length} ${reportName}`;
|
||||
}
|
||||
|
||||
if (activeReportType === 'dispensing') {
|
||||
renderDispensingTable(filteredRows);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
renderBatchAttentionTable(filteredRows);
|
||||
} else {
|
||||
renderAuditTable(filteredRows);
|
||||
}
|
||||
@@ -375,14 +453,21 @@ function applyCurrentFilters() {
|
||||
function updateReportHeading() {
|
||||
const heading = document.getElementById('reportsHeading');
|
||||
const searchInput = document.getElementById('reportActionSearch');
|
||||
const userFilter = document.getElementById('reportUserFilter')?.closest('.report-control');
|
||||
if (!heading || !searchInput) return;
|
||||
|
||||
if (activeReportType === 'dispensing') {
|
||||
heading.textContent = 'Dispensing History';
|
||||
searchInput.placeholder = 'Search user, drug, animal, notes, batch allocation...';
|
||||
if (userFilter) userFilter.style.display = '';
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
heading.textContent = 'Expired / Partial Batches';
|
||||
searchInput.placeholder = 'Search drug, batch, location, status...';
|
||||
if (userFilter) userFilter.style.display = 'none';
|
||||
} else {
|
||||
heading.textContent = 'Audit Trail (Raw)';
|
||||
searchInput.placeholder = 'Search action, entity, details...';
|
||||
if (userFilter) userFilter.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +508,11 @@ async function loadActiveReport() {
|
||||
const container = document.getElementById('reportsTableContainer');
|
||||
const reportsSummary = document.getElementById('reportsSummary');
|
||||
if (container) {
|
||||
const loadingText = activeReportType === 'dispensing' ? 'Loading dispensing history...' : 'Loading audit trail...';
|
||||
const loadingText = activeReportType === 'dispensing'
|
||||
? 'Loading dispensing history...'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'Loading expired / partial batches...'
|
||||
: 'Loading audit trail...';
|
||||
container.innerHTML = `<p class="loading" style="padding: 14px;">${loadingText}</p>`;
|
||||
}
|
||||
if (reportsSummary) reportsSummary.textContent = '';
|
||||
@@ -438,6 +527,14 @@ async function loadActiveReport() {
|
||||
dispensingRows = await response.json();
|
||||
await ensureBatchLookupForDispensing(dispensingRows);
|
||||
populateCommonFilters(dispensingRows);
|
||||
} else if (activeReportType === 'batch_attention') {
|
||||
const response = await apiCall('/reports/batch-attention');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to load batch attention report');
|
||||
}
|
||||
batchAttentionRows = await response.json();
|
||||
populateCommonFilters(batchAttentionRows);
|
||||
} else {
|
||||
const response = await apiCall('/reports/audit-trail');
|
||||
if (!response.ok) {
|
||||
|
||||
Reference in New Issue
Block a user