Batch disposal

This commit is contained in:
2026-04-06 10:41:33 -04:00
parent 5b5e17ec3e
commit b958ca493b
7 changed files with 620 additions and 65 deletions
+325 -39
View File
@@ -12,6 +12,26 @@ let deliveryDrugId = null;
let deliveryLineCounter = 0;
let deliveryLocations = [];
let currentDispenseBatches = [];
let currentDispenseLegacyQuantity = 0;
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();
const modal = document.getElementById('disposeBatchModal');
if (modal) {
closeModal(modal);
}
}
// Toast notification system
function showToast(message, type = 'info', duration = 3000) {
@@ -209,6 +229,7 @@ function setupEventListeners() {
const prescribeForm = document.getElementById('prescribeForm');
const editForm = document.getElementById('editForm');
const printNotesForm = document.getElementById('printNotesForm');
const disposeBatchForm = document.getElementById('disposeBatchForm');
const addModal = document.getElementById('addModal');
const addVariantModal = document.getElementById('addVariantModal');
const editVariantModal = document.getElementById('editVariantModal');
@@ -216,6 +237,7 @@ function setupEventListeners() {
const prescribeModal = document.getElementById('prescribeModal');
const editModal = document.getElementById('editModal');
const printNotesModal = document.getElementById('printNotesModal');
const disposeBatchModal = document.getElementById('disposeBatchModal');
const batchReceiveModal = document.getElementById('batchReceiveModal');
const receiveDeliveryModal = document.getElementById('receiveDeliveryModal');
const addDrugBtn = document.getElementById('addDrugBtn');
@@ -227,6 +249,7 @@ function setupEventListeners() {
const cancelDispenseBtn = document.getElementById('cancelDispenseBtn');
const cancelPrescribeBtn = document.getElementById('cancelPrescribeBtn');
const cancelEditBtn = document.getElementById('cancelEditBtn');
const cancelDisposeBatchBtn = document.getElementById('cancelDisposeBatchBtn');
const cancelBatchReceiveBtn = document.getElementById('cancelBatchReceiveBtn');
const cancelReceiveDeliveryBtn = document.getElementById('cancelReceiveDeliveryBtn');
const addDeliveryLineBtn = document.getElementById('addDeliveryLineBtn');
@@ -237,6 +260,7 @@ function setupEventListeners() {
const variantStrengthInput = document.getElementById('variantStrength');
const editVariantUnitSelect = document.getElementById('editVariantUnit');
const dispenseModeInputs = document.querySelectorAll('input[name="dispenseMode"]');
const dispensePrintEnabled = document.getElementById('dispensePrintEnabled');
const showAllBtn = document.getElementById('showAllBtn');
const showLowStockBtn = document.getElementById('showLowStockBtn');
const locationFilterSelect = document.getElementById('locationFilterSelect');
@@ -257,6 +281,7 @@ function setupEventListeners() {
if (prescribeForm) prescribeForm.addEventListener('submit', handlePrescribeDrug);
if (editForm) editForm.addEventListener('submit', handleEditDrug);
if (printNotesForm) printNotesForm.addEventListener('submit', handlePrintNotes);
if (disposeBatchForm) disposeBatchForm.addEventListener('submit', handleDisposeBatch);
const batchReceiveForm = document.getElementById('batchReceiveForm');
if (batchReceiveForm) batchReceiveForm.addEventListener('submit', handleBatchReceive);
@@ -286,6 +311,9 @@ function setupEventListeners() {
});
}
dispenseModeInputs.forEach(input => input.addEventListener('change', updateDispenseModeUi));
if (dispensePrintEnabled) {
dispensePrintEnabled.addEventListener('change', toggleDispensePrintFields);
}
if (addDrugBtn) addDrugBtn.addEventListener('click', () => openModal(addModal));
if (printNotesBtn) printNotesBtn.addEventListener('click', () => openModal(printNotesModal));
@@ -301,6 +329,7 @@ function setupEventListeners() {
if (cancelDispenseBtn) cancelDispenseBtn.addEventListener('click', () => closeModal(dispenseModal));
if (cancelPrescribeBtn) cancelPrescribeBtn.addEventListener('click', () => closeModal(prescribeModal));
if (cancelEditBtn) cancelEditBtn.addEventListener('click', closeEditModal);
if (cancelDisposeBatchBtn) cancelDisposeBatchBtn.addEventListener('click', closeDisposeBatchModal);
const cancelPrintNotesBtn = document.getElementById('cancelPrintNotesBtn');
if (cancelPrintNotesBtn) cancelPrintNotesBtn.addEventListener('click', () => closeModal(printNotesModal));
@@ -331,6 +360,9 @@ function setupEventListeners() {
closeButtons.forEach(btn => btn.addEventListener('click', (e) => {
const modal = e.target.closest('.modal');
if (modal?.id === 'disposeBatchModal') {
resetDisposeBatchModal();
}
closeModal(modal);
}));
@@ -401,6 +433,9 @@ function setupEventListeners() {
// Close modal when clicking outside
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
if (e.target.id === 'disposeBatchModal') {
resetDisposeBatchModal();
}
closeModal(e.target);
}
});
@@ -481,7 +516,10 @@ function updateDispenseDrugSelect() {
packPreview.textContent = 'Select a pack and whole-number count.';
}
resetDispensePrintFields();
currentDispenseBatches = [];
currentDispenseLegacyQuantity = 0;
updateDispenseModeUi();
}
@@ -490,6 +528,120 @@ function getSelectedDispenseMode() {
return document.querySelector('input[name="dispenseMode"]:checked')?.value || 'subunit';
}
function hasLegacyDispenseStock() {
return currentDispenseBatches.length === 0 && currentDispenseLegacyQuantity > 0;
}
function getDefaultLabelExpiryDate() {
const defaultExpiry = new Date();
defaultExpiry.setMonth(defaultExpiry.getMonth() + 1);
return defaultExpiry.toISOString().split('T')[0];
}
function toggleDispensePrintFields() {
const printEnabled = document.getElementById('dispensePrintEnabled');
const printFields = document.getElementById('dispensePrintFields');
const printHelpText = document.getElementById('dispensePrintHelpText');
const dosageInput = document.getElementById('dispenseDosage');
const legacyExpiryGroup = document.getElementById('dispenseLegacyExpiryGroup');
const legacyExpiryInput = document.getElementById('dispenseLegacyExpiry');
const isEnabled = Boolean(printEnabled?.checked);
const legacyStockOnly = hasLegacyDispenseStock();
if (printFields) {
printFields.style.display = isEnabled ? '' : 'none';
}
if (printHelpText) {
printHelpText.textContent = legacyStockOnly
? 'Uses the dispensed quantity, the animal name/ID entered above, the logged-in user, and a manually entered expiry date for this legacy stock.'
: 'Uses the dispensed quantity, the animal name/ID entered above, the logged-in user, and the latest expiry date from the allocated batches.';
}
if (dosageInput) {
dosageInput.required = isEnabled;
}
if (legacyExpiryGroup) {
legacyExpiryGroup.style.display = isEnabled && legacyStockOnly ? '' : 'none';
}
if (legacyExpiryInput) {
legacyExpiryInput.required = isEnabled && legacyStockOnly;
if (!legacyStockOnly) {
legacyExpiryInput.value = '';
}
}
}
function resetDispensePrintFields() {
const printEnabled = document.getElementById('dispensePrintEnabled');
const dosageInput = document.getElementById('dispenseDosage');
const legacyExpiryInput = document.getElementById('dispenseLegacyExpiry');
if (printEnabled) {
printEnabled.checked = false;
}
if (dosageInput) {
dosageInput.value = '';
}
if (legacyExpiryInput) {
legacyExpiryInput.value = '';
}
toggleDispensePrintFields();
}
function formatLabelExpiryDate(expiryDate) {
const expiryParts = expiryDate.split('-');
return `${expiryParts[2]}/${expiryParts[1]}/${expiryParts[0]}`;
}
function getDrugContextForVariant(variantId) {
for (const drug of allDrugs) {
const variant = (drug.variants || []).find(item => item.id === variantId);
if (variant) {
return { drug, variant };
}
}
return { drug: null, variant: null };
}
function getLatestAllocatedBatchExpiryDate(allocationEntries) {
const allocatedBatches = allocationEntries
.map(entry => currentDispenseBatches.find(batch => batch.id === entry.batch_id))
.filter(batch => batch?.expiry_date);
if (allocatedBatches.length === 0) {
return null;
}
return allocatedBatches
.map(batch => batch.expiry_date)
.sort((left, right) => new Date(right) - new Date(left))[0];
}
async function requestLabelPrint({ animalName, drugName, variantStrength, quantity, unit, dosage, expiryDate }) {
const labelData = {
variables: {
practice_name: 'Many Tears Animal Rescue',
animal_name: animalName,
drug_name: `${drugName} ${variantStrength}`,
dosage,
quantity: `${quantity} ${unit}`,
expiry_date: formatLabelExpiryDate(expiryDate)
}
};
const labelResponse = await apiCall('/labels/print', {
method: 'POST',
body: JSON.stringify(labelData)
});
if (!labelResponse.ok) {
const error = await labelResponse.json();
throw new Error(error.detail || 'Label printing request failed');
}
return labelResponse.json();
}
function populateDispensePackSelect(variant) {
const packSelect = document.getElementById('dispensePackSelect');
const packCount = document.getElementById('dispensePackCount');
@@ -622,6 +774,7 @@ function isBatchExpired(batch) {
function renderVariantInventoryDetails(variant) {
const activePacks = getActivePacksForVariant(variant);
const isReadOnly = currentUser?.role === 'readonly';
const batches = [...(variant.batches || [])]
.filter(batch => Number(batch.quantity) > 0)
.sort((a, b) => new Date(a.expiry_date) - new Date(b.expiry_date));
@@ -638,18 +791,31 @@ function renderVariantInventoryDetails(variant) {
const batchesHtml = batches.length > 0
? batches.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_label;
const stocktakeLabel = hasPackState
? `${formatDisplayNumber(batch.current_full_pack_count)} full ${escapeHtml(batch.received_pack_label)} + ${formatDisplayNumber(batch.current_loose_base_units)} ${escapeHtml(variant.unit)} loose`
: `${formatDisplayNumber(batch.quantity)} ${escapeHtml(variant.unit)}`;
const batchCardStyles = expired
? 'padding: 8px; background: #fff1f2; border: 1px solid #f3a6ad; border-radius: 5px; font-size: 0.9em;'
: 'padding: 8px; background: #ffffff; border: 1px solid #d8e2ea; border-radius: 5px; font-size: 0.9em;';
const expiryStyles = expired ? 'color: #b91c1c; font-weight: 700;' : 'color: #4b5563;';
return `
<div style="padding: 8px; background: #ffffff; border: 1px solid #d8e2ea; border-radius: 5px; font-size: 0.9em;">
<div style="${batchCardStyles}">
<div style="display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
<strong>${escapeHtml(batch.batch_number)}</strong>
<span style="color: #4b5563;">Expires ${formatDisplayDate(batch.expiry_date)}</span>
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<strong>${escapeHtml(batch.batch_number)}</strong>
${expired ? '<span style="background: #b91c1c; color: white; padding: 2px 6px; border-radius: 999px; font-size: 0.75em; font-weight: 700;">Expired</span>' : ''}
</div>
<span style="${expiryStyles}">Expires ${formatDisplayDate(batch.expiry_date)}</span>
</div>
<div style="margin-top: 4px; color: #374151;">${escapeHtml(locationLabel)} | ${stocktakeLabel}</div>
${expired && !isReadOnly ? `
<div style="margin-top: 8px; display: flex; justify-content: flex-end;">
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); disposeBatch(${batch.id}, '${String(batch.batch_number).replace(/'/g, "\\'")}')">Dispose Expired Batch</button>
</div>
` : ''}
</div>
`;
}).join('')
@@ -671,6 +837,57 @@ function renderVariantInventoryDetails(variant) {
`;
}
function disposeBatch(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() || '';
const modal = document.getElementById('disposeBatchModal');
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');
}
if (modal) {
closeDisposeBatchModal();
}
await loadDrugs();
showToast('Expired batch marked as disposed.', 'success');
} catch (error) {
console.error('Error disposing batch:', error);
showToast('Failed to dispose batch: ' + error.message, 'error');
}
}
function getBatchLocationLabel(batch) {
return batch.location_name || batch.location?.name || `Location #${batch.location_id}`;
}
@@ -765,10 +982,16 @@ function getBatchAvailableDispenseQuantity(batch, mode = getSelectedDispenseMode
}
function getTotalAvailableDispenseQuantity(mode = getSelectedDispenseMode(), selectedPack = getSelectedDispensePack()) {
if (hasLegacyDispenseStock()) {
return mode === 'pack' ? 0 : currentDispenseLegacyQuantity;
}
return currentDispenseBatches.reduce((sum, batch) => sum + getBatchAvailableDispenseQuantity(batch, mode, selectedPack), 0);
}
function getTotalAvailableDispensePackCount(selectedPack = getSelectedDispensePack()) {
if (hasLegacyDispenseStock()) {
return 0;
}
if (!selectedPack) {
return 0;
}
@@ -922,7 +1145,9 @@ async function updateBatchInfo() {
const packSelect = document.getElementById('dispensePackSelect');
if (packSelect) packSelect.innerHTML = '<option value="">-- Select pack --</option>';
currentDispenseBatches = [];
currentDispenseLegacyQuantity = 0;
renderExpiredDispenseBatches([]);
toggleDispensePrintFields();
updateDispenseAllocationSummary();
return;
}
@@ -946,13 +1171,20 @@ async function updateBatchInfo() {
const stockedBatches = batches.filter(b => b.quantity > 0);
const expiredBatches = stockedBatches.filter(isBatchExpired);
const activeBatches = stockedBatches.filter(batch => !isBatchExpired(batch));
const totalBatchQuantity = stockedBatches.reduce((sum, batch) => sum + Number(batch.quantity || 0), 0);
currentDispenseLegacyQuantity = Math.max(0, Number(variant?.quantity || 0) - totalBatchQuantity);
currentDispenseBatches = activeBatches;
renderExpiredDispenseBatches(expiredBatches);
if (activeBatches.length === 0) {
batchInfoContent.innerHTML = expiredBatches.length > 0
? '<p style="color: #d32f2f; margin: 0;">⚠️ No in-date batches available for this variant. Expired batches are hidden from selection.</p>'
: '<p style="color: #d32f2f; margin: 0;">⚠️ No active batches available for this variant</p>';
if (currentDispenseLegacyQuantity > 0) {
batchInfoContent.innerHTML = `<div style="padding: 10px; background: #fff8e8; border: 1px solid #f3d18d; border-radius: 4px; color: #7a4f01;"><strong>Legacy stock only.</strong> ${formatDisplayNumber(currentDispenseLegacyQuantity)} ${escapeHtml(variant?.unit || 'units')} available outside the batch system. Dispense by quantity only; whole-pack allocation is unavailable.</div>`;
} else {
batchInfoContent.innerHTML = expiredBatches.length > 0
? '<p style="color: #d32f2f; margin: 0;">⚠️ No in-date batches available for this variant. Expired batches are hidden from selection.</p>'
: '<p style="color: #d32f2f; margin: 0;">⚠️ No active batches available for this variant</p>';
}
toggleDispensePrintFields();
updateDispenseAllocationSummary();
return;
}
@@ -961,12 +1193,15 @@ async function updateBatchInfo() {
activeBatches.sort((a, b) => new Date(a.expiry_date) - new Date(b.expiry_date));
currentDispenseBatches = activeBatches;
renderDispenseBatchAllocationRows(activeBatches);
toggleDispensePrintFields();
autoAllocateDispenseBatches();
} catch (error) {
console.error('Error loading batches:', error);
batchInfoContent.innerHTML = '<p style="color: #d32f2f; margin: 0;">Error loading batches</p>';
currentDispenseBatches = [];
currentDispenseLegacyQuantity = 0;
renderExpiredDispenseBatches([]);
toggleDispensePrintFields();
updateDispenseAllocationSummary();
}
}
@@ -1019,10 +1254,11 @@ function updateDispenseAllocationSummary() {
const inputs = Array.from(document.querySelectorAll('.dispense-batch-allocation'));
const mode = getSelectedDispenseMode();
const selectedPack = getSelectedDispensePack();
const legacyStockOnly = hasLegacyDispenseStock();
const totalAvailableQuantity = getTotalAvailableDispenseQuantity(mode, selectedPack);
const totalAvailablePacks = mode === 'pack' ? getTotalAvailableDispensePackCount(selectedPack) : 0;
if (!summarySection || !summaryContent || !variantId || !inputs.length) {
if (!summarySection || !summaryContent || !variantId || (!inputs.length && !legacyStockOnly)) {
if (summarySection) summarySection.style.display = 'none';
return;
}
@@ -1056,7 +1292,22 @@ function updateDispenseAllocationSummary() {
summarySection.style.display = 'block';
if (requestedQuantity <= 0) {
summaryContent.innerHTML = `<span style="color: #666;">Enter a dispense amount to allocate batches.</span>`;
summaryContent.innerHTML = legacyStockOnly
? `<span style="color: #666;">Enter a dispense quantity. ${formatDisplayNumber(currentDispenseLegacyQuantity)} ${escapeHtml(unitLabel)} of legacy stock is available outside batches.</span>`
: `<span style="color: #666;">Enter a dispense amount to allocate batches.</span>`;
return;
}
if (legacyStockOnly) {
if (mode === 'pack') {
summaryContent.innerHTML = `<span style="color: #d32f2f; font-weight: 600;">Whole-pack dispensing is unavailable for stock that is not attached to batches.</span>`;
return;
}
if (requestedQuantity - currentDispenseLegacyQuantity > 1e-6) {
summaryContent.innerHTML = `<span style="color: #d32f2f; font-weight: 600;">Requested ${formatDisplayNumber(requestedQuantity)} ${escapeHtml(unitLabel)} but only ${formatDisplayNumber(currentDispenseLegacyQuantity)} ${escapeHtml(unitLabel)} of legacy stock is available.</span>`;
return;
}
summaryContent.innerHTML = `<span style="color: #1565c0; font-weight: 600;">Dispensing ${formatDisplayNumber(requestedQuantity)} ${escapeHtml(unitLabel)} from legacy stock outside batches.</span>`;
return;
}
@@ -1289,10 +1540,14 @@ async function handleDispenseDrug(e) {
const requestedPackCountValue = document.getElementById('dispensePackCount').value;
const animalName = document.getElementById('dispenseAnimal').value;
const notes = document.getElementById('dispenseNotes').value;
const printEnabled = document.getElementById('dispensePrintEnabled')?.checked;
const dosage = document.getElementById('dispenseDosage')?.value.trim() || '';
const legacyExpiryDate = document.getElementById('dispenseLegacyExpiry')?.value || '';
const selectedPackId = requestedPackIdValue ? parseInt(requestedPackIdValue, 10) : null;
const selectedPackCount = requestedPackCountValue ? parseFloat(requestedPackCountValue) : null;
const variant = getVariantById(variantId);
const legacyStockOnly = hasLegacyDispenseStock();
const selectedPack = variant && selectedPackId
? getActivePacksForVariant(variant).find(pack => pack.id === selectedPackId)
: null;
@@ -1303,6 +1558,10 @@ async function handleDispenseDrug(e) {
}
if (dispenseMode === 'pack') {
if (legacyStockOnly) {
showToast('Whole-pack dispensing is unavailable for stock that is not attached to batches.', 'warning');
return;
}
if (!selectedPack) {
showToast('Please select a pack type for whole-pack dispensing.', 'warning');
return;
@@ -1352,7 +1611,7 @@ async function handleDispenseDrug(e) {
return;
}
if (allocations.length === 0) {
if (!legacyStockOnly && allocations.length === 0) {
showToast('Allocate quantity against at least one batch.', 'warning');
return;
}
@@ -1374,11 +1633,27 @@ async function handleDispenseDrug(e) {
}
}
if (Math.abs(allocatedTotal - quantity) > 1e-6) {
if (!legacyStockOnly && Math.abs(allocatedTotal - quantity) > 1e-6) {
showToast('Batch allocations must exactly match the requested dispense quantity.', 'warning');
return;
}
const printExpiryDate = printEnabled
? (legacyStockOnly ? legacyExpiryDate : getLatestAllocatedBatchExpiryDate(allocationEntries))
: null;
if (printEnabled && (!animalName.trim() || !dosage)) {
showToast('Animal name/ID and dosage instructions are required to print a label.', 'warning');
return;
}
if (printEnabled && !printExpiryDate) {
showToast(legacyStockOnly
? 'Enter an expiry date to print a label for legacy stock.'
: 'Unable to determine a batch expiry date for the selected allocation.', 'warning');
return;
}
const dispensingData = {
drug_variant_id: variantId,
quantity: quantity,
@@ -1401,10 +1676,40 @@ async function handleDispenseDrug(e) {
throw new Error(error.detail || 'Failed to dispense drug');
}
let successMessage = 'Drug dispensed successfully!';
let toastType = 'success';
if (printEnabled) {
try {
const { drug } = getDrugContextForVariant(variantId);
const labelResult = await requestLabelPrint({
animalName: animalName.trim(),
drugName: drug?.name || 'Unknown drug',
variantStrength: variant?.strength || '',
quantity,
unit: variant?.unit || 'units',
dosage,
expiryDate: printExpiryDate
});
if (!labelResult.success) {
successMessage = `Drug dispensed, but label printing failed: ${labelResult.message}`;
toastType = 'warning';
} else {
successMessage = 'Drug dispensed and label printed successfully!';
}
} catch (printError) {
console.error('Error printing label after dispensing:', printError);
successMessage = 'Drug dispensed, but label printing failed: ' + printError.message;
toastType = 'warning';
}
}
document.getElementById('dispenseForm').reset();
resetDispensePrintFields();
closeModal(document.getElementById('dispenseModal'));
await loadDrugs();
showToast('Drug dispensed successfully!', 'success');
showToast(successMessage, toastType, toastType === 'warning' ? 5000 : undefined);
} catch (error) {
console.error('Error dispensing drug:', error);
showToast('Failed to dispense drug: ' + error.message, 'error');
@@ -1879,9 +2184,7 @@ function prescribeVariant(variantId, drugName, variantStrength, unit) {
}
// Set default expiry date to 1 month from now
const defaultExpiry = new Date();
defaultExpiry.setMonth(defaultExpiry.getMonth() + 1);
document.getElementById('prescribeExpiry').value = defaultExpiry.toISOString().split('T')[0];
document.getElementById('prescribeExpiry').value = getDefaultLabelExpiryDate();
// Open prescribe modal
openModal(document.getElementById('prescribeModal'));
@@ -1907,34 +2210,17 @@ async function handlePrescribeDrug(e) {
return;
}
// Convert expiry date to DD/MM/YYYY format
const expiryParts = expiryDate.split('-');
const formattedExpiry = `${expiryParts[2]}/${expiryParts[1]}/${expiryParts[0]}`;
try {
// First, print the label
const labelData = {
variables: {
practice_name: "Many Tears Animal Rescue",
animal_name: animalName,
drug_name: `${drugName} ${variantStrength}`,
dosage: dosage,
quantity: `${quantity} ${unit}`,
expiry_date: formattedExpiry
}
};
const labelResponse = await apiCall('/labels/print', {
method: 'POST',
body: JSON.stringify(labelData)
const labelResult = await requestLabelPrint({
animalName,
drugName,
variantStrength,
quantity,
unit,
dosage,
expiryDate
});
if (!labelResponse.ok) {
const error = await labelResponse.json();
throw new Error(error.detail || 'Label printing request failed');
}
const labelResult = await labelResponse.json();
console.log('Label print result:', labelResult);
if (!labelResult.success) {