diff --git a/pos/blueprints/inventory_bp.py b/pos/blueprints/inventory_bp.py
index d741e9a..590e316 100644
--- a/pos/blueprints/inventory_bp.py
+++ b/pos/blueprints/inventory_bp.py
@@ -2271,6 +2271,22 @@ def list_inventory_subcategories(category_id):
conn.close()
+@inventory_bp.route('/categories/all', methods=['GET'])
+@require_auth()
+def list_all_inventory_categories():
+ """Return all active categories (flat, with parent_id) for selectors."""
+ conn = get_tenant_conn(g.tenant_id)
+ try:
+ cur = conn.cursor()
+ cur.execute(
+ "SELECT id, name, parent_id FROM categories WHERE is_active = true ORDER BY name"
+ )
+ rows = cur.fetchall()
+ return jsonify({'categories': [{'id': r[0], 'name': r[1], 'parent_id': r[2]} for r in rows]})
+ finally:
+ conn.close()
+
+
# ─── Global Tier Discounts ───────────────────────
@inventory_bp.route('/tier-discounts', methods=['GET'])
diff --git a/pos/static/js/inventory.js b/pos/static/js/inventory.js
index 9cbdab4..b23c686 100644
--- a/pos/static/js/inventory.js
+++ b/pos/static/js/inventory.js
@@ -17,6 +17,11 @@
var compatSource = 'both'; // default, loaded from config
var inventorySearchController = null;
+ var user = window.POS_USER || {};
+ var userRole = (user.role || '').toLowerCase();
+ var userPerms = user.permissions || [];
+ var canEditPrices = userRole === 'owner' || userRole === 'admin' || userPerms.indexOf('config.edit_prices') !== -1;
+
// Load compatibility source setting
(function loadCompatSource() {
fetch('/pos/api/config/vehicle-compat-source', { headers: { 'Authorization': 'Bearer ' + token } })
@@ -181,6 +186,7 @@
'
' + esc(it.location) + ' | ' +
'' +
' ' +
+ ' ' +
' ' +
' ' +
' ' +
@@ -307,39 +313,49 @@
// CREATE ITEM (createModal)
// =====================================================================
- function loadCategories() {
+ function loadCategories(selectedId) {
var sel = document.getElementById('newCategory');
if (!sel) return;
- apiFetch(API + '/categories').then(function(data) {
+ apiFetch(API + '/categories/all').then(function(data) {
if (!data || !data.categories) return;
- sel.innerHTML = '';
- data.categories.forEach(function(c) {
- sel.innerHTML += '';
+ var cats = data.categories;
+ var top = cats.filter(function(c) { return !c.parent_id; });
+ var subs = cats.filter(function(c) { return c.parent_id; });
+ sel.innerHTML = '';
+ top.forEach(function(c) {
+ sel.innerHTML += '';
});
});
}
window.loadCategories = loadCategories;
function onCategoryChange(categoryId) {
- var subSel = document.getElementById('newSubcategory');
- if (!subSel) return;
- if (!categoryId) {
- subSel.innerHTML = '';
- subSel.disabled = true;
- return;
- }
- apiFetch(API + '/categories/' + categoryId + '/subcategories').then(function(data) {
- if (!data || !data.subcategories) return;
- subSel.innerHTML = '';
- data.subcategories.forEach(function(s) {
- subSel.innerHTML += '';
- });
- subSel.disabled = false;
- });
+ // Kept for backwards compatibility; selector is now flat.
}
window.onCategoryChange = onCategoryChange;
+ function resetCreateModal() {
+ document.getElementById('editItemId').value = '';
+ document.getElementById('createModalTitle').textContent = 'Nuevo Producto';
+ document.getElementById('createModalBtn').textContent = 'Crear Producto';
+ var ids = ['newPartNumber','newName','newBrand','newBarcode','newSku2','newSku3','newUnit','newCost','newPrice1','newMinStock','newInitialStock','newMaxStock','newTaxRate','newLocation','newDescription'];
+ ids.forEach(function(id) {
+ var el = document.getElementById(id);
+ if (el) el.value = '';
+ });
+ document.getElementById('newCategory').innerHTML = '';
+ document.getElementById('newIsActive').value = 'true';
+ document.getElementById('initialStockField').style.display = '';
+ document.querySelectorAll('#createModal .price-field input').forEach(function(el) { el.disabled = false; });
+ }
+
function showCreateModal() {
+ resetCreateModal();
document.getElementById('createModal').classList.add('is-open');
loadCategories();
// Attach AI classification on part number blur
@@ -357,6 +373,41 @@
}
}
+ function showEditItemModal(itemId) {
+ resetCreateModal();
+ document.getElementById('editItemId').value = itemId;
+ document.getElementById('createModalTitle').textContent = 'Editar Producto';
+ document.getElementById('createModalBtn').textContent = 'Guardar Cambios';
+ document.getElementById('initialStockField').style.display = 'none';
+ document.getElementById('createModal').classList.add('is-open');
+ apiFetch(API + '/items/' + itemId).then(function(it) {
+ if (!it) return;
+ document.getElementById('newPartNumber').value = it.part_number || '';
+ document.getElementById('newName').value = it.name || '';
+ document.getElementById('newBrand').value = it.brand || '';
+ document.getElementById('newBarcode').value = it.barcode || '';
+ document.getElementById('newUnit').value = it.unit || '';
+ document.getElementById('newCost').value = it.cost != null ? it.cost : '';
+ document.getElementById('newPrice1').value = it.price_1 != null ? it.price_1 : '';
+ document.getElementById('newMinStock').value = it.min_stock != null ? it.min_stock : '';
+ document.getElementById('newMaxStock').value = it.max_stock != null ? it.max_stock : '';
+ document.getElementById('newTaxRate').value = it.tax_rate != null ? it.tax_rate : '';
+ document.getElementById('newLocation').value = it.location || '';
+ document.getElementById('newDescription').value = it.description || '';
+ document.getElementById('newIsActive').value = (it.is_active === false ? 'false' : 'true');
+ document.getElementById('newSku2').value = (it.sku_aliases && it.sku_aliases[0]) ? it.sku_aliases[0].sku : '';
+ document.getElementById('newSku3').value = (it.sku_aliases && it.sku_aliases[1]) ? it.sku_aliases[1].sku : '';
+ if (!canEditPrices) {
+ document.querySelectorAll('#createModal .price-field input').forEach(function(el) { el.disabled = true; });
+ }
+ loadCategories(it.category_id);
+ }).catch(function(e) {
+ alert('Error al cargar producto: ' + e.message);
+ closeCreateModal();
+ });
+ }
+ window.showEditItemModal = showEditItemModal;
+
function classifyPartNumber(partNumber) {
var resultEl = document.getElementById('createResult');
resultEl.innerHTML = 'Consultando IA...';
@@ -387,58 +438,57 @@
function closeCreateModal() {
document.getElementById('createModal').classList.remove('is-open');
document.getElementById('createResult').innerHTML = '';
- var catSel = document.getElementById('newCategory');
- var subSel = document.getElementById('newSubcategory');
- if (catSel) catSel.innerHTML = '';
- if (subSel) { subSel.innerHTML = ''; subSel.disabled = true; }
+ resetCreateModal();
}
function createItem() {
- var elPrice2 = document.getElementById('newPrice2');
- var elPrice3 = document.getElementById('newPrice3');
+ var editId = document.getElementById('editItemId').value;
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,
+ unit: document.getElementById('newUnit').value.trim() || undefined,
cost: parseFloat(document.getElementById('newCost').value) || 0,
price_1: parseFloat(document.getElementById('newPrice1').value) || 0,
- price_2: elPrice2 ? (parseFloat(elPrice2.value) || 0) : 0,
- price_3: elPrice3 ? (parseFloat(elPrice3.value) || 0) : 0,
min_stock: parseInt(document.getElementById('newMinStock').value) || 0,
- initial_stock: parseInt(document.getElementById('newInitialStock').value) || 0,
+ max_stock: parseInt(document.getElementById('newMaxStock').value) || 0,
+ tax_rate: parseFloat(document.getElementById('newTaxRate').value) || 0,
location: document.getElementById('newLocation').value.trim(),
+ description: document.getElementById('newDescription').value.trim(),
+ is_active: document.getElementById('newIsActive').value === 'true',
sku_aliases: []
};
var sku2 = document.getElementById('newSku2').value.trim();
var sku3 = document.getElementById('newSku3').value.trim();
var categoryId = document.getElementById('newCategory').value;
- var subcategoryId = document.getElementById('newSubcategory').value;
+ if (categoryId) data.category_id = parseInt(categoryId);
if (sku2) data.sku_aliases.push({sku: sku2, label: 'Alternativo 1'});
if (sku3) data.sku_aliases.push({sku: sku3, label: 'Alternativo 2'});
- if (subcategoryId) {
- data.category_id = parseInt(subcategoryId);
- } else if (categoryId) {
- data.category_id = parseInt(categoryId);
- }
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) {
+
+ var url = API + '/items';
+ var method = 'POST';
+ if (editId) {
+ url = API + '/items/' + editId;
+ method = 'PUT';
+ // Stock inicial solo en creación
+ } else {
+ data.initial_stock = parseInt(document.getElementById('newInitialStock').value) || 0;
+ }
+
+ apiFetch(url, { method: method, body: JSON.stringify(data) }).then(function (result) {
+ if (result && (result.id || result.message)) {
+ var msg = editId ? 'Producto actualizado' : ('Creado ID ' + result.id + ' | Barcode: ' + result.barcode);
+ if (!editId && 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','newSku2','newSku3','newCost','newPrice1','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') + '';
diff --git a/pos/templates/inventory.html b/pos/templates/inventory.html
index 3967057..df89914 100644
--- a/pos/templates/inventory.html
+++ b/pos/templates/inventory.html
@@ -15,7 +15,7 @@
-
+
@@ -710,42 +710,48 @@
-
+
@@ -1058,7 +1064,7 @@
-
+
|