diff --git a/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js b/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js
index b2f1471..c3d6a48 100644
--- a/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js
+++ b/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js
@@ -46,11 +46,33 @@
const processingEl = document.getElementById('scan-processing');
const processingMsgEl= document.getElementById('scan-processing-msg');
+ // Add-stock modal elements
+ const addStockModalEl = document.getElementById('addStockModal');
+ const bsAddStockModal = addStockModalEl ? new bootstrap.Modal(addStockModalEl) : null;
+ const addStockItemName = document.getElementById('add-stock-item-name');
+ const addStockCurrentQty= document.getElementById('add-stock-current-qty');
+ const addStockUomLabel = document.getElementById('add-stock-uom-label');
+ const addStockQtyInput = document.getElementById('add-stock-qty');
+ const addStockCostInput = document.getElementById('add-stock-cost');
+ const addStockNotesInput= document.getElementById('add-stock-notes');
+ const addStockStatusEl = document.getElementById('add-stock-status');
+ const addStockConfirmBtn= document.getElementById('add-stock-confirm-btn');
+
+ let _addStockItemId = null;
+ let _lastScanData = null;
+
if (!modalEl || !videoEl || !canvasEl) return;
scanBtn.addEventListener('click', openScanner);
modalEl.addEventListener('hide.bs.modal', onModalClose);
if (shutterBtn) shutterBtn.addEventListener('click', captureFrame);
+ if (addStockConfirmBtn) addStockConfirmBtn.addEventListener('click', submitAddStock);
+ // "Create new entry instead" hides the add-stock modal and pre-fills the create form
+ const addStockNewBtn = document.getElementById('add-stock-new-btn');
+ if (addStockNewBtn) addStockNewBtn.addEventListener('click', () => {
+ bsAddStockModal?.hide();
+ if (_lastScanData) fillFromScan(_lastScanData, /* skipDuplicatePrompt */ true);
+ });
window.addEventListener('beforeunload', releaseCamera);
// Pre-warm camera if browser has already granted permission (no prompt risk)
@@ -301,7 +323,15 @@
}
bsModal.hide();
- fillFromScan(data);
+
+ if (data.existingInventoryId) {
+ // Product already in inventory — show inline add-stock prompt
+ _lastScanData = data;
+ _addStockItemId = data.existingInventoryId;
+ openAddStockModal(data);
+ } else {
+ fillFromScan(data);
+ }
} catch (err) {
hideProcessing();
@@ -309,9 +339,80 @@
}
}
+ // ── Add-stock modal ───────────────────────────────────────────────────
+
+ function openAddStockModal(data) {
+ if (!bsAddStockModal) { fillFromScan(data); return; }
+
+ const uom = data.existingUnitOfMeasure || 'lbs';
+ if (addStockItemName) addStockItemName.textContent = data.existingInventoryName || data.colorName || 'This product';
+ if (addStockCurrentQty) addStockCurrentQty.textContent = `${(data.existingQuantityOnHand ?? 0).toFixed(2)} ${uom}`;
+ if (addStockUomLabel) addStockUomLabel.textContent = uom;
+ if (addStockQtyInput) addStockQtyInput.value = '';
+ if (addStockCostInput) addStockCostInput.value = data.unitPrice > 0 ? data.unitPrice : '';
+ if (addStockNotesInput) addStockNotesInput.value = '';
+ if (addStockStatusEl) { addStockStatusEl.className = 'd-none'; addStockStatusEl.textContent = ''; }
+ if (addStockConfirmBtn) addStockConfirmBtn.disabled = false;
+
+ bsAddStockModal.show();
+ }
+
+ async function submitAddStock() {
+ const qty = parseFloat(addStockQtyInput?.value);
+ if (!qty || qty <= 0) {
+ showAddStockStatus('danger', 'Please enter a quantity greater than zero.');
+ return;
+ }
+
+ addStockConfirmBtn.disabled = true;
+ addStockConfirmBtn.innerHTML = '
Saving…';
+
+ try {
+ const params = new URLSearchParams({
+ inventoryItemId: _addStockItemId,
+ quantity: qty,
+ });
+ const cost = parseFloat(addStockCostInput?.value);
+ if (cost > 0) params.append('unitCost', cost);
+ const notes = addStockNotesInput?.value?.trim();
+ if (notes) params.append('notes', notes);
+
+ const resp = await fetch('/Inventory/AddStock?' + params.toString(), { method: 'POST' });
+ if (!resp.ok) throw new Error(`Server error ${resp.status}`);
+ const data = await resp.json();
+
+ if (!data.success) {
+ showAddStockStatus('danger', data.errorMessage || 'Failed to add stock.');
+ addStockConfirmBtn.disabled = false;
+ addStockConfirmBtn.innerHTML = '
Add Stock';
+ return;
+ }
+
+ // Success — close modal and show confirmation on the form
+ bsAddStockModal.hide();
+ showFormStatus('success',
+ `
` +
+ `Added
${qty.toFixed(2)} ${data.unitOfMeasure} to
${data.itemName}. ` +
+ `New stock: ${(data.newQuantityOnHand ?? 0).toFixed(2)} ${data.unitOfMeasure}. ` +
+ `
View item`
+ );
+
+ } catch (err) {
+ showAddStockStatus('danger', 'Error: ' + err.message);
+ addStockConfirmBtn.disabled = false;
+ addStockConfirmBtn.innerHTML = '
Add Stock';
+ }
+ }
+
+ function showAddStockStatus(type, msg) {
+ if (!addStockStatusEl) return;
+ addStockStatusEl.className = `alert alert-${type} py-2 small`;
+ addStockStatusEl.textContent = msg;
+ }
+
// ── Fill the inventory form from scan result ───────────────────────────
- function fillFromScan(data) {
+ function fillFromScan(data, skipDuplicatePrompt = false) {
const filled = [];
function setIf(id, value, label) {
@@ -404,14 +505,16 @@
? '
Added to platform catalog'
: '';
- if (data.existingInventoryId) {
+ if (data.existingInventoryId && !skipDuplicatePrompt) {
+ // Duplicate handled by add-stock modal — don't show a banner here
+ } else if (data.existingInventoryId && skipDuplicatePrompt) {
const itemName = data.existingInventoryName || data.colorName || 'This product';
const filledNote = filled.length > 0 ? ` Fields pre-filled from scan.` : '';
showFormStatus('warning',
`
` +
- `
${itemName} is already in your inventory. ` +
- `
View existing item` +
- ` — or continue below to add a new entry (e.g. a new lot or bag size).${filledNote}${catalogNote}`
+ `Creating a new entry —
${itemName} already exists. ` +
+ `
View existing item` +
+ `${filledNote}${catalogNote}`
);
} else if (filled.length > 0) {
showFormStatus('success', `Filled from label scan: ${filled.join(', ')}.${catalogNote}`);