feat(catalog): full vehicle selector flow in brand catalog

Brand catalog now follows the same navigation as the regular catalog:
1. Brands -> 2. Models -> 3. Years -> 4. Engines -> 5. Categories -> 6. Parts

Backend:
- Add /mye-parts endpoint for MYE-specific parts with category filter
- Uses existing /models, /years, /engines, /categories endpoints

Frontend:
- Complete rewrite of brand-catalog.js with breadcrumb navigation
- State machine: brands -> models -> years -> engines -> categories -> parts
- Search and pagination preserved at parts level
- Breadcrumb allows jumping back to any previous step
This commit is contained in:
2026-05-14 22:35:01 +00:00
parent 79fa7984a1
commit da362e32a6
3 changed files with 292 additions and 47 deletions

View File

@@ -763,3 +763,107 @@ def brand_parts():
finally:
cur.close()
return _with_conns(_query)
@catalog_bp.route('/mye-parts', methods=['GET'])
@require_auth('catalog.view')
def mye_parts():
"""Return parts for a specific MYE + category (brand-catalog flow).
Skips the group/subgroup level and goes directly from category to parts.
"""
mye_id = request.args.get('mye_id', type=int)
category_id = request.args.get('category_id', type=int)
search = request.args.get('search', '').strip()
limit = request.args.get('limit', 50, type=int)
offset = request.args.get('offset', 0, type=int)
if not mye_id:
return jsonify({'error': 'mye_id required'}), 400
def _query(master, tenant, branch_id):
cur = master.cursor()
try:
# Build dynamic filters
cat_filter = ""
search_filter = ""
params = [mye_id]
if category_id:
cat_filter = "AND pc.id_part_category = %s"
params.append(category_id)
if search:
search_filter = "AND (p.oem_part_number ILIKE %s OR COALESCE(NULLIF(p.name_es, ''), p.name_part) ILIKE %s)"
like_term = f"%{search}%"
params.extend([like_term, like_term])
# Get parts
query_params = list(params)
cur.execute(f"""
SELECT DISTINCT p.id_part, p.oem_part_number,
COALESCE(NULLIF(p.name_es, ''), p.name_part) as name,
pg.id_part_group, pg.name_part_group,
pc.id_part_category, pc.name_part_category
FROM vehicle_parts vp
JOIN parts p ON p.id_part = vp.part_id
JOIN part_groups pg ON pg.id_part_group = p.group_id
JOIN part_categories pc ON pc.id_part_category = pg.category_id
WHERE vp.model_year_engine_id = %s
{cat_filter}
{search_filter}
ORDER BY p.id_part
LIMIT %s OFFSET %s
""", query_params + [limit, offset])
part_rows = cur.fetchall()
part_ids = [r[0] for r in part_rows]
# Count total
cur.execute(f"""
SELECT COUNT(DISTINCT p.id_part)
FROM vehicle_parts vp
JOIN parts p ON p.id_part = vp.part_id
JOIN part_groups pg ON pg.id_part_group = p.group_id
JOIN part_categories pc ON pc.id_part_category = pg.category_id
WHERE vp.model_year_engine_id = %s
{cat_filter}
{search_filter}
""", params)
total = cur.fetchone()[0]
# Enrich with local stock if available
local_stock = {}
if tenant and part_ids:
try:
from services.catalog_service import _get_local_stock_bulk
local_stock = _get_local_stock_bulk(tenant, part_ids)
except Exception:
pass
items = []
for r in part_rows:
part_id = r[0]
stock_info = local_stock.get(part_id, {})
items.append({
'id': part_id,
'oem_part_number': r[1],
'name': r[2],
'group': {'id': r[3], 'name': r[4]},
'category': {'id': r[5], 'name': r[6]},
'local_stock': stock_info.get('stock', 0),
'local_price': stock_info.get('price', None),
})
return jsonify({
'mye_id': mye_id,
'category_id': category_id,
'search': search,
'items': items,
'total': total,
'limit': limit,
'offset': offset,
})
finally:
cur.close()
return _with_conns(_query)