WIP gettnig there
This commit is contained in:
+266
-165
@@ -5,12 +5,12 @@ let showLowStockOnly = false;
|
||||
let selectedLocationFilter = '';
|
||||
let searchTerm = '';
|
||||
let expandedDrugs = new Set();
|
||||
let expandedVariants = 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) {
|
||||
@@ -231,11 +231,11 @@ function setupEventListeners() {
|
||||
const addDeliveryLineBtn = document.getElementById('addDeliveryLineBtn');
|
||||
const addVariantFromDeliveryBtn = document.getElementById('addVariantFromDeliveryBtn');
|
||||
const addVariantPackRowBtn = document.getElementById('addVariantPackRowBtn');
|
||||
const addEditVariantPackRowBtn = document.getElementById('addEditVariantPackRowBtn');
|
||||
const variantUnitSelect = document.getElementById('variantUnit');
|
||||
const variantStrengthInput = document.getElementById('variantStrength');
|
||||
const editVariantUnitSelect = document.getElementById('editVariantUnit');
|
||||
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');
|
||||
@@ -267,11 +267,17 @@ function setupEventListeners() {
|
||||
if (addDeliveryLineBtn) addDeliveryLineBtn.addEventListener('click', () => appendDeliveryLine());
|
||||
if (addVariantFromDeliveryBtn) addVariantFromDeliveryBtn.addEventListener('click', handleAddVariantFromDelivery);
|
||||
if (addVariantPackRowBtn) addVariantPackRowBtn.addEventListener('click', () => appendVariantPackRow());
|
||||
if (addEditVariantPackRowBtn) addEditVariantPackRowBtn.addEventListener('click', () => appendEditVariantPackRow());
|
||||
if (variantUnitSelect) {
|
||||
variantUnitSelect.addEventListener('change', () => {
|
||||
refreshVariantPackRowLabels();
|
||||
});
|
||||
}
|
||||
if (editVariantUnitSelect) {
|
||||
editVariantUnitSelect.addEventListener('change', () => {
|
||||
refreshEditVariantPackRowLabels();
|
||||
});
|
||||
}
|
||||
if (variantStrengthInput && variantUnitSelect) {
|
||||
variantStrengthInput.addEventListener('blur', () => {
|
||||
variantUnitSelect.value = inferBaseUnitFromStrength(variantStrengthInput.value);
|
||||
@@ -307,9 +313,6 @@ 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);
|
||||
|
||||
@@ -574,6 +577,63 @@ function formatDisplayDate(value) {
|
||||
return parsed.toLocaleDateString();
|
||||
}
|
||||
|
||||
function formatDisplayNumber(value) {
|
||||
const numeric = Number(value);
|
||||
if (Number.isNaN(numeric)) return '0';
|
||||
return Number.isInteger(numeric) ? String(numeric) : String(Number(numeric.toFixed(3)));
|
||||
}
|
||||
|
||||
function renderVariantInventoryDetails(variant) {
|
||||
const activePacks = getActivePacksForVariant(variant);
|
||||
const batches = [...(variant.batches || [])]
|
||||
.filter(batch => Number(batch.quantity) > 0)
|
||||
.sort((a, b) => new Date(a.expiry_date) - new Date(b.expiry_date));
|
||||
|
||||
const packsHtml = activePacks.length > 0
|
||||
? activePacks.map(pack => `
|
||||
<div style="padding: 6px 8px; background: #ffffff; border: 1px solid #d8e2ea; border-radius: 5px; font-size: 0.9em;">
|
||||
<strong>${escapeHtml(pack.label)}</strong>
|
||||
<span style="color: #4b5563;"> (${formatDisplayNumber(pack.pack_size_in_base_units)} ${escapeHtml(variant.unit)})</span>
|
||||
</div>
|
||||
`).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 locationLabel = getBatchLocationLabel(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)}`;
|
||||
|
||||
return `
|
||||
<div style="padding: 8px; background: #ffffff; border: 1px solid #d8e2ea; border-radius: 5px; font-size: 0.9em;">
|
||||
<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>
|
||||
<div style="margin-top: 4px; color: #374151;">${escapeHtml(locationLabel)} | ${stocktakeLabel}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')
|
||||
: '<div style="padding: 6px 8px; background: #ffffff; border: 1px dashed #cfd8e3; border-radius: 5px; font-size: 0.9em; color: #6b7280;">No active batches</div>';
|
||||
|
||||
return `
|
||||
<div style="margin-top: 10px; background: #f2f6fa; border: 1px solid #d6e0ea; border-radius: 8px; padding: 10px;" onclick="event.stopPropagation()">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
||||
<div>
|
||||
<div style="font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.04em; color: #4b5563; margin-bottom: 6px;">Active Packs</div>
|
||||
<div style="display: grid; gap: 6px;">${packsHtml}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.04em; color: #4b5563; margin-bottom: 6px;">Current Batches</div>
|
||||
<div style="display: grid; gap: 6px;">${batchesHtml}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function getBatchLocationLabel(batch) {
|
||||
return batch.location_name || batch.location?.name || `Location #${batch.location_id}`;
|
||||
}
|
||||
@@ -882,8 +942,9 @@ function renderDrugs() {
|
||||
const variantsHtml = isExpanded ? `
|
||||
${drug.variants.map(variant => {
|
||||
const variantIsLowStock = variant.quantity <= variant.low_stock_threshold;
|
||||
const variantExpanded = expandedVariants.has(variant.id);
|
||||
return `
|
||||
<div class="variant-item ${variantIsLowStock ? 'low-stock' : ''}">
|
||||
<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>
|
||||
@@ -893,17 +954,18 @@ function renderDrugs() {
|
||||
<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-actions">
|
||||
${!isReadOnly ? `
|
||||
<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>
|
||||
<button class="btn btn-danger btn-small" onclick="deleteVariant(${variant.id})">Delete</button>
|
||||
<button class="btn btn-primary btn-small" onclick="event.stopPropagation(); 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="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('')}` : '';
|
||||
@@ -931,7 +993,7 @@ function renderDrugs() {
|
||||
<button class="btn btn-info btn-small" onclick="event.stopPropagation(); showDrugHistory(${drug.id})">📋 History</button>
|
||||
${!isReadOnly ? `
|
||||
<button class="btn btn-warning btn-small" onclick="event.stopPropagation(); openEditModal(${drug.id})">Edit Drug</button>
|
||||
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); deleteDrug(${drug.id})">Delete</button>
|
||||
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); deleteDrug(${drug.id})" title="${drug.variants.some(v => v.has_inventory_history) ? 'Drug has variants with history and cannot be deleted' : ''}">Delete</button>
|
||||
` : ''}
|
||||
<span class="expand-icon">${isExpanded ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
@@ -1099,12 +1161,29 @@ function closeEditModal() {
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
expandedDrugs.add(drugId);
|
||||
}
|
||||
renderDrugs();
|
||||
}
|
||||
|
||||
function toggleVariantExpansion(variantId, event) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (expandedVariants.has(variantId)) {
|
||||
expandedVariants.delete(variantId);
|
||||
} else {
|
||||
expandedVariants.add(variantId);
|
||||
}
|
||||
renderDrugs();
|
||||
}
|
||||
|
||||
// Open add variant modal
|
||||
function openAddVariantModal(drugId) {
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
@@ -1130,6 +1209,10 @@ function getVariantPackRowsContainer() {
|
||||
return document.getElementById('variantPackRows');
|
||||
}
|
||||
|
||||
function getEditVariantPackRowsContainer() {
|
||||
return document.getElementById('editVariantPackRows');
|
||||
}
|
||||
|
||||
function refreshVariantPackRowLabels() {
|
||||
const container = getVariantPackRowsContainer();
|
||||
const baseUnit = document.getElementById('variantUnit')?.value || 'units';
|
||||
@@ -1144,6 +1227,20 @@ function refreshVariantPackRowLabels() {
|
||||
});
|
||||
}
|
||||
|
||||
function refreshEditVariantPackRowLabels() {
|
||||
const container = getEditVariantPackRowsContainer();
|
||||
const baseUnit = document.getElementById('editVariantUnit')?.value || 'units';
|
||||
if (!container) return;
|
||||
|
||||
container.querySelectorAll('.edit-variant-pack-row').forEach(row => {
|
||||
const packUnit = row.querySelector('.edit-variant-pack-unit')?.value || 'pack';
|
||||
const label = row.querySelector('.edit-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;
|
||||
@@ -1209,6 +1306,71 @@ function initializeVariantPackRows() {
|
||||
appendVariantPackRow({ packUnit: 'bottle' });
|
||||
}
|
||||
|
||||
function appendEditVariantPackRow(prefill = {}) {
|
||||
const container = getEditVariantPackRowsContainer();
|
||||
if (!container) return;
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.className = 'delivery-line edit-variant-pack-row';
|
||||
|
||||
const selectedPackUnit = prefill.packUnit || 'bottle';
|
||||
const selectedSize = prefill.packSize || '';
|
||||
const baseUnit = document.getElementById('editVariantUnit')?.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="edit-variant-pack-unit">
|
||||
<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="edit-variant-pack-size-label">Bottle Size (${baseUnit}) *</label>
|
||||
<input type="number" class="edit-variant-pack-size" min="0.0001" step="0.0001" value="${selectedSize}">
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-small edit-variant-pack-remove-btn">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const removeBtn = row.querySelector('.edit-variant-pack-remove-btn');
|
||||
const unitSelect = row.querySelector('.edit-variant-pack-unit');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', () => {
|
||||
row.remove();
|
||||
});
|
||||
}
|
||||
|
||||
if (unitSelect) {
|
||||
unitSelect.addEventListener('change', refreshEditVariantPackRowLabels);
|
||||
}
|
||||
|
||||
container.appendChild(row);
|
||||
refreshEditVariantPackRowLabels();
|
||||
}
|
||||
|
||||
function initializeEditVariantPackRows() {
|
||||
const container = getEditVariantPackRowsContainer();
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
appendEditVariantPackRow({ packUnit: 'bottle' });
|
||||
}
|
||||
|
||||
function setEditVariantFieldLockState(isLocked) {
|
||||
const strengthInput = document.getElementById('editVariantStrength');
|
||||
const quantityInput = document.getElementById('editVariantQuantity');
|
||||
const unitSelect = document.getElementById('editVariantUnit');
|
||||
const lockNotice = document.getElementById('editVariantLockNotice');
|
||||
|
||||
if (strengthInput) strengthInput.disabled = isLocked;
|
||||
if (quantityInput) quantityInput.disabled = isLocked;
|
||||
if (unitSelect) unitSelect.disabled = isLocked;
|
||||
if (lockNotice) lockNotice.style.display = isLocked ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Handle add variant form
|
||||
async function handleAddVariant(e) {
|
||||
e.preventDefault();
|
||||
@@ -1319,14 +1481,11 @@ function openEditVariantModal(variantId) {
|
||||
document.getElementById('editVariantUnit').value = variant.unit;
|
||||
document.getElementById('editVariantThreshold').value = variant.low_stock_threshold;
|
||||
|
||||
document.getElementById('editVariantModal').classList.add('show');
|
||||
}
|
||||
const hasInventoryContext = Boolean(variant.has_inventory_history);
|
||||
setEditVariantFieldLockState(hasInventoryContext);
|
||||
initializeEditVariantPackRows();
|
||||
|
||||
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;
|
||||
document.getElementById('editVariantModal').classList.add('show');
|
||||
}
|
||||
|
||||
// Handle edit variant form
|
||||
@@ -1334,165 +1493,89 @@ async function handleEditVariant(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const variantId = parseInt(document.getElementById('editVariantId').value);
|
||||
const strengthInput = document.getElementById('editVariantStrength');
|
||||
const quantityInput = document.getElementById('editVariantQuantity');
|
||||
const unitSelect = document.getElementById('editVariantUnit');
|
||||
const baseUnit = unitSelect.value;
|
||||
const variantData = {
|
||||
strength: document.getElementById('editVariantStrength').value,
|
||||
quantity: parseFloat(document.getElementById('editVariantQuantity').value),
|
||||
unit: document.getElementById('editVariantUnit').value,
|
||||
low_stock_threshold: parseFloat(document.getElementById('editVariantThreshold').value)
|
||||
};
|
||||
|
||||
if (!strengthInput.disabled && !quantityInput.disabled && !unitSelect.disabled) {
|
||||
const quantityValue = parseFloat(quantityInput.value);
|
||||
if (Number.isNaN(quantityValue) || quantityValue < 0) {
|
||||
showToast('Please enter a valid quantity (0 or greater)', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
variantData.strength = strengthInput.value;
|
||||
variantData.quantity = quantityValue;
|
||||
variantData.unit = baseUnit;
|
||||
variantData.base_unit = baseUnit;
|
||||
}
|
||||
|
||||
const packRows = Array.from(document.querySelectorAll('#editVariantPackRows .edit-variant-pack-row'));
|
||||
const newPackPayloads = [];
|
||||
for (let i = 0; i < packRows.length; i += 1) {
|
||||
const row = packRows[i];
|
||||
const packUnitRaw = row.querySelector('.edit-variant-pack-unit')?.value || '';
|
||||
const packSizeRaw = row.querySelector('.edit-variant-pack-size')?.value || '';
|
||||
|
||||
if (!packUnitRaw && !packSizeRaw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const packSize = parseFloat(packSizeRaw);
|
||||
if (!packUnitRaw || Number.isNaN(packSize) || packSize <= 0) {
|
||||
showToast(`Pack row ${i + 1} is incomplete`, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedPackUnit = packUnitRaw.trim().toLowerCase();
|
||||
const titleCasePack = normalizedPackUnit.charAt(0).toUpperCase() + normalizedPackUnit.slice(1);
|
||||
newPackPayloads.push({
|
||||
label: `${titleCasePack} ${packSize} ${baseUnit}`,
|
||||
pack_unit_name: normalizedPackUnit,
|
||||
pack_size_in_base_units: packSize,
|
||||
is_active: true
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/variants/${variantId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(variantData)
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to update variant');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to update variant');
|
||||
}
|
||||
|
||||
for (const packPayload of newPackPayloads) {
|
||||
const packResponse = await apiCall(`/variants/${variantId}/packs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(packPayload)
|
||||
});
|
||||
|
||||
if (!packResponse.ok) {
|
||||
const packError = await packResponse.json();
|
||||
throw new Error(packError.detail || 'Variant updated but failed to add one or more pack sizes');
|
||||
}
|
||||
}
|
||||
|
||||
closeModal(document.getElementById('editVariantModal'));
|
||||
await loadDrugs();
|
||||
renderDrugs();
|
||||
showToast('Variant updated successfully!', 'success');
|
||||
const message = newPackPayloads.length > 0
|
||||
? `Variant updated and ${newPackPayloads.length} pack size${newPackPayloads.length === 1 ? '' : 's'} added`
|
||||
: 'Variant updated successfully!';
|
||||
showToast(message, 'success');
|
||||
} catch (error) {
|
||||
console.error('Error updating variant:', error);
|
||||
showToast('Failed to update variant. Check the console for details.', 'error');
|
||||
showToast('Failed to update variant: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1683,6 +1766,12 @@ async function handlePrintNotes(e) {
|
||||
|
||||
// Delete variant
|
||||
async function deleteVariant(variantId) {
|
||||
const variant = getVariantById(variantId);
|
||||
if (variant && variant.has_inventory_history) {
|
||||
showToast('Cannot delete variant with batch or dispensing history', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to delete this variant?')) return;
|
||||
|
||||
try {
|
||||
@@ -1690,14 +1779,17 @@ async function deleteVariant(variantId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to delete variant');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to delete variant');
|
||||
}
|
||||
|
||||
await loadDrugs();
|
||||
renderDrugs();
|
||||
showToast('Variant deleted successfully!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error deleting variant:', error);
|
||||
showToast('Failed to delete variant. Check the console for details.', 'error');
|
||||
showToast('Failed to delete variant: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1803,6 +1895,12 @@ async function handleEditDrug(e) {
|
||||
|
||||
// Delete drug
|
||||
async function deleteDrug(drugId) {
|
||||
const drug = allDrugs.find(d => d.id === drugId);
|
||||
if (drug && drug.variants.some(v => v.has_inventory_history)) {
|
||||
showToast('Cannot delete drug with variants that have batch or dispensing history', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to delete this drug?')) return;
|
||||
|
||||
try {
|
||||
@@ -1810,13 +1908,16 @@ async function deleteDrug(drugId) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to delete drug');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to delete drug');
|
||||
}
|
||||
|
||||
await loadDrugs();
|
||||
showToast('Drug deleted successfully!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error deleting drug:', error);
|
||||
showToast('Failed to delete drug. Check the console for details.', 'error');
|
||||
showToast('Failed to delete drug: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-29
@@ -366,6 +366,9 @@
|
||||
<h2>Edit Variant</h2>
|
||||
<form id="editVariantForm">
|
||||
<input type="hidden" id="editVariantId">
|
||||
<p id="editVariantLockNotice" style="display:none; margin: 0 0 12px; padding: 8px 10px; background: #fff8e1; border: 1px solid #f5c15d; border-radius: 6px; color: #7a4f01;">
|
||||
Strength, quantity, and base unit are locked once this variant has stock/batch history.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="editVariantStrength">Strength *</label>
|
||||
<input type="text" id="editVariantStrength" required>
|
||||
@@ -389,6 +392,12 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Add Pack Sizes</label>
|
||||
<div id="editVariantPackRows" class="delivery-lines"></div>
|
||||
<button type="button" id="addEditVariantPackRowBtn" class="btn btn-secondary btn-small">+ Add Another Size</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editVariantThreshold">Low Stock Threshold *</label>
|
||||
@@ -616,35 +625,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variant Pack Management Modal -->
|
||||
<div id="variantPacksModal" class="modal">
|
||||
<div class="modal-content modal-large">
|
||||
<span class="close">×</span>
|
||||
<h2>Pack Presentations</h2>
|
||||
<p id="variantPacksLabel" style="margin: 6px 0 16px; color: #666; font-weight: 600;"></p>
|
||||
|
||||
<div class="form-group">
|
||||
<h3 style="margin-bottom: 8px;">Add Pack Presentation</h3>
|
||||
<form id="variantPacksForm">
|
||||
<input type="hidden" id="variantPacksVariantId">
|
||||
<div class="form-row">
|
||||
<input type="text" id="variantPacksNewLabel" placeholder="Label (e.g., Bottle 300 ml)" required>
|
||||
<input type="text" id="variantPacksNewUnit" placeholder="Pack Unit Name (e.g., bottle, box)" required>
|
||||
<input type="number" id="variantPacksNewSize" min="0.0001" step="0.0001" placeholder="Size in base units" required>
|
||||
<button type="submit" class="btn btn-primary btn-small">Add Pack</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="variantPacksList" class="locations-list">
|
||||
<p class="loading">Loading packs...</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" id="closeVariantPacksBtn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user