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:
@@ -629,18 +629,42 @@ def get_parts_for_nexpart_triple(master_conn, mye_id, group_slug, subgroup_slug,
|
||||
.get(subgroup_slug, {})
|
||||
.get(part_type_slug, [])
|
||||
)
|
||||
if not part_ids:
|
||||
return {
|
||||
'data': [],
|
||||
'pagination': _pagination(page, per_page, 0),
|
||||
'mode': 'local',
|
||||
}
|
||||
return get_parts_local(
|
||||
result = get_parts_local(
|
||||
master_conn, mye_id=None, group_id=None,
|
||||
tenant_conn=tenant_conn, branch_id=branch_id,
|
||||
page=page, per_page=per_page,
|
||||
oem_part_ids=part_ids,
|
||||
)
|
||||
# Inject local inventory items linked to this vehicle
|
||||
# (get_parts_local with oem_part_ids skips mye_id, so we call it separately)
|
||||
if tenant_conn and mye_id:
|
||||
from services.inventory_vehicle_compat import get_inventory_by_vehicle
|
||||
local_rows = get_inventory_by_vehicle(tenant_conn, master_conn, mye_id, branch_id)
|
||||
for lr in local_rows:
|
||||
inv_id, pn, name, brand, p1, p2, p3, img, desc, stock = lr
|
||||
# Only include if name roughly matches the Nexpart part_type
|
||||
if part_type_slug and part_type_slug.lower() not in (name or '').lower():
|
||||
continue
|
||||
result['data'].append({
|
||||
'id_part': f'inv:{inv_id}',
|
||||
'id_aftermarket': None,
|
||||
'oem_part_number': None,
|
||||
'part_number': pn,
|
||||
'name': name,
|
||||
'description': desc,
|
||||
'image_url': img,
|
||||
'manufacturer': brand,
|
||||
'priority_tier': 1,
|
||||
'local_stock': int(stock) if stock else 0,
|
||||
'local_price': float(p1) if p1 else None,
|
||||
'bodega_count': 0,
|
||||
'warehouse_stock': 0,
|
||||
'warehouse_price': None,
|
||||
'in_stock_network': False,
|
||||
'price_usd': None,
|
||||
'source': 'local_inventory',
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def get_nexpart_part_types_for_vehicle(master_conn, mye_id, group_slug, subgroup_slug):
|
||||
@@ -983,6 +1007,8 @@ def get_parts_local(master_conn, mye_id, group_id, tenant_conn, branch_id,
|
||||
local_map = _get_local_stock_bulk(tenant_conn, branch_id, oem_numbers, result_oem_ids)
|
||||
|
||||
items = []
|
||||
seen_part_numbers = set()
|
||||
|
||||
for r in rows:
|
||||
aft_id = r[0]
|
||||
oem_part_id = r[1]
|
||||
@@ -1003,6 +1029,10 @@ def get_parts_local(master_conn, mye_id, group_id, tenant_conn, branch_id,
|
||||
local = local_map.get(oem_number) or local_map.get(f'cat:{oem_part_id}')
|
||||
image_url = (local.get('image_url') if local else None) or oem_image
|
||||
|
||||
part_number = aft_number or oem_number
|
||||
if part_number:
|
||||
seen_part_numbers.add(part_number.upper())
|
||||
|
||||
items.append({
|
||||
# Keep fields compatible with OEM mode output so the frontend
|
||||
# can render with minimal branching.
|
||||
@@ -1022,8 +1052,39 @@ def get_parts_local(master_conn, mye_id, group_id, tenant_conn, branch_id,
|
||||
'warehouse_price': float(warehouse_price) if warehouse_price is not None else None,
|
||||
'in_stock_network': bodega_count > 0,
|
||||
'price_usd': float(price_usd) if price_usd is not None else None,
|
||||
'source': 'aftermarket',
|
||||
})
|
||||
|
||||
# ─── Inject local inventory items linked to this vehicle ──────────────────
|
||||
if mye_id and tenant_conn:
|
||||
from services.inventory_vehicle_compat import get_inventory_by_vehicle
|
||||
local_rows = get_inventory_by_vehicle(tenant_conn, master_conn, mye_id, branch_id)
|
||||
for lr in local_rows:
|
||||
inv_id, pn, name, brand, p1, p2, p3, img, desc, stock = lr
|
||||
if pn and pn.upper() in seen_part_numbers:
|
||||
continue # deduplicate: already shown via aftermarket match
|
||||
seen_part_numbers.add(pn.upper() if pn else '')
|
||||
items.append({
|
||||
'id_part': f'inv:{inv_id}',
|
||||
'id_aftermarket': None,
|
||||
'oem_part_number': None,
|
||||
'part_number': pn,
|
||||
'name': name,
|
||||
'description': desc,
|
||||
'image_url': img,
|
||||
'manufacturer': brand,
|
||||
'priority_tier': 1, # treat as tier 1 since it's local stock
|
||||
'local_stock': int(stock) if stock else 0,
|
||||
'local_price': float(p1) if p1 else None,
|
||||
'bodega_count': 0,
|
||||
'warehouse_stock': 0,
|
||||
'warehouse_price': None,
|
||||
'in_stock_network': False,
|
||||
'price_usd': None,
|
||||
'source': 'local_inventory',
|
||||
})
|
||||
total += 1
|
||||
|
||||
return {'data': items, 'pagination': _pagination(page, per_page, total), 'mode': 'local'}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user