WIP
This commit is contained in:
+816
-8
@@ -7,6 +7,10 @@ let searchTerm = '';
|
||||
let expandedDrugs = new Set();
|
||||
let currentUser = null;
|
||||
let accessToken = null;
|
||||
let deliveryDrugId = null;
|
||||
let deliveryLineCounter = 0;
|
||||
let deliveryLocations = [];
|
||||
let activeVariantPacksVariantId = null;
|
||||
|
||||
// Toast notification system
|
||||
function showToast(message, type = 'info', duration = 3000) {
|
||||
@@ -212,6 +216,7 @@ function setupEventListeners() {
|
||||
const editModal = document.getElementById('editModal');
|
||||
const printNotesModal = document.getElementById('printNotesModal');
|
||||
const batchReceiveModal = document.getElementById('batchReceiveModal');
|
||||
const receiveDeliveryModal = document.getElementById('receiveDeliveryModal');
|
||||
const addDrugBtn = document.getElementById('addDrugBtn');
|
||||
const dispenseBtn = document.getElementById('dispenseBtn');
|
||||
const printNotesBtn = document.getElementById('printNotesBtn');
|
||||
@@ -222,6 +227,15 @@ function setupEventListeners() {
|
||||
const cancelPrescribeBtn = document.getElementById('cancelPrescribeBtn');
|
||||
const cancelEditBtn = document.getElementById('cancelEditBtn');
|
||||
const cancelBatchReceiveBtn = document.getElementById('cancelBatchReceiveBtn');
|
||||
const cancelReceiveDeliveryBtn = document.getElementById('cancelReceiveDeliveryBtn');
|
||||
const addDeliveryLineBtn = document.getElementById('addDeliveryLineBtn');
|
||||
const addVariantFromDeliveryBtn = document.getElementById('addVariantFromDeliveryBtn');
|
||||
const addVariantPackRowBtn = document.getElementById('addVariantPackRowBtn');
|
||||
const variantUnitSelect = document.getElementById('variantUnit');
|
||||
const variantStrengthInput = document.getElementById('variantStrength');
|
||||
const dispenseModeSelect = document.getElementById('dispenseMode');
|
||||
const variantPacksForm = document.getElementById('variantPacksForm');
|
||||
const closeVariantPacksBtn = document.getElementById('closeVariantPacksBtn');
|
||||
const showAllBtn = document.getElementById('showAllBtn');
|
||||
const showLowStockBtn = document.getElementById('showLowStockBtn');
|
||||
const locationFilterSelect = document.getElementById('locationFilterSelect');
|
||||
@@ -246,11 +260,31 @@ function setupEventListeners() {
|
||||
const batchReceiveForm = document.getElementById('batchReceiveForm');
|
||||
if (batchReceiveForm) batchReceiveForm.addEventListener('submit', handleBatchReceive);
|
||||
if (cancelBatchReceiveBtn) cancelBatchReceiveBtn.addEventListener('click', () => closeModal(batchReceiveModal));
|
||||
|
||||
const receiveDeliveryForm = document.getElementById('receiveDeliveryForm');
|
||||
if (receiveDeliveryForm) receiveDeliveryForm.addEventListener('submit', handleReceiveDelivery);
|
||||
if (cancelReceiveDeliveryBtn) cancelReceiveDeliveryBtn.addEventListener('click', () => closeModal(receiveDeliveryModal));
|
||||
if (addDeliveryLineBtn) addDeliveryLineBtn.addEventListener('click', () => appendDeliveryLine());
|
||||
if (addVariantFromDeliveryBtn) addVariantFromDeliveryBtn.addEventListener('click', handleAddVariantFromDelivery);
|
||||
if (addVariantPackRowBtn) addVariantPackRowBtn.addEventListener('click', () => appendVariantPackRow());
|
||||
if (variantUnitSelect) {
|
||||
variantUnitSelect.addEventListener('change', () => {
|
||||
refreshVariantPackRowLabels();
|
||||
});
|
||||
}
|
||||
if (variantStrengthInput && variantUnitSelect) {
|
||||
variantStrengthInput.addEventListener('blur', () => {
|
||||
variantUnitSelect.value = inferBaseUnitFromStrength(variantStrengthInput.value);
|
||||
refreshVariantPackRowLabels();
|
||||
});
|
||||
}
|
||||
if (dispenseModeSelect) dispenseModeSelect.addEventListener('change', updateDispenseModeUi);
|
||||
|
||||
if (addDrugBtn) addDrugBtn.addEventListener('click', () => openModal(addModal));
|
||||
if (printNotesBtn) printNotesBtn.addEventListener('click', () => openModal(printNotesModal));
|
||||
if (dispenseBtn) dispenseBtn.addEventListener('click', () => {
|
||||
updateDispenseDrugSelect();
|
||||
updateDispenseModeUi();
|
||||
openModal(dispenseModal);
|
||||
});
|
||||
|
||||
@@ -272,6 +306,9 @@ function setupEventListeners() {
|
||||
|
||||
const closeLocationManagementBtn = document.getElementById('closeLocationManagementBtn');
|
||||
if (closeLocationManagementBtn) closeLocationManagementBtn.addEventListener('click', () => closeModal(document.getElementById('locationManagementModal')));
|
||||
|
||||
if (variantPacksForm) variantPacksForm.addEventListener('submit', handleCreateVariantPack);
|
||||
if (closeVariantPacksBtn) closeVariantPacksBtn.addEventListener('click', () => closeModal(document.getElementById('variantPacksModal')));
|
||||
|
||||
const createLocationForm = document.getElementById('createLocationForm');
|
||||
if (createLocationForm) createLocationForm.addEventListener('submit', createLocation);
|
||||
@@ -333,6 +370,28 @@ function setupEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
const dispenseQuantityInput = document.getElementById('dispenseQuantity');
|
||||
if (dispenseQuantityInput) {
|
||||
dispenseQuantityInput.addEventListener('input', () => {
|
||||
const mode = document.getElementById('dispenseMode')?.value || 'subunit';
|
||||
if (mode !== 'subunit') {
|
||||
return;
|
||||
}
|
||||
|
||||
const packSelect = document.getElementById('dispensePackSelect');
|
||||
const packCount = document.getElementById('dispensePackCount');
|
||||
const packPreview = document.getElementById('dispensePackPreview');
|
||||
const variantId = parseInt(document.getElementById('dispenseDrugSelect')?.value || '', 10);
|
||||
const variant = getVariantById(variantId);
|
||||
|
||||
if (packSelect) packSelect.value = '';
|
||||
if (packCount) packCount.value = '';
|
||||
if (packPreview && variant) {
|
||||
packPreview.textContent = `Enter direct quantity in ${variant.unit}.`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('modal')) {
|
||||
@@ -398,6 +457,108 @@ function updateDispenseDrugSelect() {
|
||||
select.appendChild(option);
|
||||
});
|
||||
});
|
||||
|
||||
const packSelect = document.getElementById('dispensePackSelect');
|
||||
const packCount = document.getElementById('dispensePackCount');
|
||||
const packPreview = document.getElementById('dispensePackPreview');
|
||||
const modeSelect = document.getElementById('dispenseMode');
|
||||
if (packSelect) {
|
||||
packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
}
|
||||
if (packCount) {
|
||||
packCount.value = '';
|
||||
}
|
||||
if (modeSelect) {
|
||||
modeSelect.value = 'subunit';
|
||||
}
|
||||
if (packPreview) {
|
||||
packPreview.textContent = 'Select a pack and whole-number count.';
|
||||
}
|
||||
|
||||
updateDispenseModeUi();
|
||||
}
|
||||
|
||||
function populateDispensePackSelect(variant) {
|
||||
const packSelect = document.getElementById('dispensePackSelect');
|
||||
const packCount = document.getElementById('dispensePackCount');
|
||||
const packPreview = document.getElementById('dispensePackPreview');
|
||||
if (!packSelect) return;
|
||||
|
||||
const activePacks = getActivePacksForVariant(variant);
|
||||
packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
|
||||
activePacks.forEach(pack => {
|
||||
const option = document.createElement('option');
|
||||
option.value = String(pack.id);
|
||||
option.textContent = `${pack.label} (${pack.pack_size_in_base_units} ${variant.unit})`;
|
||||
packSelect.appendChild(option);
|
||||
});
|
||||
|
||||
if (packCount) packCount.value = '';
|
||||
if (packPreview) {
|
||||
packPreview.textContent = activePacks.length > 0
|
||||
? `Select a pack and whole-number count (${variant.unit} base unit).`
|
||||
: `No active packs for this variant.`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDispenseModeUi() {
|
||||
const mode = document.getElementById('dispenseMode')?.value || 'subunit';
|
||||
const quantityGroup = document.getElementById('dispenseQuantityGroup');
|
||||
const packRow = document.getElementById('dispensePackRow');
|
||||
const quantityInput = document.getElementById('dispenseQuantity');
|
||||
const packSelect = document.getElementById('dispensePackSelect');
|
||||
const packCount = document.getElementById('dispensePackCount');
|
||||
|
||||
if (quantityGroup) {
|
||||
quantityGroup.style.display = mode === 'subunit' ? '' : 'none';
|
||||
}
|
||||
if (packRow) {
|
||||
packRow.style.display = mode === 'pack' ? '' : 'none';
|
||||
}
|
||||
|
||||
if (quantityInput) {
|
||||
quantityInput.required = mode === 'subunit';
|
||||
}
|
||||
if (packSelect) {
|
||||
packSelect.required = mode === 'pack';
|
||||
}
|
||||
if (packCount) {
|
||||
packCount.required = mode === 'pack';
|
||||
}
|
||||
|
||||
updateAllocationPreview();
|
||||
}
|
||||
|
||||
function updateDispenseQuantityFromPack() {
|
||||
const mode = document.getElementById('dispenseMode')?.value || 'subunit';
|
||||
if (mode !== 'pack') return;
|
||||
|
||||
const variantId = parseInt(document.getElementById('dispenseDrugSelect')?.value || '', 10);
|
||||
const packId = parseInt(document.getElementById('dispensePackSelect')?.value || '', 10);
|
||||
const packCount = parseFloat(document.getElementById('dispensePackCount')?.value || '');
|
||||
const quantityInput = document.getElementById('dispenseQuantity');
|
||||
const preview = document.getElementById('dispensePackPreview');
|
||||
|
||||
const variant = getVariantById(variantId);
|
||||
if (!quantityInput || !preview || !variant) return;
|
||||
|
||||
const selectedPack = getActivePacksForVariant(variant).find(pack => pack.id === packId);
|
||||
if (selectedPack && !Number.isNaN(packCount) && packCount > 0) {
|
||||
if (Math.abs(packCount - Math.round(packCount)) > 1e-6) {
|
||||
preview.textContent = 'Whole-pack mode requires a whole-number pack count.';
|
||||
return;
|
||||
}
|
||||
const quantity = packCount * selectedPack.pack_size_in_base_units;
|
||||
quantityInput.value = String(quantity);
|
||||
preview.textContent = `${packCount} × ${selectedPack.pack_size_in_base_units} = ${quantity} ${variant.unit}`;
|
||||
updateAllocationPreview();
|
||||
return;
|
||||
}
|
||||
|
||||
preview.textContent = selectedPack
|
||||
? `1 ${selectedPack.pack_unit_name} = ${selectedPack.pack_size_in_base_units} ${variant.unit}`
|
||||
: `Select a pack to calculate quantity.`;
|
||||
}
|
||||
|
||||
function formatDisplayDate(value) {
|
||||
@@ -455,6 +616,8 @@ function updateLocationFilterOptions() {
|
||||
|
||||
function populateDispenseBatchSelect(activeBatches) {
|
||||
const batchSelect = document.getElementById('dispenseBatchSelect');
|
||||
const selectedVariantId = parseInt(document.getElementById('dispenseDrugSelect').value || '', 10);
|
||||
const unitLabel = getVariantById(selectedVariantId)?.unit || 'units';
|
||||
const previousValue = batchSelect.value;
|
||||
|
||||
batchSelect.innerHTML = '<option value="">Automatic FEFO Selection</option>';
|
||||
@@ -465,7 +628,7 @@ function populateDispenseBatchSelect(activeBatches) {
|
||||
const locationLabel = getBatchLocationLabel(batch);
|
||||
const fefoLabel = index === 0 ? ' [FEFO default]' : '';
|
||||
option.value = batch.id;
|
||||
option.textContent = `${batch.batch_number} | ${batch.quantity} units | ${locationLabel} | Expires ${expiryLabel}${fefoLabel}`;
|
||||
option.textContent = `${batch.batch_number} | ${batch.quantity} ${unitLabel} | ${locationLabel} | Expires ${expiryLabel}${fefoLabel}`;
|
||||
batchSelect.appendChild(option);
|
||||
});
|
||||
|
||||
@@ -484,8 +647,16 @@ async function updateBatchInfo() {
|
||||
if (!variantId) {
|
||||
batchInfoSection.style.display = 'none';
|
||||
batchSelect.innerHTML = '<option value="">Automatic FEFO Selection</option>';
|
||||
const packSelect = document.getElementById('dispensePackSelect');
|
||||
if (packSelect) packSelect.innerHTML = '<option value="">-- Select pack --</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
const variant = getVariantById(variantId);
|
||||
if (variant) {
|
||||
populateDispensePackSelect(variant);
|
||||
}
|
||||
updateDispenseModeUi();
|
||||
|
||||
batchInfoSection.style.display = 'block';
|
||||
batchInfoContent.innerHTML = '<p class="loading">Loading batches...</p>';
|
||||
@@ -558,6 +729,7 @@ async function updateBatchInfo() {
|
||||
// Update allocation preview based on quantity and allow_split flag
|
||||
async function updateAllocationPreview() {
|
||||
const variantId = parseInt(document.getElementById('dispenseDrugSelect').value);
|
||||
const unitLabel = getVariantById(variantId)?.unit || 'units';
|
||||
const quantity = parseFloat(document.getElementById('dispenseQuantity').value);
|
||||
const allowSplit = document.getElementById('dispenseAllowSplit').checked;
|
||||
const preferredBatchId = parseInt(document.getElementById('dispenseBatchSelect').value);
|
||||
@@ -626,10 +798,10 @@ async function updateAllocationPreview() {
|
||||
|
||||
if (remainingQty > 0 && allowSplit) {
|
||||
allocationPreviewContent.innerHTML = `
|
||||
<p style="color: #d32f2f; margin: 0 0 10px 0;">✕ Warning: Only ${quantity - remainingQty} units available across all batches (${remainingQty} short)</p>
|
||||
<p style="color: #d32f2f; margin: 0 0 10px 0;">✕ Warning: Only ${quantity - remainingQty} ${escapeHtml(unitLabel)} available across all batches (${remainingQty} short)</p>
|
||||
<div>${allocations.map(a => `
|
||||
<div style="padding: 5px; margin: 3px 0; background: white; border-radius: 2px; font-size: 0.9em;">
|
||||
<strong>${a.batchNumber}</strong>${a.preferred ? ' <span style="color: #1565c0;">(preferred)</span>' : ''}: ${a.quantity} units (${escapeHtml(a.location)}) - Expires ${formatDisplayDate(a.expiryDate)}
|
||||
<strong>${a.batchNumber}</strong>${a.preferred ? ' <span style="color: #1565c0;">(preferred)</span>' : ''}: ${a.quantity} ${escapeHtml(unitLabel)} (${escapeHtml(a.location)}) - Expires ${formatDisplayDate(a.expiryDate)}
|
||||
</div>
|
||||
`).join('')}</div>
|
||||
`;
|
||||
@@ -638,7 +810,7 @@ async function updateAllocationPreview() {
|
||||
|
||||
const allocationHtml = allocations.map(a => `
|
||||
<div style="padding: 5px; margin: 3px 0; background: white; border-radius: 2px; font-size: 0.9em;">
|
||||
<strong>${a.batchNumber}</strong>${a.preferred ? ' <span style="color: #1565c0;">(preferred)</span>' : ''}: ${a.quantity} units (${escapeHtml(a.location)}) - Expires ${formatDisplayDate(a.expiryDate)}
|
||||
<strong>${a.batchNumber}</strong>${a.preferred ? ' <span style="color: #1565c0;">(preferred)</span>' : ''}: ${a.quantity} ${escapeHtml(unitLabel)} (${escapeHtml(a.location)}) - Expires ${formatDisplayDate(a.expiryDate)}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
@@ -725,7 +897,7 @@ function renderDrugs() {
|
||||
</div>
|
||||
<div class="variant-actions">
|
||||
${!isReadOnly ? `
|
||||
<button class="btn btn-success btn-small" onclick="openBatchReceiveModal(${variant.id})">📦 Receive Batch</button>
|
||||
<button class="btn btn-info btn-small" onclick="event.stopPropagation(); openVariantPacksModal(${variant.id})">📦 Packs</button>
|
||||
<button class="btn btn-primary btn-small" onclick="prescribeVariant(${variant.id}, '${drug.name.replace(/'/g, "\\'")}', '${variant.strength.replace(/'/g, "\\'")}', '${variant.unit.replace(/'/g, "\\'")}')">🏷️ Prescribe & Print</button>
|
||||
<button class="btn btn-success btn-small" onclick="dispenseVariant(${variant.id})">💊 Dispense</button>
|
||||
<button class="btn btn-warning btn-small" onclick="openEditVariantModal(${variant.id})">Edit</button>
|
||||
@@ -753,6 +925,7 @@ function renderDrugs() {
|
||||
</div>
|
||||
<div class="drug-actions">
|
||||
${!isReadOnly ? `
|
||||
<button class="btn btn-success btn-small" onclick="event.stopPropagation(); openReceiveDeliveryModal(${drug.id})">📦 Receive Delivery</button>
|
||||
<button class="btn btn-primary btn-small" onclick="event.stopPropagation(); openAddVariantModal(${drug.id})">➕ Add</button>
|
||||
` : ''}
|
||||
<button class="btn btn-info btn-small" onclick="event.stopPropagation(); showDrugHistory(${drug.id})">📋 History</button>
|
||||
@@ -825,13 +998,45 @@ async function handleDispenseDrug(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const variantId = parseInt(document.getElementById('dispenseDrugSelect').value);
|
||||
const quantity = parseFloat(document.getElementById('dispenseQuantity').value);
|
||||
let quantity = parseFloat(document.getElementById('dispenseQuantity').value);
|
||||
const dispenseMode = (document.getElementById('dispenseMode').value || 'subunit').toLowerCase();
|
||||
const preferredBatchIdValue = document.getElementById('dispenseBatchSelect').value;
|
||||
const requestedPackIdValue = document.getElementById('dispensePackSelect').value;
|
||||
const requestedPackCountValue = document.getElementById('dispensePackCount').value;
|
||||
const animalName = document.getElementById('dispenseAnimal').value;
|
||||
const userName = document.getElementById('dispenseUser').value;
|
||||
const notes = document.getElementById('dispenseNotes').value;
|
||||
const allowSplit = document.getElementById('dispenseAllowSplit').checked;
|
||||
|
||||
const selectedPackId = requestedPackIdValue ? parseInt(requestedPackIdValue, 10) : null;
|
||||
const selectedPackCount = requestedPackCountValue ? parseFloat(requestedPackCountValue) : null;
|
||||
const variant = getVariantById(variantId);
|
||||
const selectedPack = variant && selectedPackId
|
||||
? getActivePacksForVariant(variant).find(pack => pack.id === selectedPackId)
|
||||
: null;
|
||||
|
||||
if (!['subunit', 'pack'].includes(dispenseMode)) {
|
||||
showToast('Please select a valid dispense mode.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispenseMode === 'pack') {
|
||||
if (!selectedPack) {
|
||||
showToast('Please select a pack type for whole-pack dispensing.', 'warning');
|
||||
return;
|
||||
}
|
||||
if (selectedPackCount == null || Number.isNaN(selectedPackCount) || selectedPackCount <= 0) {
|
||||
showToast('Please enter a valid pack count greater than zero.', 'warning');
|
||||
return;
|
||||
}
|
||||
if (Math.abs(selectedPackCount - Math.round(selectedPackCount)) > 1e-6) {
|
||||
showToast('Whole-pack dispensing requires a whole-number pack count.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
quantity = selectedPackCount * selectedPack.pack_size_in_base_units;
|
||||
}
|
||||
|
||||
if (!variantId || isNaN(quantity) || quantity <= 0 || !userName) {
|
||||
showToast('Please fill in all required fields (Drug Variant, Quantity > 0, Dispensed by)', 'warning');
|
||||
return;
|
||||
@@ -840,7 +1045,10 @@ async function handleDispenseDrug(e) {
|
||||
const dispensingData = {
|
||||
drug_variant_id: variantId,
|
||||
quantity: quantity,
|
||||
dispense_mode: dispenseMode,
|
||||
batch_id: preferredBatchIdValue ? parseInt(preferredBatchIdValue) : null,
|
||||
requested_pack_id: dispenseMode === 'pack' ? selectedPackId : null,
|
||||
requested_pack_count: dispenseMode === 'pack' ? selectedPackCount : null,
|
||||
animal_name: animalName || null,
|
||||
user_name: userName,
|
||||
notes: notes || null,
|
||||
@@ -903,19 +1111,143 @@ function openAddVariantModal(drugId) {
|
||||
if (!drug) return;
|
||||
|
||||
currentDrug = drug;
|
||||
const form = document.getElementById('variantForm');
|
||||
if (form) form.reset();
|
||||
document.getElementById('variantDrugId').value = drug.id;
|
||||
initializeVariantPackRows();
|
||||
document.getElementById('addVariantModal').classList.add('show');
|
||||
}
|
||||
|
||||
function inferBaseUnitFromStrength(strength) {
|
||||
const value = String(strength || '').toLowerCase();
|
||||
if (value.includes('/ml') || value.includes('ml')) return 'ml';
|
||||
if (value.includes('tablet')) return 'tablets';
|
||||
if (value.includes('capsule')) return 'capsules';
|
||||
return 'units';
|
||||
}
|
||||
|
||||
function getVariantPackRowsContainer() {
|
||||
return document.getElementById('variantPackRows');
|
||||
}
|
||||
|
||||
function refreshVariantPackRowLabels() {
|
||||
const container = getVariantPackRowsContainer();
|
||||
const baseUnit = document.getElementById('variantUnit')?.value || 'units';
|
||||
if (!container) return;
|
||||
|
||||
container.querySelectorAll('.variant-pack-row').forEach(row => {
|
||||
const packUnit = row.querySelector('.variant-pack-unit')?.value || 'pack';
|
||||
const label = row.querySelector('.variant-pack-size-label');
|
||||
if (!label) return;
|
||||
const titleCasePack = packUnit.charAt(0).toUpperCase() + packUnit.slice(1);
|
||||
label.textContent = `${titleCasePack} Size (${baseUnit}) *`;
|
||||
});
|
||||
}
|
||||
|
||||
function appendVariantPackRow(prefill = {}) {
|
||||
const container = getVariantPackRowsContainer();
|
||||
if (!container) return;
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.className = 'delivery-line variant-pack-row';
|
||||
|
||||
const selectedPackUnit = prefill.packUnit || 'bottle';
|
||||
const selectedSize = prefill.packSize || '';
|
||||
const baseUnit = document.getElementById('variantUnit')?.value || 'units';
|
||||
|
||||
row.innerHTML = `
|
||||
<div class="delivery-line-grid" style="grid-template-columns: 1.2fr 1.2fr auto;">
|
||||
<div class="form-group">
|
||||
<label>Pack Type *</label>
|
||||
<select class="variant-pack-unit" required>
|
||||
<option value="bottle" ${selectedPackUnit === 'bottle' ? 'selected' : ''}>Bottle</option>
|
||||
<option value="box" ${selectedPackUnit === 'box' ? 'selected' : ''}>Box</option>
|
||||
<option value="vial" ${selectedPackUnit === 'vial' ? 'selected' : ''}>Vial</option>
|
||||
<option value="packet" ${selectedPackUnit === 'packet' ? 'selected' : ''}>Packet</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="variant-pack-size-label">Bottle Size (${baseUnit}) *</label>
|
||||
<input type="number" class="variant-pack-size" min="0.0001" step="0.0001" value="${selectedSize}" required>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-small variant-pack-remove-btn">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const removeBtn = row.querySelector('.variant-pack-remove-btn');
|
||||
const unitSelect = row.querySelector('.variant-pack-unit');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', () => {
|
||||
if (container.querySelectorAll('.variant-pack-row').length <= 1) {
|
||||
showToast('At least one pack size is required', 'warning');
|
||||
return;
|
||||
}
|
||||
row.remove();
|
||||
});
|
||||
}
|
||||
|
||||
if (unitSelect) {
|
||||
unitSelect.addEventListener('change', refreshVariantPackRowLabels);
|
||||
}
|
||||
|
||||
container.appendChild(row);
|
||||
refreshVariantPackRowLabels();
|
||||
}
|
||||
|
||||
function initializeVariantPackRows() {
|
||||
const container = getVariantPackRowsContainer();
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
|
||||
const strengthValue = document.getElementById('variantStrength')?.value || '';
|
||||
const inferredBaseUnit = inferBaseUnitFromStrength(strengthValue);
|
||||
const variantUnitSelect = document.getElementById('variantUnit');
|
||||
if (variantUnitSelect) {
|
||||
variantUnitSelect.value = inferredBaseUnit;
|
||||
}
|
||||
|
||||
appendVariantPackRow({ packUnit: 'bottle' });
|
||||
}
|
||||
|
||||
// Handle add variant form
|
||||
async function handleAddVariant(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const drugId = parseInt(document.getElementById('variantDrugId').value);
|
||||
const baseUnit = document.getElementById('variantUnit').value;
|
||||
const rows = Array.from(document.querySelectorAll('#variantPackRows .variant-pack-row'));
|
||||
|
||||
if (rows.length === 0) {
|
||||
showToast('Please add at least one pack size', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const packPayloads = [];
|
||||
for (let i = 0; i < rows.length; i += 1) {
|
||||
const row = rows[i];
|
||||
const packUnit = row.querySelector('.variant-pack-unit')?.value;
|
||||
const packSize = parseFloat(row.querySelector('.variant-pack-size')?.value || '');
|
||||
|
||||
if (!packUnit || Number.isNaN(packSize) || packSize <= 0) {
|
||||
showToast(`Pack row ${i + 1} is incomplete`, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedPackUnit = packUnit.trim().toLowerCase();
|
||||
const titleCasePack = normalizedPackUnit.charAt(0).toUpperCase() + normalizedPackUnit.slice(1);
|
||||
packPayloads.push({
|
||||
label: `${titleCasePack} ${packSize} ${baseUnit}`,
|
||||
pack_unit_name: normalizedPackUnit,
|
||||
pack_size_in_base_units: packSize,
|
||||
is_active: true
|
||||
});
|
||||
}
|
||||
|
||||
const variantData = {
|
||||
strength: document.getElementById('variantStrength').value,
|
||||
quantity: parseFloat(document.getElementById('variantQuantity').value),
|
||||
unit: document.getElementById('variantUnit').value,
|
||||
quantity: 0,
|
||||
unit: baseUnit,
|
||||
base_unit: baseUnit,
|
||||
low_stock_threshold: parseFloat(document.getElementById('variantThreshold').value)
|
||||
};
|
||||
|
||||
@@ -927,9 +1259,41 @@ async function handleAddVariant(e) {
|
||||
|
||||
if (!response.ok) throw new Error('Failed to add variant');
|
||||
|
||||
const createdVariant = await response.json();
|
||||
|
||||
for (const packPayload of packPayloads) {
|
||||
const packResponse = await apiCall(`/variants/${createdVariant.id}/packs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(packPayload)
|
||||
});
|
||||
|
||||
if (!packResponse.ok) {
|
||||
const packError = await packResponse.json();
|
||||
throw new Error(packError.detail || 'Variant created but pack size creation failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Archive the auto-created default 1:1 pack when custom pack sizes are configured.
|
||||
const packsResponse = await apiCall(`/variants/${createdVariant.id}/packs`);
|
||||
if (packsResponse.ok) {
|
||||
const packs = await packsResponse.json();
|
||||
const defaultPack = packs.find(
|
||||
p => p.is_active && Number(p.pack_size_in_base_units) === 1 && (p.pack_unit_name || '').toLowerCase() === baseUnit.toLowerCase()
|
||||
);
|
||||
if (defaultPack && packs.filter(p => p.is_active).length > 1) {
|
||||
await apiCall(`/variant-packs/${defaultPack.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ is_active: false })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('variantForm').reset();
|
||||
closeModal(document.getElementById('addVariantModal'));
|
||||
await loadDrugs();
|
||||
if (document.getElementById('receiveDeliveryModal')?.classList.contains('show')) {
|
||||
refreshDeliveryVariantSelects();
|
||||
}
|
||||
renderDrugs();
|
||||
showToast('Variant added successfully!', 'success');
|
||||
} catch (error) {
|
||||
@@ -958,6 +1322,13 @@ function openEditVariantModal(variantId) {
|
||||
document.getElementById('editVariantModal').classList.add('show');
|
||||
}
|
||||
|
||||
function inferPackUnitName(baseUnit) {
|
||||
const value = String(baseUnit || 'pack').trim().toLowerCase();
|
||||
if (!value) return 'pack';
|
||||
if (value.endsWith('s') && value.length > 1) return value.slice(0, -1);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Handle edit variant form
|
||||
async function handleEditVariant(e) {
|
||||
e.preventDefault();
|
||||
@@ -988,6 +1359,140 @@ async function handleEditVariant(e) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openVariantPacksModal(variantId) {
|
||||
const variant = getVariantById(variantId);
|
||||
if (!variant) {
|
||||
showToast('Variant not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const parentDrug = allDrugs.find(d => (d.variants || []).some(v => v.id === variantId));
|
||||
activeVariantPacksVariantId = variantId;
|
||||
|
||||
const label = document.getElementById('variantPacksLabel');
|
||||
if (label) {
|
||||
const drugName = parentDrug ? parentDrug.name : 'Drug';
|
||||
label.textContent = `${drugName} ${variant.strength} | Base Unit: ${variant.unit}`;
|
||||
}
|
||||
|
||||
const form = document.getElementById('variantPacksForm');
|
||||
if (form) form.reset();
|
||||
document.getElementById('variantPacksVariantId').value = String(variantId);
|
||||
document.getElementById('variantPacksNewUnit').value = inferPackUnitName(variant.unit);
|
||||
|
||||
await refreshVariantPacksList();
|
||||
openModal(document.getElementById('variantPacksModal'));
|
||||
}
|
||||
|
||||
async function refreshVariantPacksList() {
|
||||
const variantId = parseInt(document.getElementById('variantPacksVariantId')?.value || '', 10);
|
||||
const list = document.getElementById('variantPacksList');
|
||||
if (!variantId || !list) return;
|
||||
|
||||
list.innerHTML = '<p class="loading">Loading packs...</p>';
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/variants/${variantId}/packs`);
|
||||
if (!response.ok) throw new Error('Failed to load pack presentations');
|
||||
const packs = await response.json();
|
||||
|
||||
if (!Array.isArray(packs) || packs.length === 0) {
|
||||
list.innerHTML = '<p class="empty">No pack presentations defined.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = `
|
||||
<div class="locations-table">
|
||||
${packs.map(pack => `
|
||||
<div class="location-item" style="${pack.is_active ? '' : 'opacity: 0.6;'}">
|
||||
<div style="flex: 1;">
|
||||
<strong>${escapeHtml(pack.label)}</strong>
|
||||
<div style="font-size: 0.88em; color: #666;">
|
||||
${escapeHtml(pack.pack_unit_name)} | ${pack.pack_size_in_base_units} base units
|
||||
${pack.is_active ? '' : ' | archived'}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn ${pack.is_active ? 'btn-danger' : 'btn-secondary'} btn-small" onclick="toggleVariantPackActive(${pack.id}, ${pack.is_active ? 'false' : 'true'})">
|
||||
${pack.is_active ? 'Archive' : 'Restore'}
|
||||
</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error loading variant packs:', error);
|
||||
list.innerHTML = '<p class="empty">Failed to load pack presentations</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateVariantPack(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const variantId = parseInt(document.getElementById('variantPacksVariantId').value, 10);
|
||||
const label = document.getElementById('variantPacksNewLabel').value.trim();
|
||||
const packUnitName = document.getElementById('variantPacksNewUnit').value.trim();
|
||||
const size = parseFloat(document.getElementById('variantPacksNewSize').value);
|
||||
|
||||
if (!variantId || !label || !packUnitName || Number.isNaN(size) || size <= 0) {
|
||||
showToast('Please complete all pack fields', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/variants/${variantId}/packs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
label,
|
||||
pack_unit_name: packUnitName,
|
||||
pack_size_in_base_units: size,
|
||||
is_active: true
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to add pack presentation');
|
||||
}
|
||||
|
||||
document.getElementById('variantPacksForm').reset();
|
||||
const variant = getVariantById(variantId);
|
||||
document.getElementById('variantPacksNewUnit').value = inferPackUnitName(variant?.unit || 'pack');
|
||||
await loadDrugs();
|
||||
await refreshVariantPacksList();
|
||||
if (document.getElementById('receiveDeliveryModal')?.classList.contains('show')) {
|
||||
refreshDeliveryVariantSelects();
|
||||
}
|
||||
showToast('Pack presentation added', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error creating pack presentation:', error);
|
||||
showToast('Failed to add pack presentation: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleVariantPackActive(packId, nextActiveState) {
|
||||
try {
|
||||
const response = await apiCall(`/variant-packs/${packId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ is_active: Boolean(nextActiveState) })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to update pack state');
|
||||
}
|
||||
|
||||
await loadDrugs();
|
||||
await refreshVariantPacksList();
|
||||
if (document.getElementById('receiveDeliveryModal')?.classList.contains('show')) {
|
||||
refreshDeliveryVariantSelects();
|
||||
}
|
||||
showToast('Pack updated', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error updating pack state:', error);
|
||||
showToast('Failed to update pack: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Dispense from variant
|
||||
function dispenseVariant(variantId) {
|
||||
// Update the dropdown display with all variants
|
||||
@@ -1751,3 +2256,306 @@ async function handleBatchReceive(e) {
|
||||
showToast('Failed to receive batch: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveDeliveryDrug() {
|
||||
return allDrugs.find(d => d.id === deliveryDrugId);
|
||||
}
|
||||
|
||||
function getVariantById(variantId) {
|
||||
for (const drug of allDrugs) {
|
||||
const found = (drug.variants || []).find(v => v.id === variantId);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildDeliveryVariantOptions(drug, selectedVariantId = '') {
|
||||
if (!drug || !drug.variants || drug.variants.length === 0) {
|
||||
return '<option value="">-- No variants available --</option>';
|
||||
}
|
||||
|
||||
return [`<option value="">-- Select variant --</option>`, ...drug.variants.map(v => {
|
||||
const selected = String(v.id) === String(selectedVariantId) ? ' selected' : '';
|
||||
return `<option value="${v.id}"${selected}>${escapeHtml(v.strength)} (${escapeHtml(v.unit)})</option>`;
|
||||
})].join('');
|
||||
}
|
||||
|
||||
function getActivePacksForVariant(variant) {
|
||||
if (!variant || !Array.isArray(variant.packs)) return [];
|
||||
return variant.packs.filter(pack => pack.is_active);
|
||||
}
|
||||
|
||||
function buildDeliveryPackOptions(variant, selectedPackId = '') {
|
||||
const packs = getActivePacksForVariant(variant);
|
||||
if (packs.length === 0) {
|
||||
return '<option value="">-- No active packs --</option>';
|
||||
}
|
||||
|
||||
return [`<option value="">-- Select pack --</option>`, ...packs.map(pack => {
|
||||
const selected = String(pack.id) === String(selectedPackId) ? ' selected' : '';
|
||||
const label = `${pack.label} (${pack.pack_size_in_base_units} ${variant.unit})`;
|
||||
return `<option value="${pack.id}"${selected}>${escapeHtml(label)}</option>`;
|
||||
})].join('');
|
||||
}
|
||||
|
||||
function buildDeliveryLocationOptions(selectedLocationId = '') {
|
||||
return [`<option value="">-- Select location --</option>`, ...deliveryLocations.map(location => {
|
||||
const selected = String(location.id) === String(selectedLocationId) ? ' selected' : '';
|
||||
return `<option value="${location.id}"${selected}>${escapeHtml(location.name)}</option>`;
|
||||
})].join('');
|
||||
}
|
||||
|
||||
function updateDeliveryLineQuantityDisplay(line) {
|
||||
const variantId = parseInt(line.querySelector('.delivery-variant-select')?.value || '', 10);
|
||||
const packSelect = line.querySelector('.delivery-pack-select');
|
||||
|
||||
const variant = getVariantById(variantId);
|
||||
if (!variant || !packSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPackId = packSelect.value;
|
||||
packSelect.innerHTML = buildDeliveryPackOptions(variant, currentPackId);
|
||||
}
|
||||
|
||||
function wireDeliveryLineEvents(line) {
|
||||
const variantSelect = line.querySelector('.delivery-variant-select');
|
||||
const packSelect = line.querySelector('.delivery-pack-select');
|
||||
const packCountInput = line.querySelector('.delivery-pack-count');
|
||||
|
||||
if (variantSelect && packSelect) {
|
||||
variantSelect.addEventListener('change', () => {
|
||||
const variantId = parseInt(variantSelect.value || '', 10);
|
||||
const variant = getVariantById(variantId);
|
||||
packSelect.innerHTML = buildDeliveryPackOptions(variant, '');
|
||||
if (packCountInput) packCountInput.value = '';
|
||||
updateDeliveryLineQuantityDisplay(line);
|
||||
});
|
||||
}
|
||||
|
||||
if (packSelect) {
|
||||
packSelect.addEventListener('change', () => {
|
||||
updateDeliveryLineQuantityDisplay(line);
|
||||
});
|
||||
}
|
||||
|
||||
if (packCountInput) {
|
||||
packCountInput.addEventListener('input', () => {
|
||||
updateDeliveryLineQuantityDisplay(line);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function appendDeliveryLine(prefill = {}) {
|
||||
const container = document.getElementById('deliveryLinesContainer');
|
||||
const drug = getActiveDeliveryDrug();
|
||||
if (!container || !drug) return;
|
||||
|
||||
deliveryLineCounter += 1;
|
||||
const lineId = `delivery-line-${deliveryLineCounter}`;
|
||||
|
||||
const line = document.createElement('div');
|
||||
line.className = 'delivery-line';
|
||||
line.dataset.lineId = lineId;
|
||||
|
||||
const initialVariant = drug.variants.find(v => String(v.id) === String(prefill.variantId)) || drug.variants[0] || null;
|
||||
const initialVariantId = prefill.variantId || (initialVariant ? initialVariant.id : '');
|
||||
const initialPackId = prefill.packId || '';
|
||||
const initialPackCount = prefill.packCount || '';
|
||||
|
||||
line.innerHTML = `
|
||||
<div class="delivery-line-grid">
|
||||
<div class="form-group">
|
||||
<label>Variant</label>
|
||||
<select class="delivery-variant-select" required>
|
||||
${buildDeliveryVariantOptions(drug, initialVariantId)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pack Type</label>
|
||||
<select class="delivery-pack-select" required>
|
||||
${buildDeliveryPackOptions(initialVariant, initialPackId)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pack Count</label>
|
||||
<input type="number" class="delivery-pack-count" min="0.0001" step="0.0001" value="${initialPackCount}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Batch Number</label>
|
||||
<input type="text" class="delivery-batch-number" value="${prefill.batchNumber || ''}" placeholder="e.g. ABC123" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Expiry</label>
|
||||
<input type="date" class="delivery-expiry-date" value="${prefill.expiryDate || ''}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Location</label>
|
||||
<select class="delivery-location-select" required>
|
||||
${buildDeliveryLocationOptions(prefill.locationId || '')}
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-small delivery-remove-btn">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const removeBtn = line.querySelector('.delivery-remove-btn');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', () => {
|
||||
if (container.children.length <= 1) {
|
||||
showToast('At least one delivery line is required', 'warning');
|
||||
return;
|
||||
}
|
||||
line.remove();
|
||||
});
|
||||
}
|
||||
|
||||
wireDeliveryLineEvents(line);
|
||||
updateDeliveryLineQuantityDisplay(line);
|
||||
|
||||
container.appendChild(line);
|
||||
}
|
||||
|
||||
function refreshDeliveryVariantSelects() {
|
||||
const drug = getActiveDeliveryDrug();
|
||||
const container = document.getElementById('deliveryLinesContainer');
|
||||
if (!drug || !container) return;
|
||||
|
||||
container.querySelectorAll('.delivery-line').forEach(line => {
|
||||
const select = line.querySelector('.delivery-variant-select');
|
||||
const packSelect = line.querySelector('.delivery-pack-select');
|
||||
if (!select) return;
|
||||
|
||||
const currentVariantId = select.value;
|
||||
select.innerHTML = buildDeliveryVariantOptions(drug, currentVariantId);
|
||||
|
||||
const variant = getVariantById(parseInt(select.value || '', 10));
|
||||
if (packSelect) {
|
||||
const currentPackId = packSelect.value;
|
||||
packSelect.innerHTML = buildDeliveryPackOptions(variant, currentPackId);
|
||||
}
|
||||
|
||||
updateDeliveryLineQuantityDisplay(line);
|
||||
});
|
||||
}
|
||||
|
||||
async function initializeDeliveryLocations() {
|
||||
try {
|
||||
const response = await apiCall('/locations');
|
||||
if (!response.ok) throw new Error('Failed to load locations');
|
||||
const locations = await response.json();
|
||||
deliveryLocations = locations.filter(location => location.is_active);
|
||||
} catch (error) {
|
||||
console.error('Error loading delivery locations:', error);
|
||||
showToast('Failed to load storage locations', 'error');
|
||||
deliveryLocations = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function openReceiveDeliveryModal(drugId) {
|
||||
deliveryDrugId = drugId;
|
||||
const drug = getActiveDeliveryDrug();
|
||||
if (!drug) {
|
||||
showToast('Drug not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('receiveDeliveryForm');
|
||||
const container = document.getElementById('deliveryLinesContainer');
|
||||
const label = document.getElementById('receiveDeliveryDrugLabel');
|
||||
|
||||
if (form) form.reset();
|
||||
if (container) container.innerHTML = '';
|
||||
if (label) label.textContent = `Drug: ${drug.name}`;
|
||||
|
||||
await initializeDeliveryLocations();
|
||||
appendDeliveryLine();
|
||||
|
||||
openModal(document.getElementById('receiveDeliveryModal'));
|
||||
}
|
||||
|
||||
function handleAddVariantFromDelivery() {
|
||||
if (!deliveryDrugId) {
|
||||
showToast('Select a drug first', 'warning');
|
||||
return;
|
||||
}
|
||||
openAddVariantModal(deliveryDrugId);
|
||||
}
|
||||
|
||||
async function handleReceiveDelivery(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const drug = getActiveDeliveryDrug();
|
||||
const container = document.getElementById('deliveryLinesContainer');
|
||||
if (!drug || !container) {
|
||||
showToast('Delivery context unavailable', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = Array.from(container.querySelectorAll('.delivery-line'));
|
||||
if (lines.length === 0) {
|
||||
showToast('Add at least one delivery line', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const payloads = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
const line = lines[i];
|
||||
const variantId = parseInt(line.querySelector('.delivery-variant-select')?.value || '', 10);
|
||||
const packIdRaw = line.querySelector('.delivery-pack-select')?.value || '';
|
||||
const packId = packIdRaw ? parseInt(packIdRaw, 10) : null;
|
||||
const packCountRaw = line.querySelector('.delivery-pack-count')?.value || '';
|
||||
const packCount = packCountRaw ? parseFloat(packCountRaw) : null;
|
||||
const batchNumber = (line.querySelector('.delivery-batch-number')?.value || '').trim();
|
||||
const expiryDate = line.querySelector('.delivery-expiry-date')?.value || '';
|
||||
const locationId = parseInt(line.querySelector('.delivery-location-select')?.value || '', 10);
|
||||
|
||||
if (!variantId || !packId || packCount === null || Number.isNaN(packCount) || packCount <= 0 || !batchNumber || !expiryDate || !locationId) {
|
||||
showToast(`Delivery line ${i + 1} is incomplete`, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const variant = drug.variants.find(v => v.id === variantId);
|
||||
const selectedPack = variant ? getActivePacksForVariant(variant).find(pack => pack.id === packId) : null;
|
||||
if (!selectedPack) {
|
||||
showToast(`Delivery line ${i + 1} has an invalid pack selection`, 'warning');
|
||||
return;
|
||||
}
|
||||
const computedQuantity = packCount * selectedPack.pack_size_in_base_units;
|
||||
|
||||
payloads.push({
|
||||
variantId,
|
||||
payload: {
|
||||
batch_number: batchNumber,
|
||||
received_pack_id: packId,
|
||||
received_pack_count: packCount,
|
||||
expiry_date: expiryDate,
|
||||
location_id: locationId,
|
||||
notes: `Received ${packCount} ${selectedPack.pack_unit_name}(s), total ${computedQuantity} ${variant ? variant.unit : 'units'}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
for (let i = 0; i < payloads.length; i += 1) {
|
||||
const entry = payloads[i];
|
||||
const response = await apiCall(`/variants/${entry.variantId}/batches`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(entry.payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(`Line ${i + 1}: ${error.detail || 'Failed to receive delivery line'}`);
|
||||
}
|
||||
}
|
||||
|
||||
closeModal(document.getElementById('receiveDeliveryModal'));
|
||||
await loadDrugs();
|
||||
showToast(`Delivery received successfully (${payloads.length} line${payloads.length === 1 ? '' : 's'})`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Error receiving delivery:', error);
|
||||
showToast('Failed to receive delivery: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user