Opción C: Vinculación híbrida de inventario local con vehículos

- Nueva tabla inventory_vehicle_compat (v3.1)
- Motor inventory_vehicle_compat.py: auto-match + gestión manual
- catalog_service.get_parts_local() ahora incluye piezas locales vinculadas
- inventory_bp: auto-match en create/update + endpoints REST /vehicles
- Frontend catalog.js: badge 'Stock Local' para piezas nativas del tenant
- Frontend inventory.js: panel de vehículos compatibles con auto-match
- Tests: test_compatibility.py (9/9 pasan)

Piezas locales aparecen en navegación por vehículo aunque no estén en TecDoc.
Auto-match busca part_number en parts/aftermarket_parts y copia MYEs compatibles.
This commit is contained in:
2026-04-27 06:52:30 +00:00
parent 142abbc217
commit efbd763e43
8 changed files with 690 additions and 14 deletions

View File

@@ -875,7 +875,11 @@
partsGrid.querySelectorAll('.part-card').forEach(function (card) {
card.addEventListener('click', function () {
openPartDetail(parseInt(this.dataset.partId));
var pid = this.dataset.partId;
if (typeof pid === 'string' && pid.indexOf('inv:') === 0) {
return;
}
openPartDetail(parseInt(pid));
});
});
@@ -940,6 +944,10 @@
} else {
stockBadge = '<span class="stock-badge stock-badge--none">Sin stock</span>';
}
// Local inventory native badge
var sourceBadge = p.source === 'local_inventory'
? '<span class="stock-badge stock-badge--local" style="margin-left:4px;background:#4f46e5;">Stock Local</span>'
: '';
var imgHtml = p.image_url
? '<img src="' + esc(p.image_url) + '" alt="' + esc(p.name) + '">'
@@ -961,10 +969,11 @@
? esc(p.part_number) + '<span class="part-card__oem-sub"> · OEM: ' + esc(p.oem_part_number) + '</span>'
: esc(p.oem_part_number);
return '<article class="part-card' + tierClass + '" role="listitem" data-part-id="' + p.id_part + '">' +
return '<article class="part-card' + tierClass + '" role="listitem" data-part-id="' + p.id_part + '" data-source="' + (p.source || '') + '">' +
'<div class="part-card__image">' + imgHtml + '</div>' +
'<div class="part-card__body">' +
manuBadge +
sourceBadge +
'<div class="part-card__oem">' + skuLine + '</div>' +
'<div class="part-card__name">' + esc(p.name) + '</div>' +
'</div>' +
@@ -975,10 +984,15 @@
'</article>';
}).join('');
// Wire part card clicks → open detail panel
// Wire part card clicks → open detail panel (skip local-inventory items)
partsGrid.querySelectorAll('.part-card').forEach(function (card) {
card.addEventListener('click', function () {
openPartDetail(parseInt(this.dataset.partId));
var pid = this.dataset.partId;
if (typeof pid === 'string' && pid.indexOf('inv:') === 0) {
// local-inventory item: info already visible on card
return;
}
openPartDetail(parseInt(pid));
});
});
@@ -1345,7 +1359,11 @@
searchDropdown.querySelectorAll('.search-result-item').forEach(function (el) {
el.addEventListener('click', function () {
searchDropdown.classList.remove('is-visible');
openPartDetail(parseInt(this.dataset.partId));
var pid = this.dataset.partId;
if (typeof pid === 'string' && pid.indexOf('inv:') === 0) {
return;
}
openPartDetail(parseInt(pid));
});
});
});