// /home/Autopartes/pos/static/js/inventory.js // Inventory management UI — rewritten to match design-system HTML structure // Panels: panel-stock, panel-entradas, panel-salidas, panel-traspasos, panel-ajustes, panel-conteos, panel-alertas (function () { 'use strict'; var API = '/pos/api/inventory'; var token = localStorage.getItem('pos_token'); if (!token) { window.location.href = '/pos/login'; return; } var headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; var currentPage = 1; var currentSearch = ''; var draftCountId = null; var inventoryVS = null; var compatSource = 'both'; // default, loaded from config // Load compatibility source setting (function loadCompatSource() { fetch('/pos/api/config/vehicle-compat-source', { headers: { 'Authorization': 'Bearer ' + token } }) .then(function(r) { return r.json(); }) .then(function(d) { if (d.source) compatSource = d.source; }).catch(function() {}); })(); // --- API helper --- function apiFetch(url, opts) { return fetch(url, Object.assign({ headers: headers }, opts || {})) .then(function (resp) { if (resp.status === 401) { localStorage.removeItem('pos_token'); window.location.href = '/pos/login'; return null; } return resp.json(); }); } // --- Helpers --- function fmt(n) { return (parseFloat(n) || 0).toFixed(2); } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // ===================================================================== // TAB SWITCHING — uses design-system switchTab() already in the HTML. // We hook into it to trigger data loads when tabs are activated. // ===================================================================== var _origSwitchTab = window.switchTab; window.switchTab = function (name) { if (typeof _origSwitchTab === 'function') _origSwitchTab(name); if (name === 'alertas') loadAlerts(); if (name === 'stock') loadItems(currentPage); }; // ===================================================================== // STOCK / PRODUCTS (panel-stock) // ===================================================================== function renderInventoryRow(it) { return '' + '' + esc(it.barcode) + '' + '' + esc(it.part_number) + '' + '' + esc(it.name) + '' + '' + esc(it.brand) + '' + '' + it.stock + '' + '$' + fmt(it.cost) + '' + '$' + fmt(it.price_1) + '' + '$' + fmt(it.price_2) + '' + '$' + fmt(it.price_3) + '' + '' + esc(it.location) + '' + '' + ' ' + '' + ''; } function loadItems(page, search) { currentPage = page || 1; currentSearch = search !== undefined ? search : currentSearch; var params = new URLSearchParams({ page: currentPage, per_page: 50 }); if (currentSearch) params.set('q', currentSearch); apiFetch(API + '/items?' + params.toString()).then(function (data) { if (!data) return; var tbody = document.getElementById('productTableBody'); var items = data.data || []; if (!items.length) { tbody.innerHTML = 'Sin productos'; document.getElementById('productPagination').innerHTML = ''; return; } if (!inventoryVS) { inventoryVS = new VirtualScroll({ container: tbody, rowHeight: 48, buffer: 3, renderRow: renderInventoryRow, emptyHtml: 'Sin productos' }); } inventoryVS.setData(items); // Pagination var pg = data.pagination || {}; var pgEl = document.getElementById('productPagination'); if (pg.total_pages > 1) { pgEl.innerHTML = ''; } else { pgEl.innerHTML = '' + (pg.total || 0) + ' productos'; } }); } // Search var searchInput = document.getElementById('productSearch'); var searchTimeout; if (searchInput) { searchInput.addEventListener('input', function () { clearTimeout(searchTimeout); searchTimeout = setTimeout(function () { loadItems(1, searchInput.value.trim()); }, 350); }); } // ===================================================================== // CREATE ITEM (createModal) // ===================================================================== function showCreateModal() { document.getElementById('createModal').classList.add('is-open'); // Attach AI classification on part number blur var pnInput = document.getElementById('newPartNumber'); if (pnInput && !pnInput._classifyBound) { pnInput._classifyBound = true; pnInput.addEventListener('blur', function () { var pn = this.value.trim(); if (pn.length < 3) return; var nameInput = document.getElementById('newName'); // Only auto-classify if name is still empty if (nameInput && nameInput.value.trim()) return; classifyPartNumber(pn); }); } } function classifyPartNumber(partNumber) { var resultEl = document.getElementById('createResult'); resultEl.innerHTML = 'Consultando IA...'; apiFetch(API + '/classify/' + encodeURIComponent(partNumber)).then(function (data) { if (!data) return; if (data.name) { document.getElementById('newName').value = data.name; } if (data.brand) { document.getElementById('newBrand').value = data.brand; } // Show suggestion label var parts = []; if (data.name) parts.push(data.name); if (data.brand) parts.push(data.brand); if (data.vehicle) parts.push(data.vehicle); if (data.category) parts.push(data.category); if (parts.length > 0) { resultEl.innerHTML = 'Sugerido por IA: ' + esc(parts.join(' | ')) + ''; } else { resultEl.innerHTML = 'IA no pudo identificar este numero de parte'; } }).catch(function () { resultEl.innerHTML = ''; }); } function closeCreateModal() { document.getElementById('createModal').classList.remove('is-open'); document.getElementById('createResult').innerHTML = ''; } function createItem() { var data = { part_number: document.getElementById('newPartNumber').value.trim(), name: document.getElementById('newName').value.trim(), brand: document.getElementById('newBrand').value.trim(), barcode: document.getElementById('newBarcode').value.trim() || undefined, cost: parseFloat(document.getElementById('newCost').value) || 0, price_1: parseFloat(document.getElementById('newPrice1').value) || 0, price_2: parseFloat(document.getElementById('newPrice2').value) || 0, price_3: parseFloat(document.getElementById('newPrice3').value) || 0, min_stock: parseInt(document.getElementById('newMinStock').value) || 0, initial_stock: parseInt(document.getElementById('newInitialStock').value) || 0, location: document.getElementById('newLocation').value.trim() }; if (!data.part_number || !data.name) { document.getElementById('createResult').innerHTML = 'Numero de parte y nombre son obligatorios'; return; } apiFetch(API + '/items', { method: 'POST', body: JSON.stringify(data) }).then(function (result) { if (result && result.id) { var msg = 'Creado ID ' + result.id + ' | Barcode: ' + result.barcode; if (result.vehicle_compatibilities_added > 0) { msg += ' | ' + result.vehicle_compatibilities_added + ' vehiculo(s) asignado(s) por IA'; } document.getElementById('createResult').innerHTML = '' + msg + ''; loadItems(currentPage); // Close modal, clear form, refresh badges closeCreateModal(); ['newPartNumber','newName','newBrand','newBarcode','newCost','newPrice1','newPrice2','newPrice3','newMinStock','newInitialStock','newLocation'].forEach(function(id) { var el = document.getElementById(id); if (el) el.value = ''; }); if (window.loadInventoryStats) window.loadInventoryStats(); } else { document.getElementById('createResult').innerHTML = '' + (result ? result.error || 'Error' : 'Error de red') + ''; } }); } // ===================================================================== // PURCHASE / ENTRADA (purchaseModal) // ===================================================================== function showPurchaseModal() { document.getElementById('purchaseModal').classList.add('is-open'); } function closePurchaseModal() { document.getElementById('purchaseModal').classList.remove('is-open'); document.getElementById('purchaseResult').innerHTML = ''; } function recordPurchase() { var data = { inventory_id: parseInt(document.getElementById('purchaseItemId').value), quantity: parseInt(document.getElementById('purchaseQty').value), unit_cost: parseFloat(document.getElementById('purchaseCost').value), supplier_invoice: document.getElementById('purchaseInvoice').value.trim(), notes: document.getElementById('purchaseNotes').value.trim() }; if (!data.inventory_id || !data.quantity || !data.unit_cost) { document.getElementById('purchaseResult').innerHTML = 'Complete todos los campos obligatorios'; return; } apiFetch(API + '/purchase', { method: 'POST', body: JSON.stringify(data) }).then(function (result) { document.getElementById('purchaseResult').innerHTML = result && result.operation_id ? 'Compra registrada (op #' + result.operation_id + ')' : '' + (result ? result.error || 'Error' : 'Error de red') + ''; }); } // ===================================================================== // ADJUSTMENT / AJUSTE (adjustmentModal) // ===================================================================== function showAdjustmentModal() { document.getElementById('adjustmentModal').classList.add('is-open'); } function closeAdjustmentModal() { document.getElementById('adjustmentModal').classList.remove('is-open'); document.getElementById('adjustResult').innerHTML = ''; } function recordAdjustment() { var data = { inventory_id: parseInt(document.getElementById('adjustItemId').value), quantity: parseInt(document.getElementById('adjustQty').value), reason: document.getElementById('adjustReason').value.trim() }; if (!data.inventory_id || data.quantity === undefined || !data.reason) { document.getElementById('adjustResult').innerHTML = 'Complete todos los campos (razon obligatoria)'; return; } apiFetch(API + '/adjustment', { method: 'POST', body: JSON.stringify(data) }).then(function (result) { document.getElementById('adjustResult').innerHTML = result && result.operation_id ? 'Ajuste registrado (op #' + result.operation_id + ')' : '' + (result ? result.error || 'Error' : 'Error de red') + ''; }); } // ===================================================================== // TRANSFER / TRASPASO (transferModal) // ===================================================================== function showTransferModal() { document.getElementById('transferModal').classList.add('is-open'); } function closeTransferModal() { document.getElementById('transferModal').classList.remove('is-open'); document.getElementById('transferResult').innerHTML = ''; } function recordTransfer() { var data = { inventory_id: parseInt(document.getElementById('transferItemId').value), from_branch_id: parseInt(document.getElementById('transferFrom').value), to_branch_id: parseInt(document.getElementById('transferTo').value), quantity: parseInt(document.getElementById('transferQty').value), notes: document.getElementById('transferNotes').value.trim() }; if (!data.inventory_id || !data.from_branch_id || !data.to_branch_id || !data.quantity) { document.getElementById('transferResult').innerHTML = 'Complete todos los campos'; return; } apiFetch(API + '/transfer', { method: 'POST', body: JSON.stringify(data) }).then(function (result) { document.getElementById('transferResult').innerHTML = result && result.out_operation_id ? 'Transferencia registrada' : '' + (result ? result.error || 'Error' : 'Error de red') + ''; }); } // ===================================================================== // PHYSICAL COUNT / CONTEO (countModal) // ===================================================================== function showCountModal() { document.getElementById('countModal').classList.add('is-open'); // Pre-add one line if empty if (!document.querySelectorAll('#countLines .count-row').length) { addCountLine(); } } function closeCountModal() { document.getElementById('countModal').classList.remove('is-open'); } function addCountLine() { var container = document.getElementById('countLines'); var row = document.createElement('div'); row.className = 'count-row'; row.innerHTML = '' + '' + ''; container.appendChild(row); } function startPhysicalCount() { var rows = document.querySelectorAll('#countLines .count-row'); var items = []; rows.forEach(function (row) { var invId = parseInt(row.querySelector('.count-inv-id').value); var qty = parseInt(row.querySelector('.count-qty').value); if (invId && !isNaN(qty)) items.push({ inventory_id: invId, counted_quantity: qty }); }); if (!items.length) { alert('Agregue al menos una linea'); return; } apiFetch(API + '/physical-count/start', { method: 'POST', body: JSON.stringify({ items: items }) }).then(function (result) { if (!result || !result.count_id) { document.getElementById('countResults').innerHTML = '' + (result ? result.error || 'Error' : 'Error de red') + ''; return; } draftCountId = result.count_id; var html = '

Borrador #' + result.count_id + ' — ' + esc(result.message) + '

'; html += ''; (result.results || []).forEach(function (r) { var color = r.difference === 0 ? 'var(--color-success)' : (r.difference < 0 ? 'var(--color-error)' : 'var(--color-warning)'); html += ''; }); html += '
IDEsperadoContadoDiferencia
' + r.inventory_id + '' + r.expected + '' + r.counted + '' + (r.difference > 0 ? '+' : '') + r.difference + '
'; html += '
'; html += ''; html += ''; html += '
'; document.getElementById('countResults').innerHTML = html; }); } function approvePhysicalCount() { if (!draftCountId) { alert('No hay borrador activo'); return; } apiFetch(API + '/physical-count/approve', { method: 'POST', body: JSON.stringify({ count_id: draftCountId }) }).then(function (result) { if (result && result.status === 'approved') { document.getElementById('countResults').innerHTML = '' + esc(result.message) + ''; draftCountId = null; } else { document.getElementById('countResults').innerHTML += '
' + (result ? result.error || 'Error' : 'Error de red') + ''; } }); } function cancelDraft() { draftCountId = null; document.getElementById('countResults').innerHTML = 'Borrador cancelado'; } // ===================================================================== // ALERTS (panel-alertas) // ===================================================================== function loadAlerts() { apiFetch(API + '/alerts').then(function (data) { if (!data) return; var alerts = data.data || []; var container = document.getElementById('alertsContent'); if (!container) return; if (!alerts.length) { container.innerHTML = '

Sin alertas activas

'; return; } var html = ''; // Group by severity var critical = alerts.filter(function (a) { return a.severity === 'critical'; }); var warning = alerts.filter(function (a) { return a.severity === 'warning'; }); var info = alerts.filter(function (a) { return a.severity !== 'critical' && a.severity !== 'warning'; }); if (critical.length) { html += '
Criticas
' + critical.length + '
'; html += '
'; critical.forEach(function (a) { var icon = a.type === 'zero' ? 'AGOTADO' : (a.type === 'low' ? 'BAJO' : a.type.toUpperCase()); html += buildAlertCard(a, icon, 'critical'); }); html += '
'; } if (warning.length) { html += '
Advertencias
' + warning.length + '
'; html += '
'; warning.forEach(function (a) { html += buildAlertCard(a, 'EXCESO', 'warning'); }); html += '
'; } if (info.length) { html += '
Informativas
' + info.length + '
'; html += '
'; info.forEach(function (a) { html += buildAlertCard(a, 'INFO', 'info'); }); html += '
'; } container.innerHTML = html; }); } function buildAlertCard(a, icon, level) { var cls = level === 'critical' ? 'alert-card--critical' : (level === 'warning' ? 'alert-card--warning' : 'alert-card--info'); return '
' + '
' + '
' + '
[' + icon + '] ' + esc(a.part_number) + ' — ' + esc(a.name) + '
' + '
Stock: ' + a.stock + (a.min_stock ? ' (min: ' + a.min_stock + ')' : '') + (a.max_stock ? ' (max: ' + a.max_stock + ')' : '') + ' · Sucursal ' + a.branch_id + '
' + '
'; } // ===================================================================== // HISTORY MODAL // ===================================================================== function viewHistory(itemId) { apiFetch(API + '/items/' + itemId + '/history').then(function (data) { if (!data) return; var history = data.data || []; var html = ''; if (!history.length) { html = '

Sin movimientos

'; } else { html = ''; history.forEach(function (h) { var qtyColor = h.quantity > 0 ? 'var(--color-success)' : 'var(--color-error)'; html += '' + '' + '' + '' + '' + '' + '' + ''; }); html += '
FechaTipoCantidadCostoEmpleadoNotas
' + esc(h.date) + '' + esc(h.type) + '' + (h.quantity > 0 ? '+' : '') + h.quantity + '' + (h.cost ? '$' + fmt(h.cost) : '—') + '' + esc(h.employee) + '' + esc(h.notes) + '
'; } document.getElementById('historyContent').innerHTML = html; document.getElementById('historyModal').classList.add('is-open'); }); } function closeHistoryModal() { document.getElementById('historyModal').classList.remove('is-open'); } // ===================================================================== // BARCODE LABEL PRINT // ===================================================================== function printBarcode(barcode, partNumber, name) { var w = window.open('', '_blank', 'width=400,height=250'); w.document.write('Etiqueta'); w.document.write('

' + barcode + '

'); w.document.write('

' + partNumber + '

'); w.document.write('

' + name + '

'); w.document.write(''); w.document.close(); w.print(); } // ===================================================================== // PRODUCT DETAIL MODAL (shows item info + movement history) // ===================================================================== function uploadItemImage(itemId) { var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/jpeg,image/png,image/webp'; input.onchange = function () { if (!input.files || !input.files[0]) return; var file = input.files[0]; if (file.size > 5 * 1024 * 1024) { alert('Imagen demasiado grande (max 5 MB)'); return; } var fd = new FormData(); fd.append('file', file); var statusEl = document.getElementById('imgUploadStatus'); if (statusEl) statusEl.textContent = 'Subiendo...'; fetch(API + '/items/' + itemId + '/image', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: fd }) .then(function (r) { return r.json(); }) .then(function (result) { if (result.image_url) { // Refresh detail view viewProductDetail(itemId); } else { if (statusEl) statusEl.textContent = result.error || 'Error'; } }) .catch(function () { if (statusEl) statusEl.textContent = 'Error de red'; }); }; input.click(); } function deleteItemImage(itemId) { if (!confirm('Eliminar imagen de este producto?')) return; fetch(API + '/items/' + itemId + '/image', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }) .then(function (r) { return r.json(); }) .then(function (result) { if (result.message) { viewProductDetail(itemId); } else { alert(result.error || 'Error'); } }) .catch(function () { alert('Error de red'); }); } function viewProductDetail(itemId) { apiFetch(API + '/items/' + itemId).then(function (data) { if (!data || data.error) { alert(data ? data.error : 'Error de red'); return; } var history = data.history || []; var html = ''; // Product image section html += '
'; if (data.image_url) { html += '' + esc(data.name) + ''; html += '
'; html += ''; html += ''; html += '
'; } else { html += '
'; html += ''; html += '
Sin imagen
'; html += '
'; html += ''; } html += ''; html += '
'; // Product info header html += '
'; html += '
No. Parte' + esc(data.part_number) + '
'; html += '
Nombre' + esc(data.name) + '
'; html += '
Marca' + esc(data.brand) + '
'; html += '
Codigo de Barras' + esc(data.barcode) + '
'; html += '
Ubicacion' + esc(data.location || '-') + '
'; html += '
Stock' + (data.stock || 0) + '
'; html += '
'; // Prices html += '
'; html += '
Costo$' + fmt(data.cost) + '
'; html += '
Precio 1$' + fmt(data.price_1) + '
'; html += '
Precio 2$' + fmt(data.price_2) + '
'; html += '
Precio 3$' + fmt(data.price_3) + '
'; html += '
'; // Cross-references section html += '
Cross-References / Equivalencias
'; html += '
'; html += '

Cargando equivalencias...

'; html += '
'; // Load cross-references from catalog API var partNumber = data.part_number; var catalogPartId = data.catalog_part_id; (function loadCrossRefs() { // Try catalog part detail if we have catalog_part_id var url = catalogPartId ? '/pos/api/catalog/part/' + catalogPartId : '/pos/api/catalog/search?q=' + encodeURIComponent(partNumber); fetch(url, { headers: { 'Authorization': 'Bearer ' + token } }) .then(function(r) { return r.json(); }) .then(function(d) { var el = document.getElementById('crossRefContent'); if (!el) return; var alternatives = d.alternatives || []; var bodegas = d.bodegas || []; // If it was a search, get alternatives from first result if (!catalogPartId && d.data && d.data.length > 0) { // Fetch detail for first match fetch('/pos/api/catalog/part/' + d.data[0].id_part, { headers: { 'Authorization': 'Bearer ' + token } }) .then(function(r2) { return r2.json(); }) .then(function(d2) { renderCrossRefs(el, d2.alternatives || [], d2.bodegas || []); }) .catch(function() { el.innerHTML = '

Sin conexion al catalogo.

'; }); return; } renderCrossRefs(el, alternatives, bodegas); }) .catch(function() { var el = document.getElementById('crossRefContent'); if (el) el.innerHTML = '

Sin conexion al catalogo central.

'; }); })(); function renderCrossRefs(el, alternatives, bodegas) { var html2 = ''; if (bodegas && bodegas.length > 0) { html2 += '
Disponible en Bodegas:
'; html2 += ''; bodegas.forEach(function(b) { html2 += ''; }); html2 += '
BodegaStockPrecioUbicacion
' + esc(b.business_name || b.bodega || '') + '' + (b.stock || b.stock_quantity || 0) + '$' + fmt(b.price || 0) + '' + esc(b.location || b.warehouse_location || '') + '
'; } if (alternatives && alternatives.length > 0) { html2 += '
Partes Equivalentes (Aftermarket):
'; html2 += ''; alternatives.forEach(function(a) { html2 += ''; }); html2 += '
No. ParteFabricanteNombre
' + esc(a.part_number || a.cross_reference_number || '') + '' + esc(a.manufacturer || a.source_ref || '') + '' + esc(a.name || a.name_aftermarket_parts || '') + '
'; } if (!html2) { html2 = '

No se encontraron equivalencias para esta parte.

'; } el.innerHTML = html2; } // Vehicle compatibility section html += '
Vehiculos Compatibles
'; html += '
'; html += '

Cargando compatibilidades...

'; html += '
'; // Load vehicle compatibilities (function loadCompat() { fetch('/pos/api/inventory/items/' + itemId + '/vehicles', { headers: { 'Authorization': 'Bearer ' + token } }) .then(function(r) { return r.json(); }) .then(function(d) { var el = document.getElementById('compatContent'); if (!el) return; var list = d.vehicles || []; var html2 = ''; if (list.length > 0) { html2 += ''; list.forEach(function(c) { var sourceLabel = c.source === 'qwen_ai' ? 'IA' : (c.source === 'auto_match' ? 'TecDoc' : esc(c.source || '')); html2 += ''; html2 += ''; }); html2 += '
MarcaModeloAnoMotorOrigen
' + esc(c.brand || '') + '' + esc(c.model || '') + '' + esc(c.year || '') + '' + esc(c.engine || '') + '' + sourceLabel + '
'; } else { html2 += '

Sin vehiculos vinculados.

'; } var btnLabel = compatSource === 'qwen' ? 'Auto-Match con IA (QWEN)' : (compatSource === 'both' ? 'Auto-Match (TecDoc + IA)' : 'Auto-Match por TecDoc'); var btnDesc = compatSource === 'qwen' ? 'Busca compatibilidad usando inteligencia artificial' : (compatSource === 'both' ? 'Busca en catalogo central y con IA' : 'Busca en catalogo central y vincula automaticamente'); html2 += '
' + btnDesc + '
'; el.innerHTML = html2; }) .catch(function() { var el = document.getElementById('compatContent'); if (el) el.innerHTML = '

Error al cargar compatibilidades.

'; }); })(); // Movement history html += '
Historial de Movimientos
'; if (!history.length) { html += '

Sin movimientos

'; } else { html += ''; history.forEach(function (h) { var qtyColor = h.quantity > 0 ? 'var(--color-success)' : 'var(--color-error)'; html += '' + '' + '' + '' + '' + '' + '' + ''; }); html += '
FechaTipoCantidadCostoEmpleadoNotas
' + esc(h.date) + '' + esc(h.type) + '' + (h.quantity > 0 ? '+' : '') + h.quantity + '' + (h.cost ? '$' + fmt(h.cost) : '\u2014') + '' + esc(h.employee) + '' + esc(h.notes) + '
'; } document.getElementById('historyContent').innerHTML = html; document.getElementById('historyModal').classList.add('is-open'); }); } // Vehicle compatibility actions function autoMatchCompat(itemId) { fetch('/pos/api/inventory/items/' + itemId + '/vehicles/auto-match', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } }).then(function(r) { return r.json(); }) .then(function(d) { var msg = ''; if (d.tecdoc && d.qwen) { var t = d.tecdoc.matched ? (d.tecdoc.matched_count || d.tecdoc.matches ? d.tecdoc.matches.length : 0) : 0; var q = d.qwen.total_qwen || 0; var qi = d.qwen.inserted || 0; msg = 'Auto-match completado.\nTecDoc: ' + t + ' vehiculos.\nIA QWEN: ' + qi + ' nuevos vinculados (de ' + q + ' encontrados).'; } else if (d.myes) { msg = 'Auto-match completado. Vehiculos encontrados: ' + (d.total_qwen || d.myes.length) + ' (nuevos vinculados: ' + (d.inserted || 0) + ')'; } else { msg = 'Auto-match completado. Vehiculos vinculados: ' + (d.matched ? 'Si' : 'No'); } alert(msg); viewProductDetail(itemId); }).catch(function() { alert('Error en auto-match'); }); } function removeCompat(itemId, myeId) { if (!confirm('Quitar compatibilidad con este vehiculo?')) return; fetch('/pos/api/inventory/items/' + itemId + '/compatibility/' + myeId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }).then(function(r) { return r.json(); }) .then(function() { viewProductDetail(itemId); }).catch(function() { alert('Error al quitar compatibilidad'); }); } // ===================================================================== // EXPOSE GLOBALS (for onclick handlers in HTML) // ===================================================================== window._loadItems = function (p) { loadItems(p); }; window.loadItems = function (p, q) { loadItems(p, q); }; window.viewHistory = viewHistory; window.viewProductDetail = viewProductDetail; window.uploadItemImage = uploadItemImage; window.deleteItemImage = deleteItemImage; window.closeHistoryModal = closeHistoryModal; window.showCreateModal = showCreateModal; window.closeCreateModal = closeCreateModal; window.createItem = createItem; window.showPurchaseModal = showPurchaseModal; window.closePurchaseModal = closePurchaseModal; window.recordPurchase = recordPurchase; window.showAdjustmentModal = showAdjustmentModal; window.closeAdjustmentModal = closeAdjustmentModal; window.recordAdjustment = recordAdjustment; window.showTransferModal = showTransferModal; window.closeTransferModal = closeTransferModal; window.recordTransfer = recordTransfer; window.showCountModal = showCountModal; window.closeCountModal = closeCountModal; window.addCountLine = addCountLine; window.startPhysicalCount = startPhysicalCount; window.approvePhysicalCount = approvePhysicalCount; window.cancelDraft = cancelDraft; window.loadAlerts = loadAlerts; window.printBarcode = printBarcode; window.autoMatchCompat = autoMatchCompat; window.removeCompat = removeCompat; // ===================================================================== // INIT — load stock on page load // ===================================================================== loadItems(1); })();