feat(catalog): wire up brand-first OEM catalog UI

- Add brand-catalog.js overlay: Brands -> Categories -> Parts flow
- Update catalog.html: 'Por Marca' button opens BrandCatalog overlay
- Optimize /vehicle-brands to query brands table (fast) instead of 256M part_vehicle_preview
- Keep /brand-categories and /brand-parts using exact match on part_vehicle_preview
- Integrate addToCart with existing CatalogApp cart
This commit is contained in:
2026-05-14 08:37:37 +00:00
parent ff45905b49
commit ee9eea58c1
3 changed files with 359 additions and 1 deletions

View File

@@ -591,3 +591,158 @@ def _match_vin_to_catalog(master_conn, vin_info):
return None
finally:
cur.close()
# ─── Brand Catalog (vehicle-brand-first navigation) ───
@catalog_bp.route('/vehicle-brands', methods=['GET'])
@require_auth('catalog.view')
def vehicle_brands():
"""Return vehicle brands for brand-first catalog browsing.
Returns all brands from the brands table (fast) rather than scanning
the 256M-row part_vehicle_preview materialized view.
"""
def _query(master):
cur = master.cursor()
try:
cur.execute("""
SELECT id_brand, name_brand
FROM brands
ORDER BY name_brand ASC
""")
rows = cur.fetchall()
return jsonify({
'brands': [
{'id': r[0], 'name': r[1], 'part_count': 0}
for r in rows
]
})
finally:
cur.close()
return _master_only(_query)
@catalog_bp.route('/brand-categories', methods=['GET'])
@require_auth('catalog.view')
def brand_categories():
"""Return part categories available for a given vehicle brand."""
brand = request.args.get('brand', '')
if not brand:
return jsonify({'error': 'brand parameter required'}), 400
def _query(master):
cur = master.cursor()
try:
cur.execute("""
SELECT pc.id_part_category,
COALESCE(NULLIF(pc.name_es, ''), pc.name_part_category) as name,
pc.slug,
COUNT(DISTINCT p.id_part) as part_count
FROM part_vehicle_preview pvp
JOIN parts p ON p.id_part = pvp.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 pvp.name_brand = %s
GROUP BY pc.id_part_category, pc.name_part_category, pc.name_es, pc.slug
ORDER BY part_count DESC
""", (brand,))
rows = cur.fetchall()
return jsonify({
'brand': brand,
'categories': [
{'id': r[0], 'name': r[1], 'slug': r[2], 'part_count': r[3]}
for r in rows
]
})
finally:
cur.close()
return _master_only(_query)
@catalog_bp.route('/brand-parts', methods=['GET'])
@require_auth('catalog.view')
def brand_parts():
"""Return parts for a given vehicle brand + category."""
brand = request.args.get('brand', '')
category_id = request.args.get('category_id', type=int)
limit = request.args.get('limit', 50, type=int)
offset = request.args.get('offset', 0, type=int)
if not brand:
return jsonify({'error': 'brand parameter required'}), 400
def _query(master, tenant, branch_id):
cur = master.cursor()
try:
# Get parts from the brand catalog
params = [brand]
cat_filter = ""
if category_id:
cat_filter = "AND pc.id_part_category = %s"
params.append(category_id)
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 part_vehicle_preview pvp
JOIN parts p ON p.id_part = pvp.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 pvp.name_brand = %s
{cat_filter}
ORDER BY p.id_part
LIMIT %s OFFSET %s
""", 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 part_vehicle_preview pvp
JOIN parts p ON p.id_part = pvp.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 pvp.name_brand = %s
{cat_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({
'brand': brand,
'category_id': category_id,
'items': items,
'total': total,
'limit': limit,
'offset': offset,
})
finally:
cur.close()
return _with_conns(_query)