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
This commit is contained in:
@@ -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 =
|
||||
'<strong>' + esc(item.name || '') + '</strong>' +
|
||||
(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 = '<div style="padding:var(--space-3);color:var(--color-text-muted);font-size:var(--text-caption);">Sin resultados</div>';
|
||||
resultsEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
resultsEl.innerHTML = items.map(function(it) {
|
||||
return '<div class="purchase-search-result" style="padding:var(--space-3);cursor:pointer;border-bottom:1px solid var(--color-border);" ' +
|
||||
'data-id="' + it.id + '">' +
|
||||
'<div style="font-weight:var(--font-weight-semibold);">' + esc(it.name) + '</div>' +
|
||||
'<div style="font-size:var(--text-caption);color:var(--color-text-muted);">' +
|
||||
(it.part_number ? 'No. parte: ' + esc(it.part_number) + ' · ' : '') +
|
||||
(it.barcode ? 'Barcode: ' + esc(it.barcode) + ' · ' : '') +
|
||||
'Stock: ' + (it.stock || 0) +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}).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 = '<span style="color:var(--color-success);">Compra registrada (op #' + result.operation_id + ')</span>';
|
||||
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();
|
||||
})();
|
||||
|
||||
@@ -760,7 +760,13 @@
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<div class="inv-form-grid">
|
||||
<div class="inv-field"><label>ID Producto *</label><input type="number" id="purchaseItemId" placeholder="ID inventario" /></div>
|
||||
<div class="inv-field inv-field--full" style="position:relative;">
|
||||
<label>Producto *</label>
|
||||
<input type="hidden" id="purchaseItemId" />
|
||||
<input type="text" id="purchaseItemSearch" placeholder="Escribe nombre, No. de parte o escanea código de barras..." autocomplete="off" />
|
||||
<div id="purchaseItemResults" style="position:absolute;left:0;right:0;top:100%;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-md);max-height:200px;overflow-y:auto;z-index:100;display:none;"></div>
|
||||
<div id="purchaseItemSelected" style="margin-top:var(--space-1);font-size:var(--text-caption);color:var(--color-text-secondary);"></div>
|
||||
</div>
|
||||
<div class="inv-field"><label>Cantidad *</label><input type="number" id="purchaseQty" placeholder="Cantidad" /></div>
|
||||
<div class="inv-field"><label>Costo Unitario *</label><input type="number" id="purchaseCost" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Factura Proveedor</label><input type="text" id="purchaseInvoice" placeholder="No. factura" /></div>
|
||||
@@ -1053,7 +1059,7 @@
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/virtual-scroll.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=17" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=18" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
|
||||
Reference in New Issue
Block a user