Refactor - API lazy loading
This commit is contained in:
+157
-76
@@ -1,5 +1,7 @@
|
||||
const API_URL = '/api';
|
||||
let allDrugs = [];
|
||||
let allDrugs = []; // level-1 summaries: no packs, no batches
|
||||
let loadedDrugDetails = new Map(); // drugId → full drug with variant packs (level-2)
|
||||
let loadedVariantBatches = new Map(); // variantId → Batch[] (level-3)
|
||||
let currentDrug = null;
|
||||
let showLowStockOnly = false;
|
||||
let selectedLocationFilter = '';
|
||||
@@ -450,9 +452,21 @@ function setupEventListeners() {
|
||||
});
|
||||
|
||||
// User menu
|
||||
if (userMenuBtn) userMenuBtn.addEventListener('click', () => {
|
||||
if (userMenuBtn) userMenuBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const dropdown = document.getElementById('userDropdown');
|
||||
if (dropdown) dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
if (dropdown) {
|
||||
const isHidden = getComputedStyle(dropdown).display === 'none';
|
||||
dropdown.style.display = isHidden ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const dropdown = document.getElementById('userDropdown');
|
||||
const btn = document.getElementById('userMenuBtn');
|
||||
if (dropdown && btn && !btn.contains(e.target) && !dropdown.contains(e.target)) {
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
if (changePasswordBtn) changePasswordBtn.addEventListener('click', openChangePasswordModal);
|
||||
@@ -532,22 +546,24 @@ async function loadDrugs() {
|
||||
const newVariant = drug?.variants?.find(v => !restore._existingVariantIds.has(v.id));
|
||||
if (newVariant) {
|
||||
restore.variantId = newVariant.id;
|
||||
// If no pack snapshot, all packs are new — pick the first active one
|
||||
// If no pack snapshot, all packs are new — pick the first active one from detail
|
||||
if (!restore._existingPackIds) {
|
||||
const firstPack = getActivePacksForVariant(newVariant)?.[0];
|
||||
await ensureDrugDetailLoaded(restore.drugId);
|
||||
const detailVariant = getVariantById(newVariant.id);
|
||||
const firstPack = getActivePacksForVariant(detailVariant)?.[0];
|
||||
if (firstPack) restore.packId = firstPack.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Resolve new pack by diffing (add-pack flow)
|
||||
// Resolve new pack by diffing (add-pack flow) — need packs from loaded detail
|
||||
if (restore._existingPackIds && restore.drugId && restore.variantId) {
|
||||
const drug = allDrugs.find(d => d.id === restore.drugId);
|
||||
const variant = drug?.variants?.find(v => v.id === restore.variantId);
|
||||
await ensureDrugDetailLoaded(restore.drugId);
|
||||
const variant = getVariantById(restore.variantId);
|
||||
const newPack = getActivePacksForVariant(variant)?.find(p => !restore._existingPackIds.has(p.id));
|
||||
if (newPack) restore.packId = newPack.id;
|
||||
}
|
||||
|
||||
_reinitGtinMappingModal(restore);
|
||||
await _reinitGtinMappingModal(restore);
|
||||
}
|
||||
|
||||
// After handleAddDrug's loadDrugs fires: find the newly created drug and set up
|
||||
@@ -959,10 +975,10 @@ function isBatchExpired(batch) {
|
||||
return expiryDate < today;
|
||||
}
|
||||
|
||||
function renderVariantInventoryDetails(variant) {
|
||||
function renderVariantInventoryDetails(variant, batches) {
|
||||
const activePacks = getActivePacksForVariant(variant);
|
||||
const isReadOnly = currentUser?.role === 'readonly';
|
||||
const batches = [...(variant.batches || [])]
|
||||
const sortedBatches = [...(batches || [])]
|
||||
.filter(batch => Number(batch.quantity) > 0)
|
||||
.sort((a, b) => new Date(a.expiry_date) - new Date(b.expiry_date));
|
||||
|
||||
@@ -975,8 +991,8 @@ function renderVariantInventoryDetails(variant) {
|
||||
`).join('')
|
||||
: '<div style="padding: 6px 8px; background: #ffffff; border: 1px dashed #cfd8e3; border-radius: 5px; font-size: 0.9em; color: #6b7280;">No active packs configured</div>';
|
||||
|
||||
const batchesHtml = batches.length > 0
|
||||
? batches.map(batch => {
|
||||
const batchesHtml = sortedBatches.length > 0
|
||||
? sortedBatches.map(batch => {
|
||||
const locationLabel = getBatchLocationLabel(batch);
|
||||
const expired = isBatchExpired(batch);
|
||||
const hasPackState = batch.current_full_pack_count != null && batch.current_loose_base_units != null && batch.received_pack_unit_name;
|
||||
@@ -1067,6 +1083,16 @@ async function handleDisposeBatch(e) {
|
||||
if (modal) {
|
||||
closeDisposeBatchModal();
|
||||
}
|
||||
// Find which variant this batch belongs to and invalidate its batch cache
|
||||
const batchVariantId = (() => {
|
||||
for (const [vid, batches] of loadedVariantBatches) {
|
||||
if (batches.some(b => b.id === batchId)) return vid;
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
if (batchVariantId) {
|
||||
loadedVariantBatches.delete(batchVariantId);
|
||||
}
|
||||
await loadDrugs();
|
||||
showToast('Expired batch marked as disposed.', 'success');
|
||||
} catch (error) {
|
||||
@@ -1087,13 +1113,7 @@ function updateLocationFilterOptions() {
|
||||
const locations = new Set();
|
||||
|
||||
allDrugs.forEach(drug => {
|
||||
drug.variants.forEach(variant => {
|
||||
(variant.batches || []).forEach(batch => {
|
||||
if (batch.quantity > 0) {
|
||||
locations.add(getBatchLocationLabel(batch));
|
||||
}
|
||||
});
|
||||
});
|
||||
(drug.locations || []).forEach(loc => locations.add(loc));
|
||||
});
|
||||
|
||||
locationFilterSelect.innerHTML = '<option value="">All Locations</option>';
|
||||
@@ -1388,7 +1408,10 @@ async function updateBatchInfo() {
|
||||
|
||||
const variant = getVariantById(variantId);
|
||||
if (variant) {
|
||||
populateDispensePackSelect(variant);
|
||||
// Ensure drug detail (with packs) is loaded before populating pack select
|
||||
const drugOfVariant = allDrugs.find(d => (d.variants || []).some(v => v.id === variantId));
|
||||
if (drugOfVariant) await ensureDrugDetailLoaded(drugOfVariant.id);
|
||||
populateDispensePackSelect(getVariantById(variantId));
|
||||
}
|
||||
updateDispenseModeUi();
|
||||
|
||||
@@ -1621,14 +1644,10 @@ function renderDrugs() {
|
||||
);
|
||||
}
|
||||
|
||||
// Apply location filter
|
||||
// Apply location filter using the pre-computed locations list in the summary
|
||||
if (selectedLocationFilter) {
|
||||
drugsToShow = drugsToShow.filter(drug =>
|
||||
drug.variants.some(variant =>
|
||||
(variant.batches || []).some(batch =>
|
||||
batch.quantity > 0 && getBatchLocationLabel(batch) === selectedLocationFilter
|
||||
)
|
||||
)
|
||||
(drug.locations || []).includes(selectedLocationFilter)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1650,43 +1669,57 @@ function renderDrugs() {
|
||||
const isExpanded = expandedDrugs.has(drug.id);
|
||||
const isReadOnly = currentUser.role === 'readonly';
|
||||
const isControlled = drug.is_controlled;
|
||||
|
||||
const variantsHtml = isExpanded ? `
|
||||
${drug.variants.map(variant => {
|
||||
const variantIsLowStock = variant.quantity <= variant.low_stock_threshold;
|
||||
const variantExpanded = expandedVariants.has(variant.id);
|
||||
const expiredQuantity = (variant.batches || [])
|
||||
.filter(batch => Number(batch.quantity) > 0 && isBatchExpired(batch))
|
||||
.reduce((sum, batch) => sum + Number(batch.quantity || 0), 0);
|
||||
const inDateQuantity = Math.max(0, Number(variant.quantity || 0) - expiredQuantity);
|
||||
const quantityDisplay = expiredQuantity > 0
|
||||
? `${formatDisplayNumber(inDateQuantity)} ${escapeHtml(variant.unit)} (${formatDisplayNumber(expiredQuantity)} expired)`
|
||||
: `${formatDisplayNumber(variant.quantity)} ${escapeHtml(variant.unit)}`;
|
||||
return `
|
||||
<div class="variant-item ${variantIsLowStock ? 'low-stock' : ''}" onclick="toggleVariantExpansion(${variant.id}, event)">
|
||||
<div class="variant-info">
|
||||
<div class="variant-details">
|
||||
<div class="variant-name">${escapeHtml(drug.name)} ${escapeHtml(variant.strength)}</div>
|
||||
<div class="variant-quantity">${quantityDisplay}</div>
|
||||
const drugDetail = loadedDrugDetails.get(drug.id);
|
||||
|
||||
let variantsHtml = '';
|
||||
if (isExpanded) {
|
||||
if (!drugDetail) {
|
||||
variantsHtml = '<div class="variant-item" style="padding: 12px; color: #6b7280; font-style: italic;">Loading variants…</div>';
|
||||
} else {
|
||||
variantsHtml = drug.variants.map(summaryVariant => {
|
||||
// Use detail variant (has packs); fall back to summary if not found
|
||||
const variant = (drugDetail.variants || []).find(v => v.id === summaryVariant.id) || summaryVariant;
|
||||
const variantIsLowStock = summaryVariant.quantity <= summaryVariant.low_stock_threshold;
|
||||
const variantExpanded = expandedVariants.has(summaryVariant.id);
|
||||
// expiredQuantity is pre-computed in the summary
|
||||
const expiredQuantity = summaryVariant.expired_quantity || 0;
|
||||
const inDateQuantity = Math.max(0, Number(summaryVariant.quantity || 0) - expiredQuantity);
|
||||
const quantityDisplay = expiredQuantity > 0
|
||||
? `${formatDisplayNumber(inDateQuantity)} ${escapeHtml(summaryVariant.unit)} (${formatDisplayNumber(expiredQuantity)} expired)`
|
||||
: `${formatDisplayNumber(summaryVariant.quantity)} ${escapeHtml(summaryVariant.unit)}`;
|
||||
const batches = loadedVariantBatches.get(summaryVariant.id);
|
||||
const batchesSection = (() => {
|
||||
if (!variantExpanded) return '';
|
||||
if (!batches) return '<div style="padding: 10px; color: #6b7280; font-style: italic;">Loading batches…</div>';
|
||||
return renderVariantInventoryDetails(variant, batches);
|
||||
})();
|
||||
return `
|
||||
<div class="variant-item ${variantIsLowStock ? 'low-stock' : ''}" onclick="toggleVariantExpansion(${summaryVariant.id}, event)">
|
||||
<div class="variant-info">
|
||||
<div class="variant-details">
|
||||
<div class="variant-name">${escapeHtml(drug.name)} ${escapeHtml(summaryVariant.strength)}</div>
|
||||
<div class="variant-quantity">${quantityDisplay}</div>
|
||||
</div>
|
||||
<div class="variant-status">
|
||||
<span class="variant-badge ${variantIsLowStock ? 'badge-low' : 'badge-normal'}">
|
||||
${variantIsLowStock ? 'Low Stock' : 'OK'}
|
||||
</span>
|
||||
<span style="margin-left: 8px; font-size: 0.85em; color: #475569;">Inventory ${variantExpanded ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="variant-status">
|
||||
<span class="variant-badge ${variantIsLowStock ? 'badge-low' : 'badge-normal'}">
|
||||
${variantIsLowStock ? 'Low Stock' : 'OK'}
|
||||
</span>
|
||||
<span style="margin-left: 8px; font-size: 0.85em; color: #475569;">Inventory ${variantExpanded ? '▼' : '▶'}</span>
|
||||
<div class="variant-actions">
|
||||
${!isReadOnly ? `
|
||||
<button class="btn btn-success btn-small" onclick="event.stopPropagation(); dispenseVariant(${summaryVariant.id})">💊 Dispense</button>
|
||||
<button class="btn btn-warning btn-small" onclick="event.stopPropagation(); openEditVariantModal(${summaryVariant.id})">Edit</button>
|
||||
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); deleteVariant(${summaryVariant.id})" title="${summaryVariant.has_inventory_history ? 'Variant has history and cannot be deleted' : ''}">Delete</button>
|
||||
` : ''}
|
||||
</div>
|
||||
${batchesSection}
|
||||
</div>
|
||||
<div class="variant-actions">
|
||||
${!isReadOnly ? `
|
||||
<button class="btn btn-success btn-small" onclick="event.stopPropagation(); dispenseVariant(${variant.id})">💊 Dispense</button>
|
||||
<button class="btn btn-warning btn-small" onclick="event.stopPropagation(); openEditVariantModal(${variant.id})">Edit</button>
|
||||
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); deleteVariant(${variant.id})" title="${variant.has_inventory_history ? 'Variant has history and cannot be deleted' : ''}">Delete</button>
|
||||
` : ''}
|
||||
</div>
|
||||
${variantExpanded ? renderVariantInventoryDetails(variant) : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('')}` : '';
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="drug-item ${isLowStock ? 'low-stock' : ''} ${isExpanded ? 'expanded' : ''}" onclick="toggleDrugExpansion(${drug.id})">
|
||||
@@ -1934,6 +1967,7 @@ async function handleDispenseDrug(e) {
|
||||
document.getElementById('dispenseForm').reset();
|
||||
resetDispensePrintFields();
|
||||
closeModal(document.getElementById('dispenseModal'));
|
||||
loadedVariantBatches.delete(variantId); // invalidate level-3 batch cache
|
||||
await loadDrugs();
|
||||
showToast(successMessage, toastType, toastType === 'warning' ? 5000 : undefined);
|
||||
} catch (error) {
|
||||
@@ -1962,30 +1996,45 @@ function closeEditModal() {
|
||||
}
|
||||
|
||||
// Show variants for a drug
|
||||
function toggleDrugExpansion(drugId) {
|
||||
async function toggleDrugExpansion(drugId) {
|
||||
if (expandedDrugs.has(drugId)) {
|
||||
expandedDrugs.delete(drugId);
|
||||
const collapsedDrug = allDrugs.find(drug => drug.id === drugId);
|
||||
if (collapsedDrug) {
|
||||
(collapsedDrug.variants || []).forEach(variant => expandedVariants.delete(variant.id));
|
||||
}
|
||||
renderDrugs();
|
||||
} else {
|
||||
expandedDrugs.add(drugId);
|
||||
renderDrugs(); // show loading state immediately
|
||||
await ensureDrugDetailLoaded(drugId);
|
||||
renderDrugs();
|
||||
}
|
||||
renderDrugs();
|
||||
}
|
||||
|
||||
function toggleVariantExpansion(variantId, event) {
|
||||
async function toggleVariantExpansion(variantId, event) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (expandedVariants.has(variantId)) {
|
||||
expandedVariants.delete(variantId);
|
||||
renderDrugs();
|
||||
} else {
|
||||
expandedVariants.add(variantId);
|
||||
if (!loadedVariantBatches.has(variantId)) {
|
||||
renderDrugs(); // show loading state immediately
|
||||
try {
|
||||
const response = await apiCall(`/variants/${variantId}/batches`);
|
||||
if (response.ok) {
|
||||
loadedVariantBatches.set(variantId, await response.json());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load batches for variant ${variantId}:`, error);
|
||||
}
|
||||
}
|
||||
renderDrugs();
|
||||
}
|
||||
renderDrugs();
|
||||
}
|
||||
|
||||
// Open add variant modal
|
||||
@@ -2256,6 +2305,7 @@ async function handleAddVariant(e) {
|
||||
|
||||
document.getElementById('variantForm').reset();
|
||||
closeModal(document.getElementById('addVariantModal'));
|
||||
loadedDrugDetails.delete(drugId); // invalidate level-2 cache for this drug
|
||||
await loadDrugs();
|
||||
if (document.getElementById('receiveDeliveryModal')?.classList.contains('show')) {
|
||||
refreshDeliveryVariantSelects();
|
||||
@@ -2369,6 +2419,8 @@ async function handleEditVariant(e) {
|
||||
}
|
||||
|
||||
closeModal(document.getElementById('editVariantModal'));
|
||||
const variantDrugId = allDrugs.find(d => (d.variants || []).some(v => v.id === variantId))?.id;
|
||||
if (variantDrugId) loadedDrugDetails.delete(variantDrugId); // invalidate level-2 cache
|
||||
await loadDrugs();
|
||||
renderDrugs();
|
||||
const message = newPackPayloads.length > 0
|
||||
@@ -2574,6 +2626,7 @@ async function handleEditDrug(e) {
|
||||
if (!response.ok) throw new Error('Failed to update drug');
|
||||
|
||||
closeEditModal();
|
||||
loadedDrugDetails.delete(drugId); // invalidate level-2 cache
|
||||
await loadDrugs();
|
||||
showToast('Drug updated successfully!', 'success');
|
||||
} catch (error) {
|
||||
@@ -2602,6 +2655,8 @@ async function deleteDrug(drugId) {
|
||||
throw new Error(error.detail || 'Failed to delete drug');
|
||||
}
|
||||
|
||||
loadedDrugDetails.delete(drugId); // invalidate level-2 cache
|
||||
loadedVariantBatches.clear(); // variants of this drug are gone
|
||||
await loadDrugs();
|
||||
showToast('Drug deleted successfully!', 'success');
|
||||
} catch (error) {
|
||||
@@ -3039,6 +3094,7 @@ async function handleBatchReceive(e) {
|
||||
|
||||
document.getElementById('batchReceiveForm').reset();
|
||||
closeModal(document.getElementById('batchReceiveModal'));
|
||||
loadedVariantBatches.delete(variantId); // invalidate level-3 cache
|
||||
await loadDrugs();
|
||||
showToast('Batch received successfully!', 'success');
|
||||
} catch (error) {
|
||||
@@ -3052,6 +3108,12 @@ function getActiveDeliveryDrug() {
|
||||
}
|
||||
|
||||
function getVariantById(variantId) {
|
||||
// Prefer loaded drug details (have packs)
|
||||
for (const detail of loadedDrugDetails.values()) {
|
||||
const found = (detail.variants || []).find(v => v.id === variantId);
|
||||
if (found) return found;
|
||||
}
|
||||
// Fall back to summary variant (no packs)
|
||||
for (const drug of allDrugs) {
|
||||
const found = (drug.variants || []).find(v => v.id === variantId);
|
||||
if (found) return found;
|
||||
@@ -3059,6 +3121,18 @@ function getVariantById(variantId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function ensureDrugDetailLoaded(drugId) {
|
||||
if (!drugId || loadedDrugDetails.has(drugId)) return;
|
||||
try {
|
||||
const response = await apiCall(`/drugs/${drugId}`);
|
||||
if (response.ok) {
|
||||
loadedDrugDetails.set(drugId, await response.json());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load drug detail for drug ${drugId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
function buildDeliveryVariantOptions(drug, selectedVariantId = '') {
|
||||
if (!drug || !drug.variants || drug.variants.length === 0) {
|
||||
return '<option value="">-- No variants available --</option>';
|
||||
@@ -3116,8 +3190,9 @@ function wireDeliveryLineEvents(line) {
|
||||
const packCountInput = line.querySelector('.delivery-pack-count');
|
||||
|
||||
if (drugSelect && variantSelect) {
|
||||
drugSelect.addEventListener('change', () => {
|
||||
drugSelect.addEventListener('change', async () => {
|
||||
const drugId = parseInt(drugSelect.value || '', 10);
|
||||
await ensureDrugDetailLoaded(drugId);
|
||||
const drug = allDrugs.find(d => d.id === drugId) || null;
|
||||
variantSelect.innerHTML = buildDeliveryVariantOptions(drug, '');
|
||||
if (packSelect) packSelect.innerHTML = buildDeliveryPackOptions(null, '');
|
||||
@@ -3466,7 +3541,7 @@ function _refreshGtinMappingSelects() {
|
||||
|
||||
// Reinitialise the GTIN mapping modal dropdowns from fresh allDrugs data,
|
||||
// optionally pre-selecting specific drug/variant/pack IDs.
|
||||
function _reinitGtinMappingModal(restore) {
|
||||
async function _reinitGtinMappingModal(restore) {
|
||||
const drugSelect = document.getElementById('gtinMappingDrugSelect');
|
||||
const variantSelect = document.getElementById('gtinMappingVariantSelect');
|
||||
const packSelect = document.getElementById('gtinMappingPackSelect');
|
||||
@@ -3488,6 +3563,7 @@ function _reinitGtinMappingModal(restore) {
|
||||
}
|
||||
|
||||
drugSelect.value = String(drugId);
|
||||
await ensureDrugDetailLoaded(drugId);
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
|
||||
// Rebuild variant list
|
||||
@@ -3504,7 +3580,7 @@ function _reinitGtinMappingModal(restore) {
|
||||
}
|
||||
|
||||
variantSelect.value = String(variantId);
|
||||
const variant = drug?.variants?.find(v => v.id === variantId);
|
||||
const variant = getVariantById(variantId); // checks loadedDrugDetails first (has packs)
|
||||
|
||||
// Rebuild pack list
|
||||
packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
@@ -3540,9 +3616,9 @@ function gtinMappingAddPack() {
|
||||
const variantId = parseInt(document.getElementById('gtinMappingVariantSelect').value || '', 10);
|
||||
if (!variantId) { showToast('Select a variant first', 'warning'); return; }
|
||||
const drugId = parseInt(document.getElementById('gtinMappingDrugSelect').value || '', 10);
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
if (!drug) return;
|
||||
const variant = drug.variants?.find(v => v.id === variantId);
|
||||
if (!drugId) return;
|
||||
// Use getVariantById which returns the detail variant (with packs) if loaded
|
||||
const variant = getVariantById(variantId);
|
||||
const existingPackIds = new Set((getActivePacksForVariant(variant) || []).map(p => p.id));
|
||||
_gtinMappingPendingRestore = { drugId, variantId, packId: null, _existingPackIds: existingPackIds };
|
||||
_gtinMappingPendingRefresh = true;
|
||||
@@ -3571,15 +3647,17 @@ function openGtinMappingModal(gtin, expiryStr, lot) {
|
||||
openModal(document.getElementById('gtinMappingModal'));
|
||||
}
|
||||
|
||||
function onGtinMappingDrugChange() {
|
||||
async function onGtinMappingDrugChange() {
|
||||
const drugId = parseInt(document.getElementById('gtinMappingDrugSelect').value || '', 10);
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
const variantSelect = document.getElementById('gtinMappingVariantSelect');
|
||||
const packSelect = document.getElementById('gtinMappingPackSelect');
|
||||
|
||||
variantSelect.innerHTML = '<option value="">-- Select variant --</option>';
|
||||
packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
|
||||
if (!drugId) return;
|
||||
await ensureDrugDetailLoaded(drugId);
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
if (!drug) return;
|
||||
variantSelect.innerHTML += drug.variants.map(v =>
|
||||
`<option value="${v.id}">${escapeHtml(v.strength)} (${escapeHtml(v.unit)})</option>`
|
||||
@@ -3587,10 +3665,8 @@ function onGtinMappingDrugChange() {
|
||||
}
|
||||
|
||||
function onGtinMappingVariantChange() {
|
||||
const drugId = parseInt(document.getElementById('gtinMappingDrugSelect').value || '', 10);
|
||||
const variantId = parseInt(document.getElementById('gtinMappingVariantSelect').value || '', 10);
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
const variant = drug?.variants?.find(v => v.id === variantId);
|
||||
const variant = getVariantById(variantId); // checks loadedDrugDetails first (has packs)
|
||||
const packSelect = document.getElementById('gtinMappingPackSelect');
|
||||
|
||||
packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
@@ -3901,6 +3977,9 @@ async function handleAddPackSize(e) {
|
||||
}
|
||||
|
||||
closeModal(document.getElementById('addPackSizeModal'));
|
||||
// Invalidate drug detail cache so the new pack will be re-fetched on next expand
|
||||
const packDrugId = allDrugs.find(d => (d.variants || []).some(v => v.id === variantId))?.id;
|
||||
if (packDrugId) loadedDrugDetails.delete(packDrugId);
|
||||
await loadDrugs();
|
||||
// Refresh delivery line pack selects so the new pack is immediately available
|
||||
refreshDeliveryVariantSelects();
|
||||
@@ -3986,6 +4065,8 @@ async function handleReceiveDelivery(e) {
|
||||
|
||||
closeModal(document.getElementById('receiveDeliveryModal'));
|
||||
_detachDeliveryBarcodeListener();
|
||||
// Invalidate batch cache for all delivered variants
|
||||
payloads.forEach(entry => loadedVariantBatches.delete(entry.variantId));
|
||||
await loadDrugs();
|
||||
showToast(`Delivery received successfully (${payloads.length} line${payloads.length === 1 ? '' : 's'})`, 'success');
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user