// /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;
// --- 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 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;
}
tbody.innerHTML = items.map(function (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) + ' ' +
'' +
'Historial ' +
'Etiqueta ' +
' ';
}).join('');
// 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');
}
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) {
document.getElementById('createResult').innerHTML = 'Creado ID ' + result.id + ' | Barcode: ' + result.barcode + ' ';
loadItems(currentPage);
} 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 =
' ' +
' ' +
'Quitar ';
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 += 'ID Esperado Contado Diferencia ';
(result.results || []).forEach(function (r) {
var color = r.difference === 0 ? 'var(--color-success)' : (r.difference < 0 ? 'var(--color-error)' : 'var(--color-warning)');
html += '' + r.inventory_id + ' ' + r.expected + ' ' + r.counted + ' ' + (r.difference > 0 ? '+' : '') + r.difference + ' ';
});
html += '
';
html += '';
html += 'Aprobar y aplicar ajustes ';
html += 'Cancelar borrador ';
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 = 'Fecha Tipo Cantidad Costo Empleado Notas ';
history.forEach(function (h) {
var qtyColor = h.quantity > 0 ? 'var(--color-success)' : 'var(--color-error)';
html += '' +
'' + esc(h.date) + ' ' +
'' + esc(h.type) + ' ' +
'' + (h.quantity > 0 ? '+' : '') + h.quantity + ' ' +
'' + (h.cost ? '$' + fmt(h.cost) : '—') + ' ' +
'' + esc(h.employee) + ' ' +
'' + esc(h.notes) + ' ' +
' ';
});
html += '
';
}
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();
}
// =====================================================================
// EXPOSE GLOBALS (for onclick handlers in HTML)
// =====================================================================
window._loadItems = function (p) { loadItems(p); };
window.viewHistory = viewHistory;
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;
// =====================================================================
// INIT — load stock on page load
// =====================================================================
loadItems(1);
})();