feat(catalog): supplier catalog cleanup, fuzzy matching, and navigation fixes
- Cleaned 137+ fake engine-displacement models from supplier imports (v3/v4 scripts: Chevrolet, Ford, Chrysler, Dodge, Jeep, Nissan, etc.) - Removed 1,251+ corrupted models (INT. prefixes, year-suffix, torque specs, empty names, trailing-year variants) - Migrated supplier tables to master DB (supplier_catalog, supplier_catalog_compat, supplier_catalog_interchange) - Fixed _get_mye_ids_with_parts() to query supplier_catalog_compat from master DB so supplier-only vehicles appear for all tenants - Added fuzzy model matcher with parenthesis stripping, noise suffix removal, compact matching, prefix/substring fallback, model aliases, and ±3 year proximity - Matched compat rows: KEEP GREEN +14,152, KNADIAN +3,021, VAZLO +127,500, LUK +477, RAYBESTOS +1,743 - Added KNADIAN catalog importer with year-range expansion and future-year filtering - Added VAZLO catalog importer with position parsing and SKU-in-model cleanup - Added Keep Green, LUK, Yokomitsu, Raybestos catalog importers - Cache clearing after cleanups (_classify_cache_*, nexus:mye_ids:*, nexus:brand_mye_counts:*) Final match rates: - KEEP GREEN: 90.3% - VAZLO: 93.6% - YOKOMITSU: 100.0% - KNADIAN: 57.4% - LUK: 51.0% - RAYBESTOS: 55.9%
This commit is contained in:
@@ -287,8 +287,41 @@
|
||||
// CREATE ITEM (createModal)
|
||||
// =====================================================================
|
||||
|
||||
function loadCategories() {
|
||||
var sel = document.getElementById('newCategory');
|
||||
if (!sel) return;
|
||||
apiFetch(API + '/categories').then(function(data) {
|
||||
if (!data || !data.categories) return;
|
||||
sel.innerHTML = '<option value="">Selecciona categoría</option>';
|
||||
data.categories.forEach(function(c) {
|
||||
sel.innerHTML += '<option value="' + c.id + '">' + esc(c.name) + '</option>';
|
||||
});
|
||||
});
|
||||
}
|
||||
window.loadCategories = loadCategories;
|
||||
|
||||
function onCategoryChange(categoryId) {
|
||||
var subSel = document.getElementById('newSubcategory');
|
||||
if (!subSel) return;
|
||||
if (!categoryId) {
|
||||
subSel.innerHTML = '<option value="">Selecciona categoría primero</option>';
|
||||
subSel.disabled = true;
|
||||
return;
|
||||
}
|
||||
apiFetch(API + '/categories/' + categoryId + '/subcategories').then(function(data) {
|
||||
if (!data || !data.subcategories) return;
|
||||
subSel.innerHTML = '<option value="">Selecciona subcategoría</option>';
|
||||
data.subcategories.forEach(function(s) {
|
||||
subSel.innerHTML += '<option value="' + s.id + '">' + esc(s.name) + '</option>';
|
||||
});
|
||||
subSel.disabled = false;
|
||||
});
|
||||
}
|
||||
window.onCategoryChange = onCategoryChange;
|
||||
|
||||
function showCreateModal() {
|
||||
document.getElementById('createModal').classList.add('is-open');
|
||||
loadCategories();
|
||||
// Attach AI classification on part number blur
|
||||
var pnInput = document.getElementById('newPartNumber');
|
||||
if (pnInput && !pnInput._classifyBound) {
|
||||
@@ -334,6 +367,10 @@
|
||||
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 = '<option value="">Selecciona categoría</option>';
|
||||
if (subSel) { subSel.innerHTML = '<option value="">Selecciona categoría primero</option>'; subSel.disabled = true; }
|
||||
}
|
||||
|
||||
function createItem() {
|
||||
@@ -350,8 +387,20 @@
|
||||
price_3: elPrice3 ? (parseFloat(elPrice3.value) || 0) : 0,
|
||||
min_stock: parseInt(document.getElementById('newMinStock').value) || 0,
|
||||
initial_stock: parseInt(document.getElementById('newInitialStock').value) || 0,
|
||||
location: document.getElementById('newLocation').value.trim()
|
||||
location: document.getElementById('newLocation').value.trim(),
|
||||
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 (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 = '<span style="color:var(--color-error);">Numero de parte y nombre son obligatorios</span>';
|
||||
return;
|
||||
@@ -366,7 +415,7 @@
|
||||
loadItems(currentPage);
|
||||
// Close modal, clear form, refresh badges
|
||||
closeCreateModal();
|
||||
['newPartNumber','newName','newBrand','newBarcode','newCost','newPrice1','newMinStock','newInitialStock','newLocation'].forEach(function(id) {
|
||||
['newPartNumber','newName','newBrand','newBarcode','newSku2','newSku3','newCost','newPrice1','newMinStock','newInitialStock','newLocation'].forEach(function(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
@@ -377,6 +426,54 @@
|
||||
});
|
||||
}
|
||||
|
||||
function submitBulkImport() {
|
||||
var fileInput = document.getElementById('bulkImportFile');
|
||||
var resultEl = document.getElementById('bulkImportResult');
|
||||
var mode = document.getElementById('bulkImportMode').value;
|
||||
var strategy = document.getElementById('bulkImportStrategy').value;
|
||||
if (!fileInput.files || !fileInput.files[0]) {
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.innerHTML = '<span style="color:var(--color-error);">Selecciona un archivo CSV o Excel.</span>';
|
||||
return;
|
||||
}
|
||||
var file = fileInput.files[0];
|
||||
var formData = new FormData();
|
||||
formData.append('file', file);
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.innerHTML = '<span style="color:var(--color-text-muted);">Importando...</span>';
|
||||
fetch(API + '/items/bulk-import', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'X-Import-Mode': mode,
|
||||
'X-Import-Strategy': strategy
|
||||
},
|
||||
body: formData
|
||||
}).then(function(resp) { return resp.json(); }).then(function(data) {
|
||||
if (data.error) {
|
||||
resultEl.innerHTML = '<span style="color:var(--color-error);">' + esc(data.error) + '</span>';
|
||||
return;
|
||||
}
|
||||
var html = '<div style="color:var(--color-success);">Importacion completada: <strong>' + data.created + '</strong> producto(s) creado(s)';
|
||||
if (data.skipped > 0) html += ', <strong>' + data.skipped + '</strong> saltado(s)';
|
||||
html += '</div>';
|
||||
if (data.warnings && data.warnings.length) {
|
||||
html += '<div style="margin-top:8px;max-height:160px;overflow:auto;background:var(--color-surface);padding:8px;border-radius:6px;font-size:var(--text-caption);">';
|
||||
html += '<strong style="color:var(--color-warning);">Advertencias (' + data.warnings.length + '):</strong><ul style="margin:4px 0 0 16px;padding:0;">';
|
||||
data.warnings.forEach(function(w) {
|
||||
html += '<li>' + esc(w) + '</li>';
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
resultEl.innerHTML = html;
|
||||
loadItems(currentPage);
|
||||
if (window.loadInventoryStats) window.loadInventoryStats();
|
||||
}).catch(function(err) {
|
||||
resultEl.innerHTML = '<span style="color:var(--color-error);">Error de red: ' + esc(err.message) + '</span>';
|
||||
});
|
||||
}
|
||||
window.submitBulkImport = submitBulkImport;
|
||||
|
||||
// =====================================================================
|
||||
// PURCHASE / ENTRADA (purchaseModal)
|
||||
// =====================================================================
|
||||
@@ -1006,17 +1103,36 @@
|
||||
var attrName = esc(attr.name || attr.id);
|
||||
var inputHtml = '<input type="text" class="meli-title-input" id="meliAttr-' + attrId + '" placeholder="' + attrName + '">';
|
||||
if (attr.values && attr.values.length) {
|
||||
inputHtml = '<select class="meli-title-input" id="meliAttr-' + attrId + '">' +
|
||||
// Some ML attributes (like BRAND) have a closed list but the API still
|
||||
// accepts free-text via value_name. Provide a select + "Other" fallback.
|
||||
var selectId = 'meliAttrSel-' + attrId;
|
||||
var otherId = 'meliAttrOther-' + attrId;
|
||||
inputHtml = '<select class="meli-title-input" id="' + selectId + '" onchange="onMeliAttrSelectChange(\'' + attrId + '\')">' +
|
||||
'<option value="">Selecciona ' + attrName + '</option>' +
|
||||
attr.values.map(function(v) { return '<option value="' + esc(v.name) + '">' + esc(v.name) + '</option>'; }).join('') +
|
||||
'</select>';
|
||||
'<option value="__other__">Otra marca (escribir)...</option>' +
|
||||
'</select>' +
|
||||
'<input type="text" class="meli-title-input" id="' + otherId + '" placeholder="Escribe la ' + attrName + '" style="display:none;margin-top:6px;">';
|
||||
}
|
||||
html += '<div class="inv-field"><label>' + attrName + (attr.tags && attr.tags.required ? ' *' : '') + '</label>' + inputHtml + '</div>';
|
||||
html += '<div class="inv-field" id="meliAttrWrap-' + attrId + '"><label>' + attrName + (attr.tags && attr.tags.required ? ' *' : '') + '</label>' + inputHtml + '</div>';
|
||||
});
|
||||
grid.innerHTML = html;
|
||||
}).catch(function() { grid.innerHTML = '<p style="color:var(--color-error);font-size:var(--text-caption);">Error cargando atributos</p>'; });
|
||||
};
|
||||
|
||||
window.onMeliAttrSelectChange = function(attrId) {
|
||||
var sel = document.getElementById('meliAttrSel-' + attrId);
|
||||
var other = document.getElementById('meliAttrOther-' + attrId);
|
||||
if (!sel || !other) return;
|
||||
if (sel.value === '__other__') {
|
||||
other.style.display = 'block';
|
||||
other.focus();
|
||||
} else {
|
||||
other.style.display = 'none';
|
||||
other.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.handleMeliCatKeydown = function(e) {
|
||||
if (!meliCatItems.length) return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
@@ -1071,9 +1187,21 @@
|
||||
if (stockEl) customData.stocks[id] = parseInt(stockEl.value);
|
||||
var attrs = [];
|
||||
meliCategoryAttrs.forEach(function(attr) {
|
||||
var el = document.getElementById('meliAttr-' + attr.id);
|
||||
if (el && el.value) {
|
||||
attrs.push({ id: attr.id, value_name: el.value });
|
||||
var val = '';
|
||||
var sel = document.getElementById('meliAttrSel-' + attr.id);
|
||||
if (sel) {
|
||||
if (sel.value === '__other__') {
|
||||
var otherEl = document.getElementById('meliAttrOther-' + attr.id);
|
||||
val = otherEl ? otherEl.value : '';
|
||||
} else {
|
||||
val = sel.value;
|
||||
}
|
||||
} else {
|
||||
var el = document.getElementById('meliAttr-' + attr.id);
|
||||
if (el) val = el.value;
|
||||
}
|
||||
if (val) {
|
||||
attrs.push({ id: attr.id, value_name: val });
|
||||
}
|
||||
});
|
||||
if (attrs.length) customData.attributes[id] = attrs;
|
||||
@@ -1298,6 +1426,26 @@
|
||||
var history = data.history || [];
|
||||
var html = '';
|
||||
|
||||
// Tab styles
|
||||
html += '<style>';
|
||||
html += '.compat-tabs{display:flex;gap:4px;border-bottom:1px solid var(--color-border);margin-bottom:16px;}';
|
||||
html += '.compat-tab-btn{padding:8px 16px;font-size:var(--text-body-sm);font-weight:600;cursor:pointer;border:none;background:transparent;color:var(--color-text-muted);border-bottom:2px solid transparent;margin-bottom:-1px;}';
|
||||
html += '.compat-tab-btn.is-active{color:var(--color-primary);border-bottom-color:var(--color-primary);background:var(--color-surface-0);border-radius:var(--radius-sm) var(--radius-sm) 0 0;}';
|
||||
html += '.compat-tab-panel{display:none;}';
|
||||
html += '.compat-tab-panel.is-active{display:block;}';
|
||||
html += '.compat-form{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;}';
|
||||
html += '.compat-form label{font-size:var(--text-caption);color:var(--color-text-muted);display:block;margin-bottom:4px;}';
|
||||
html += '</style>';
|
||||
|
||||
// Tabs
|
||||
html += '<div class="compat-tabs">';
|
||||
html += '<button class="compat-tab-btn is-active" onclick="switchCompatTab(\'detail\',this)">Detalle</button>';
|
||||
html += '<button class="compat-tab-btn" onclick="switchCompatTab(\'compat\',this)">Compatibilidad</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Detail panel
|
||||
html += '<div id="compatTab-detail" class="compat-tab-panel is-active">';
|
||||
|
||||
// Product image section
|
||||
html += '<div style="text-align:center;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
if (data.image_url) {
|
||||
@@ -1330,12 +1478,19 @@
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">ID Inventario</span><strong style="font-family:var(--font-mono);">' + data.id + '</strong></div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">No. Parte</span><strong>' + esc(data.part_number) + '</strong></div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Nombre</span><strong>' + esc(data.name) + '</strong></div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Marca</span>' + esc(data.brand) + '</div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Marca</span>' + esc(data.brand || '-') + '</div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Categoría</span>' + esc(data.category_name || '-') + '</div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Codigo de Barras</span><span style="font-family:var(--font-mono);">' + esc(data.barcode) + '</span></div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Ubicacion</span>' + esc(data.location || '-') + '</div>';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Stock</span><strong style="font-size:1.2em;">' + (data.stock || 0) + '</strong></div>';
|
||||
html += '</div>';
|
||||
|
||||
// SKU Aliases section
|
||||
html += '<div style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--tracking-widest);margin-bottom:8px;">SKU Alternativos</div>';
|
||||
html += '<div id="skuAliasContent" style="margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
html += '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Cargando SKU alternativos...</p>';
|
||||
html += '</div>';
|
||||
|
||||
// Prices
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
html += '<div><span style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;display:block;">Costo</span><span class="td--amount">$' + fmt(data.cost) + '</span></div>';
|
||||
@@ -1415,14 +1570,67 @@
|
||||
el.innerHTML = html2;
|
||||
}
|
||||
|
||||
// Vehicle compatibility section
|
||||
html += '<div style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--tracking-widest);margin-bottom:8px;">Vehiculos Compatibles</div>';
|
||||
// Close detail panel
|
||||
html += '</div>';
|
||||
|
||||
// Compatibility panel
|
||||
html += '<div id="compatTab-compat" class="compat-tab-panel">';
|
||||
|
||||
// Existing compatibilities
|
||||
html += '<div id="compatContent" style="margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--color-border);">';
|
||||
html += '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Cargando compatibilidades...</p>';
|
||||
html += '</div>';
|
||||
|
||||
// Load vehicle compatibilities
|
||||
(function loadCompat() {
|
||||
// Manual add form
|
||||
html += '<div style="font-size:var(--text-caption);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--tracking-widest);margin-bottom:8px;">Agregar Manualmente</div>';
|
||||
html += '<div class="compat-form">';
|
||||
html += '<div><label>Marca</label><select class="select-filter" id="manualMake" onchange="onManualMakeChange(' + itemId + ')" style="width:100%;"><option value="">Cargando...</option></select></div>';
|
||||
html += '<div><label>Modelo</label><select class="select-filter" id="manualModel" onchange="onManualModelChange(' + itemId + ')" style="width:100%;" disabled><option value="">Selecciona marca</option></select></div>';
|
||||
html += '<div><label>Ano</label><select class="select-filter" id="manualYear" onchange="onManualYearChange(' + itemId + ')" style="width:100%;" disabled><option value="">Selecciona modelo</option></select></div>';
|
||||
html += '<div><label>Motor</label><select class="select-filter" id="manualEngine" style="width:100%;" disabled><option value="">Selecciona ano</option></select></div>';
|
||||
html += '</div>';
|
||||
html += '<button class="btn btn--primary btn--sm" onclick="submitManualCompat(' + itemId + ')">Agregar compatibilidad</button>';
|
||||
|
||||
// Auto-match button
|
||||
var btnLabel = compatSource === 'qwen' ? 'Auto-Match con IA (QWEN)' : (compatSource === 'both' ? 'Auto-Match (TecDoc + IA)' : 'Auto-Match por TecDoc');
|
||||
html += '<div style="margin-top:16px;"><button class="btn btn--ghost btn--sm" onclick="autoMatchCompat(' + itemId + ')">' + btnLabel + '</button></div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Load SKU aliases
|
||||
(function loadSkuAliases() {
|
||||
fetch('/pos/api/inventory/items/' + itemId + '/skus', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var el = document.getElementById('skuAliasContent');
|
||||
if (!el) return;
|
||||
var list = d.aliases || [];
|
||||
var html2 = '';
|
||||
if (list.length > 0) {
|
||||
html2 += '<table class="data-table"><thead><tr><th>SKU</th><th>Etiqueta</th><th></th></tr></thead><tbody>';
|
||||
list.forEach(function(a) {
|
||||
html2 += '<tr><td class="td--mono">' + esc(a.sku) + '</td><td>' + esc(a.label || '-') + '</td>';
|
||||
html2 += '<td><button class="btn btn--ghost btn--sm" style="color:var(--color-error);" onclick="removeSkuAlias(' + itemId + ',' + a.id + ')">Quitar</button></td></tr>';
|
||||
});
|
||||
html2 += '</tbody></table>';
|
||||
} else {
|
||||
html2 += '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Sin SKU alternativos.</p>';
|
||||
}
|
||||
html2 += '<div style="margin-top:8px;display:flex;gap:8px;">';
|
||||
html2 += '<input type="text" class="meli-title-input" id="newAliasSku-' + itemId + '" placeholder="Nuevo SKU" style="flex:1;">';
|
||||
html2 += '<input type="text" class="meli-title-input" id="newAliasLabel-' + itemId + '" placeholder="Etiqueta (opcional)" style="flex:1;">';
|
||||
html2 += '<button class="btn btn--primary btn--sm" onclick="addSkuAlias(' + itemId + ')">Agregar</button>';
|
||||
html2 += '</div>';
|
||||
el.innerHTML = html2;
|
||||
})
|
||||
.catch(function() {
|
||||
var el = document.getElementById('skuAliasContent');
|
||||
if (el) el.innerHTML = '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Error al cargar SKU alternativos.</p>';
|
||||
});
|
||||
})();
|
||||
|
||||
// Load vehicle compatibilities and makes
|
||||
(function loadCompatPanel() {
|
||||
fetch('/pos/api/inventory/items/' + itemId + '/vehicles', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
@@ -1441,15 +1649,28 @@
|
||||
} else {
|
||||
html2 += '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Sin vehiculos vinculados.</p>';
|
||||
}
|
||||
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 += '<div style="margin-top:8px;"><button class="btn btn--primary btn--sm" onclick="autoMatchCompat(' + itemId + ')">' + btnLabel + '</button> <span style="font-size:var(--text-caption);color:var(--color-text-muted);">' + btnDesc + '</span></div>';
|
||||
el.innerHTML = html2;
|
||||
})
|
||||
.catch(function() {
|
||||
var el = document.getElementById('compatContent');
|
||||
if (el) el.innerHTML = '<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Error al cargar compatibilidades.</p>';
|
||||
});
|
||||
|
||||
// Load makes
|
||||
fetch('/pos/api/inventory/vehicles/makes', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var sel = document.getElementById('manualMake');
|
||||
if (!sel) return;
|
||||
var opts = '<option value="">Selecciona marca</option>';
|
||||
(d.makes || []).forEach(function(m) { opts += '<option value="' + esc(m.name) + '" data-id="' + m.id + '">' + esc(m.name) + '</option>'; });
|
||||
sel.innerHTML = opts;
|
||||
sel.disabled = false;
|
||||
})
|
||||
.catch(function() {
|
||||
var sel = document.getElementById('manualMake');
|
||||
if (sel) { sel.innerHTML = '<option value="">Error al cargar</option>'; }
|
||||
});
|
||||
})();
|
||||
|
||||
// Movement history
|
||||
@@ -1510,6 +1731,150 @@
|
||||
}).catch(function() { alert('Error al quitar compatibilidad'); });
|
||||
}
|
||||
|
||||
// Manual compatibility tab functions
|
||||
window.switchCompatTab = function(tab, btn) {
|
||||
document.querySelectorAll('.compat-tab-btn').forEach(function(b) { b.classList.remove('is-active'); });
|
||||
document.querySelectorAll('.compat-tab-panel').forEach(function(p) { p.classList.remove('is-active'); });
|
||||
btn.classList.add('is-active');
|
||||
document.getElementById('compatTab-' + tab).classList.add('is-active');
|
||||
};
|
||||
|
||||
window.onManualMakeChange = function(itemId) {
|
||||
var sel = document.getElementById('manualMake');
|
||||
var modelSel = document.getElementById('manualModel');
|
||||
var yearSel = document.getElementById('manualYear');
|
||||
var engineSel = document.getElementById('manualEngine');
|
||||
if (!sel || !modelSel) return;
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var brandId = opt ? opt.getAttribute('data-id') : null;
|
||||
modelSel.innerHTML = '<option value="">Cargando...</option>';
|
||||
modelSel.disabled = true;
|
||||
yearSel.innerHTML = '<option value="">Selecciona modelo</option>';
|
||||
yearSel.disabled = true;
|
||||
engineSel.innerHTML = '<option value="">Selecciona ano</option>';
|
||||
engineSel.disabled = true;
|
||||
if (!brandId) {
|
||||
modelSel.innerHTML = '<option value="">Selecciona marca</option>';
|
||||
return;
|
||||
}
|
||||
fetch('/pos/api/inventory/vehicles/models?brand_id=' + brandId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var opts = '<option value="">Selecciona modelo</option>';
|
||||
(d.models || []).forEach(function(m) { opts += '<option value="' + esc(m.name) + '" data-id="' + m.id + '">' + esc(m.name) + '</option>'; });
|
||||
modelSel.innerHTML = opts;
|
||||
modelSel.disabled = false;
|
||||
})
|
||||
.catch(function() { modelSel.innerHTML = '<option value="">Error</option>'; });
|
||||
};
|
||||
|
||||
window.onManualModelChange = function(itemId) {
|
||||
var modelSel = document.getElementById('manualModel');
|
||||
var yearSel = document.getElementById('manualYear');
|
||||
var engineSel = document.getElementById('manualEngine');
|
||||
if (!modelSel || !yearSel) return;
|
||||
var opt = modelSel.options[modelSel.selectedIndex];
|
||||
var modelId = opt ? opt.getAttribute('data-id') : null;
|
||||
yearSel.innerHTML = '<option value="">Cargando...</option>';
|
||||
yearSel.disabled = true;
|
||||
engineSel.innerHTML = '<option value="">Selecciona ano</option>';
|
||||
engineSel.disabled = true;
|
||||
if (!modelId) {
|
||||
yearSel.innerHTML = '<option value="">Selecciona modelo</option>';
|
||||
return;
|
||||
}
|
||||
fetch('/pos/api/inventory/vehicles/years?model_id=' + modelId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var opts = '<option value="">Selecciona ano</option>';
|
||||
(d.years || []).forEach(function(y) { opts += '<option value="' + y.year + '" data-id="' + y.id + '">' + y.year + '</option>'; });
|
||||
yearSel.innerHTML = opts;
|
||||
yearSel.disabled = false;
|
||||
})
|
||||
.catch(function() { yearSel.innerHTML = '<option value="">Error</option>'; });
|
||||
};
|
||||
|
||||
window.onManualYearChange = function(itemId) {
|
||||
var modelSel = document.getElementById('manualModel');
|
||||
var yearSel = document.getElementById('manualYear');
|
||||
var engineSel = document.getElementById('manualEngine');
|
||||
if (!modelSel || !yearSel || !engineSel) return;
|
||||
var mOpt = modelSel.options[modelSel.selectedIndex];
|
||||
var yOpt = yearSel.options[yearSel.selectedIndex];
|
||||
var modelId = mOpt ? mOpt.getAttribute('data-id') : null;
|
||||
var yearId = yOpt ? yOpt.getAttribute('data-id') : null;
|
||||
engineSel.innerHTML = '<option value="">Cargando...</option>';
|
||||
engineSel.disabled = true;
|
||||
if (!modelId || !yearId) {
|
||||
engineSel.innerHTML = '<option value="">Selecciona ano</option>';
|
||||
return;
|
||||
}
|
||||
fetch('/pos/api/inventory/vehicles/engines?model_id=' + modelId + '&year_id=' + yearId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var opts = '<option value="">Selecciona motor</option>';
|
||||
(d.engines || []).forEach(function(e) { opts += '<option value="' + esc(e.name) + '" data-code="' + esc(e.code || '') + '">' + esc(e.name + (e.code ? ' (' + e.code + ')' : '')) + '</option>'; });
|
||||
engineSel.innerHTML = opts;
|
||||
engineSel.disabled = false;
|
||||
})
|
||||
.catch(function() { engineSel.innerHTML = '<option value="">Error</option>'; });
|
||||
};
|
||||
|
||||
window.submitManualCompat = function(itemId) {
|
||||
var makeSel = document.getElementById('manualMake');
|
||||
var modelSel = document.getElementById('manualModel');
|
||||
var yearSel = document.getElementById('manualYear');
|
||||
var engineSel = document.getElementById('manualEngine');
|
||||
if (!makeSel || !modelSel || !yearSel) return;
|
||||
var make = makeSel.value;
|
||||
var model = modelSel.value;
|
||||
var year = yearSel.value;
|
||||
var engine = engineSel ? engineSel.value : '';
|
||||
var engineCode = engineSel && engineSel.selectedIndex > 0 ? (engineSel.options[engineSel.selectedIndex].getAttribute('data-code') || '') : '';
|
||||
if (!make || !model || !year) {
|
||||
alert('Selecciona al menos marca, modelo y ano');
|
||||
return;
|
||||
}
|
||||
fetch('/pos/api/inventory/items/' + itemId + '/vehicles/manual', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ make: make, model: model, year: parseInt(year), engine: engine, engine_code: engineCode })
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.error) { alert(d.error); return; }
|
||||
viewProductDetail(itemId);
|
||||
}).catch(function() { alert('Error al agregar compatibilidad'); });
|
||||
};
|
||||
|
||||
// SKU alias actions
|
||||
window.addSkuAlias = function(itemId) {
|
||||
var skuEl = document.getElementById('newAliasSku-' + itemId);
|
||||
var labelEl = document.getElementById('newAliasLabel-' + itemId);
|
||||
var sku = skuEl ? skuEl.value.trim() : '';
|
||||
var label = labelEl ? labelEl.value.trim() : '';
|
||||
if (!sku) { alert('Ingresa un SKU'); return; }
|
||||
fetch('/pos/api/inventory/items/' + itemId + '/skus', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sku: sku, label: label })
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.error) { alert(d.error); return; }
|
||||
viewProductDetail(itemId);
|
||||
}).catch(function() { alert('Error al agregar SKU'); });
|
||||
};
|
||||
|
||||
window.removeSkuAlias = function(itemId, aliasId) {
|
||||
if (!confirm('Eliminar este SKU alternativo?')) return;
|
||||
fetch('/pos/api/inventory/items/' + itemId + '/skus/' + aliasId, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function() {
|
||||
viewProductDetail(itemId);
|
||||
}).catch(function() { alert('Error al eliminar SKU'); });
|
||||
};
|
||||
|
||||
// =====================================================================
|
||||
// EXPOSE GLOBALS (for onclick handlers in HTML)
|
||||
// =====================================================================
|
||||
|
||||
Reference in New Issue
Block a user