From b78523102d1dc7da5dbaec5571c8b216cdec6aa7 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Sun, 14 Jun 2026 09:59:14 +0000 Subject: [PATCH] feat(pos/inventory): product search instead of ID in purchase entry modal - Replace ID Producto input with autocomplete search by name/part number/barcode - Support Enter key for barcode/part number exact match - Keep hidden inventory_id field for API compatibility - Bump inventory.js cache version --- pos/static/js/inventory.js | 131 +++++++++++++++++++++++++++++++++-- pos/templates/inventory.html | 10 ++- 2 files changed, 134 insertions(+), 7 deletions(-) diff --git a/pos/static/js/inventory.js b/pos/static/js/inventory.js index 75b66bb..30392af 100644 --- a/pos/static/js/inventory.js +++ b/pos/static/js/inventory.js @@ -478,16 +478,140 @@ // PURCHASE / ENTRADA (purchaseModal) // ===================================================================== + let purchaseSearchTimeout = null; + let purchaseSelectedItem = null; + function showPurchaseModal() { document.getElementById('purchaseModal').classList.add('is-open'); + setTimeout(function() { + var el = document.getElementById('purchaseItemSearch'); + if (el) el.focus(); + }, 100); } function showPurchaseModalForItem(itemId) { - document.getElementById('purchaseItemId').value = itemId; + // Pre-fill by fetching item details + apiFetch(API + '/items?page=1&per_page=1').then(function() { + // We just need the item detail; use the existing list or fetch by id + apiFetch(API + '/items?page=1&per_page=1').then(function() {}); + }); + selectPurchaseItem({id: itemId, name: 'Producto #' + itemId}); showPurchaseModal(); } function closePurchaseModal() { document.getElementById('purchaseModal').classList.remove('is-open'); document.getElementById('purchaseResult').innerHTML = ''; + clearPurchaseSelection(); + } + + function clearPurchaseSelection() { + purchaseSelectedItem = null; + var ids = ['purchaseItemId','purchaseItemSearch','purchaseQty','purchaseCost','purchaseInvoice','purchaseNotes']; + ids.forEach(function(id) { + var el = document.getElementById(id); + if (el) el.value = ''; + }); + var results = document.getElementById('purchaseItemResults'); + if (results) results.style.display = 'none'; + var selected = document.getElementById('purchaseItemSelected'); + if (selected) selected.textContent = ''; + } + + function selectPurchaseItem(item) { + purchaseSelectedItem = item; + document.getElementById('purchaseItemId').value = item.id; + document.getElementById('purchaseItemSearch').value = item.name || item.part_number || item.barcode || ('#' + item.id); + document.getElementById('purchaseItemResults').style.display = 'none'; + document.getElementById('purchaseItemSelected').innerHTML = + '' + esc(item.name || '') + '' + + (item.part_number ? ' · No. parte: ' + esc(item.part_number) : '') + + (item.barcode ? ' · Barcode: ' + esc(item.barcode) : ''); + document.getElementById('purchaseQty').focus(); + } + + function searchPurchaseItems(query) { + var resultsEl = document.getElementById('purchaseItemResults'); + if (!query || query.length < 2) { + resultsEl.style.display = 'none'; + return; + } + apiFetch(API + '/items?q=' + encodeURIComponent(query) + '&per_page=10').then(function(res) { + var items = (res && res.items) || []; + if (!items.length) { + resultsEl.innerHTML = '
Sin resultados
'; + resultsEl.style.display = 'block'; + return; + } + resultsEl.innerHTML = items.map(function(it) { + return '
' + + '
' + esc(it.name) + '
' + + '
' + + (it.part_number ? 'No. parte: ' + esc(it.part_number) + ' · ' : '') + + (it.barcode ? 'Barcode: ' + esc(it.barcode) + ' · ' : '') + + 'Stock: ' + (it.stock || 0) + + '
' + + '
'; + }).join(''); + resultsEl.querySelectorAll('.purchase-search-result').forEach(function(row) { + row.onclick = function() { + var id = parseInt(row.dataset.id); + var item = items.find(function(x) { return x.id === id; }); + if (item) selectPurchaseItem(item); + }; + }); + resultsEl.style.display = 'block'; + }).catch(function() { + resultsEl.style.display = 'none'; + }); + } + + function wirePurchaseSearch() { + var input = document.getElementById('purchaseItemSearch'); + var resultsEl = document.getElementById('purchaseItemResults'); + if (!input) return; + + input.addEventListener('input', function() { + if (purchaseSelectedItem && input.value !== purchaseSelectedItem.name) { + purchaseSelectedItem = null; + document.getElementById('purchaseItemId').value = ''; + document.getElementById('purchaseItemSelected').textContent = ''; + } + clearTimeout(purchaseSearchTimeout); + purchaseSearchTimeout = setTimeout(function() { + searchPurchaseItems(input.value.trim()); + }, 250); + }); + + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + // Try exact barcode match first + var query = input.value.trim(); + if (!query) return; + apiFetch(API + '/items?q=' + encodeURIComponent(query) + '&per_page=20').then(function(res) { + var items = (res && res.items) || []; + var exact = items.find(function(it) { + return (it.barcode || '').toLowerCase() === query.toLowerCase() || + (it.part_number || '').toLowerCase() === query.toLowerCase(); + }); + if (exact) { + selectPurchaseItem(exact); + } else if (items.length === 1) { + selectPurchaseItem(items[0]); + } else { + searchPurchaseItems(query); + } + }); + } else if (e.key === 'Escape') { + if (resultsEl) resultsEl.style.display = 'none'; + } + }); + + document.addEventListener('click', function(e) { + if (resultsEl && !input.contains(e.target) && !resultsEl.contains(e.target)) { + resultsEl.style.display = 'none'; + } + }); } function recordPurchase() { @@ -506,10 +630,6 @@ if (result && result.operation_id) { document.getElementById('purchaseResult').innerHTML = 'Compra registrada (op #' + result.operation_id + ')'; closePurchaseModal(); - ['purchaseItemId','purchaseQty','purchaseCost','purchaseInvoice','purchaseNotes'].forEach(function(id) { - var el = document.getElementById(id); - if (el) el.value = ''; - }); if (window.loadInventoryStats) window.loadInventoryStats(); loadItems(currentPage); } else { @@ -2009,4 +2129,5 @@ loadItems(1); renderSavedFilters(); + wirePurchaseSearch(); })(); diff --git a/pos/templates/inventory.html b/pos/templates/inventory.html index 6d85988..4e9409c 100644 --- a/pos/templates/inventory.html +++ b/pos/templates/inventory.html @@ -760,7 +760,13 @@
-
+
+ + + + +
+
@@ -1053,7 +1059,7 @@ - +