Batch disposal
This commit is contained in:
+110
-8
@@ -10,6 +10,34 @@ let activeReportType = 'dispensing';
|
||||
const batchLookupById = new Map();
|
||||
const loadedBatchVariants = new Set();
|
||||
|
||||
function openModal(modal) {
|
||||
if (!modal) return;
|
||||
modal.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
if (!modal) return;
|
||||
modal.classList.remove('show');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
function resetDisposeBatchModal() {
|
||||
const form = document.getElementById('disposeBatchForm');
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
const batchIdInput = document.getElementById('disposeBatchId');
|
||||
const batchNameInput = document.getElementById('disposeBatchName');
|
||||
if (batchIdInput) batchIdInput.value = '';
|
||||
if (batchNameInput) batchNameInput.value = '';
|
||||
}
|
||||
|
||||
function closeDisposeBatchModal() {
|
||||
resetDisposeBatchModal();
|
||||
closeModal(document.getElementById('disposeBatchModal'));
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info', duration = 3000) {
|
||||
const container = document.getElementById('toastContainer');
|
||||
if (!container) return;
|
||||
@@ -325,16 +353,15 @@ function renderBatchAttentionTable(rows) {
|
||||
if (!container) return;
|
||||
|
||||
if (!rows.length) {
|
||||
container.innerHTML = '<p class="empty" style="padding: 14px;">No expired or partial batches match the selected filters.</p>';
|
||||
container.innerHTML = '<p class="empty" style="padding: 14px;">No expired 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 statusText = 'Expired';
|
||||
const isExpired = true;
|
||||
|
||||
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'}`
|
||||
@@ -350,6 +377,7 @@ function renderBatchAttentionTable(rows) {
|
||||
<td>${escapeHtml(row.location || '-')}</td>
|
||||
<td>${escapeHtml(expiryText)}</td>
|
||||
<td>${escapeHtml(statusText)}</td>
|
||||
<td>${isExpired ? `<button type="button" class="btn btn-danger btn-small" onclick="disposeBatchFromReport(${row.batch_id}, '${String(row.batch_number || '').replace(/'/g, "\\'")}')">Dispose Expired Batch</button>` : '-'}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
@@ -366,6 +394,7 @@ function renderBatchAttentionTable(rows) {
|
||||
<th>Location</th>
|
||||
<th>Expiry</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rowsHtml}</tbody>
|
||||
@@ -373,6 +402,54 @@ function renderBatchAttentionTable(rows) {
|
||||
`;
|
||||
}
|
||||
|
||||
function disposeBatchFromReport(batchId, batchNumber) {
|
||||
const modal = document.getElementById('disposeBatchModal');
|
||||
const batchIdInput = document.getElementById('disposeBatchId');
|
||||
const batchNameInput = document.getElementById('disposeBatchName');
|
||||
const notesInput = document.getElementById('disposeBatchNotes');
|
||||
|
||||
if (!modal || !batchIdInput || !batchNameInput || !notesInput) {
|
||||
showToast('Dispose batch modal is unavailable.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
batchIdInput.value = String(batchId);
|
||||
batchNameInput.value = batchNumber;
|
||||
notesInput.value = '';
|
||||
openModal(modal);
|
||||
}
|
||||
|
||||
async function handleDisposeBatch(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const batchId = parseInt(document.getElementById('disposeBatchId')?.value || '', 10);
|
||||
const notes = document.getElementById('disposeBatchNotes')?.value.trim() || '';
|
||||
|
||||
if (!batchId) {
|
||||
showToast('Batch disposal context is unavailable.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/batches/${batchId}/dispose`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ notes: notes || null })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to dispose batch');
|
||||
}
|
||||
|
||||
closeDisposeBatchModal();
|
||||
await loadActiveReport();
|
||||
showToast('Expired batch marked as disposed.', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error disposing batch from report:', error);
|
||||
showToast('Failed to dispose batch: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function applyCurrentFilters() {
|
||||
const userFilter = document.getElementById('reportUserFilter');
|
||||
const drugFilter = document.getElementById('reportDrugFilter');
|
||||
@@ -436,7 +513,7 @@ function applyCurrentFilters() {
|
||||
const reportName = activeReportType === 'dispensing'
|
||||
? 'dispensing records'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'expired/partial batches'
|
||||
? 'expired batches'
|
||||
: 'audit events';
|
||||
reportsSummary.textContent = `Showing ${filteredRows.length} of ${sourceRows.length} ${reportName}`;
|
||||
}
|
||||
@@ -461,8 +538,8 @@ function updateReportHeading() {
|
||||
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...';
|
||||
heading.textContent = 'Expired Batches';
|
||||
searchInput.placeholder = 'Search drug, batch, location...';
|
||||
if (userFilter) userFilter.style.display = 'none';
|
||||
} else {
|
||||
heading.textContent = 'Audit Trail (Raw)';
|
||||
@@ -511,7 +588,7 @@ async function loadActiveReport() {
|
||||
const loadingText = activeReportType === 'dispensing'
|
||||
? 'Loading dispensing history...'
|
||||
: activeReportType === 'batch_attention'
|
||||
? 'Loading expired / partial batches...'
|
||||
? 'Loading expired batches...'
|
||||
: 'Loading audit trail...';
|
||||
container.innerHTML = `<p class="loading" style="padding: 14px;">${loadingText}</p>`;
|
||||
}
|
||||
@@ -581,6 +658,9 @@ function setupEventListeners() {
|
||||
const backBtn = document.getElementById('backToInventoryBtn');
|
||||
const logoutBtn = document.getElementById('reportsLogoutBtn');
|
||||
const goToLoginBtn = document.getElementById('goToLoginBtn');
|
||||
const disposeBatchForm = document.getElementById('disposeBatchForm');
|
||||
const cancelDisposeBatchBtn = document.getElementById('cancelDisposeBatchBtn');
|
||||
const closeButtons = document.querySelectorAll('.close');
|
||||
|
||||
const userFilter = document.getElementById('reportUserFilter');
|
||||
const drugFilter = document.getElementById('reportDrugFilter');
|
||||
@@ -635,6 +715,28 @@ function setupEventListeners() {
|
||||
if (goToLoginBtn) goToLoginBtn.addEventListener('click', () => {
|
||||
window.location.href = 'index.html';
|
||||
});
|
||||
|
||||
if (disposeBatchForm) disposeBatchForm.addEventListener('submit', handleDisposeBatch);
|
||||
if (cancelDisposeBatchBtn) cancelDisposeBatchBtn.addEventListener('click', closeDisposeBatchModal);
|
||||
|
||||
closeButtons.forEach(btn => btn.addEventListener('click', (e) => {
|
||||
const modal = e.target.closest('.modal');
|
||||
if (modal?.id === 'disposeBatchModal') {
|
||||
closeDisposeBatchModal();
|
||||
return;
|
||||
}
|
||||
closeModal(modal);
|
||||
}));
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('modal')) {
|
||||
if (e.target.id === 'disposeBatchModal') {
|
||||
closeDisposeBatchModal();
|
||||
return;
|
||||
}
|
||||
closeModal(e.target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializeReportsPage() {
|
||||
|
||||
Reference in New Issue
Block a user