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 = {}) {