diff --git a/backend/app/main.py b/backend/app/main.py index 5c515cc..52156e5 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1670,7 +1670,41 @@ def create_variant_batch(variant_id: int, payload: BatchCreate, db: Session = De .first() ) if existing: - raise HTTPException(status_code=400, detail="Batch number already exists for this variant") + if existing.expiry_date != payload.expiry_date: + raise HTTPException( + status_code=400, + detail="Batch number already exists for this variant with a different expiry date", + ) + + # Same batch number and expiry — restock the existing batch + existing.quantity += batch_quantity + if existing.received_pack_id == resolved["pack_id"]: + existing.received_pack_count = (existing.received_pack_count or 0) + resolved["pack_count"] + if payload.notes: + existing.notes = (existing.notes + "\n" + payload.notes) if existing.notes else payload.notes + recompute_batch_pack_state(existing) + variant.quantity += batch_quantity + + write_audit_log( + db, + action="batch.restock", + entity_type="batch", + entity_id=existing.id, + actor=current_user, + details={ + "variant_id": variant_id, + "batch_number": batch_number, + "quantity_added": batch_quantity, + "new_total_quantity": existing.quantity, + "received_pack_id": resolved["pack_id"], + "received_pack_count": resolved["pack_count"], + "expiry_date": str(payload.expiry_date), + "location_id": existing.location_id, + }, + ) + db.commit() + db.refresh(existing) + return serialize_batch_response(db, existing) row = Batch( drug_variant_id=variant_id, diff --git a/frontend/app.js b/frontend/app.js index 133a82e..27e25cb 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -296,6 +296,23 @@ function setupEventListeners() { const closeButtons = document.querySelectorAll('.close'); if (drugForm) drugForm.addEventListener('submit', handleAddDrug); + + // Auto-capitalise the first letter typed in each Add Drug text field + ['drugName', 'drugDescription'].forEach(id => { + const el = document.getElementById(id); + if (el) el.addEventListener('input', () => { + if (el.value.length === 1) el.value = el.value.toUpperCase(); + }); + }); + + // Auto-capitalise the first letter typed in each Dispense text field + ['dispenseAnimal', 'dispenseDosage', 'dispenseNotes'].forEach(id => { + const el = document.getElementById(id); + if (el) el.addEventListener('input', () => { + if (el.value.length === 1) el.value = el.value.toUpperCase(); + }); + }); + if (variantForm) variantForm.addEventListener('submit', handleAddVariant); if (editVariantForm) editVariantForm.addEventListener('submit', handleEditVariant); if (dispenseForm) dispenseForm.addEventListener('submit', handleDispenseDrug); @@ -402,7 +419,10 @@ function setupEventListeners() { const cancelAdminChangePasswordBtn = document.getElementById('cancelAdminChangePasswordBtn'); if (cancelAdminChangePasswordBtn) cancelAdminChangePasswordBtn.addEventListener('click', () => closeModal(document.getElementById('adminChangePasswordModal'))); - + + const cancelGtinMappingBtn = document.getElementById('cancelGtinMappingBtn'); + if (cancelGtinMappingBtn) cancelGtinMappingBtn.addEventListener('click', () => closeModal(document.getElementById('gtinMappingModal'))); + closeButtons.forEach(btn => btn.addEventListener('click', (e) => { const modal = e.target.closest('.modal'); if (modal?.id === 'disposeBatchModal') { @@ -2049,7 +2069,7 @@ function appendVariantPackRow(prefill = {}) {
- +
@@ -3173,6 +3193,10 @@ function parseGS1(raw) { const aimPrefix = raw.match(/^\][a-zA-Z]\d/); let data = aimPrefix ? raw.substring(3) : raw; + // Normalise semicolons to the standard GS/FNC1 character — some scanners + // emit ';' as the group separator for variable-length field termination. + data = data.replace(/;/g, '\x1d'); + const GS = '\x1d'; // FNC1 separator const hasGS = data.includes(GS); @@ -3246,6 +3270,9 @@ function _onDeliveryModalKeydown(e) { // Only act when the receive delivery modal is open if (!document.getElementById('receiveDeliveryModal')?.classList.contains('show')) return; + // Stop intercepting if the GTIN mapping modal is open on top + if (document.getElementById('gtinMappingModal')?.classList.contains('show')) return; + // Track which delivery line last had focus const focusedLine = document.activeElement?.closest('.delivery-line'); if (focusedLine) _activeScanLineEl = focusedLine; @@ -3254,7 +3281,6 @@ function _onDeliveryModalKeydown(e) { if (e.key === 'Enter') { const raw = _scanBuffer.map(x => x.char).join(''); - console.log('[barcode] Enter received. Buffer length:', raw.length, 'Content:', raw); _scanBuffer = []; if (_scanBufferTimer) { clearTimeout(_scanBufferTimer); _scanBufferTimer = null; } @@ -3267,10 +3293,8 @@ function _onDeliveryModalKeydown(e) { } _preScanFocusedInput = null; _preScanFocusedValue = null; - console.log('[barcode] Treating as scan, calling handleBarcodeScan'); handleBarcodeScan(raw); } else { - console.log('[barcode] Buffer too short for scan, ignoring'); } return; } @@ -3281,7 +3305,6 @@ function _onDeliveryModalKeydown(e) { // If gap is too large, start fresh (human typed slowly) if (_scanBuffer.length > 0 && gap > SCAN_MAX_GAP_MS) { - console.log('[barcode] Gap too large (' + gap + 'ms), resetting buffer'); _scanBuffer = []; _preScanFocusedInput = null; _preScanFocusedValue = null; @@ -3303,12 +3326,10 @@ function _onDeliveryModalKeydown(e) { } _scanBuffer.push({ char: e.key, time: now }); - console.log('[barcode] Buffered char:', e.key, '| gap:', gap + 'ms | buffer length:', _scanBuffer.length); // Auto-clear buffer if Enter never comes if (_scanBufferTimer) clearTimeout(_scanBufferTimer); _scanBufferTimer = setTimeout(() => { - console.log('[barcode] Buffer auto-cleared (no Enter)'); _scanBuffer = []; _preScanFocusedInput = null; _preScanFocusedValue = null; @@ -3767,7 +3788,6 @@ async function openReceiveDeliveryModal() { if (modalEl._barcodeListener) document.removeEventListener('keydown', modalEl._barcodeListener); modalEl._barcodeListener = _onDeliveryModalKeydown; document.addEventListener('keydown', modalEl._barcodeListener); - console.log('[barcode] Listener attached to receiveDeliveryModal'); openModal(modalEl); } diff --git a/frontend/index.html b/frontend/index.html index 7dad61f..95bbfb1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -268,7 +268,7 @@
- +
@@ -672,7 +672,7 @@
- +