diff --git a/pos/blueprints/catalog_bp.py b/pos/blueprints/catalog_bp.py index c754b78..08ac934 100644 --- a/pos/blueprints/catalog_bp.py +++ b/pos/blueprints/catalog_bp.py @@ -150,12 +150,18 @@ def models(): @catalog_bp.route('/years', methods=['GET']) @require_auth('catalog.view') def years(): - model_id = request.args.get('model_id', type=int) - if not model_id: + model_id_param = request.args.get('model_id', '') + if not model_id_param: + return jsonify({'error': 'model_id required'}), 400 + try: + model_ids = [int(x) for x in model_id_param.split(',') if x] + except ValueError: + return jsonify({'error': 'model_id must be a comma-separated list of integers'}), 400 + if not model_ids: return jsonify({'error': 'model_id required'}), 400 def _do(master, tenant, branch_id): mye_ids = catalog_service._get_mye_ids_with_parts(tenant, tenant_id=g.tenant_id, master_conn=master) if tenant else None - data = catalog_service.get_years(master, model_id, mye_ids=mye_ids) + data = catalog_service.get_years(master, model_ids, mye_ids=mye_ids) return jsonify({'data': data}) return _with_conns(_do) @@ -176,13 +182,19 @@ def years_all(): @catalog_bp.route('/engines', methods=['GET']) @require_auth('catalog.view') def engines(): - model_id = request.args.get('model_id', type=int) + model_id_param = request.args.get('model_id', '') year_id = request.args.get('year_id', type=int) - if not model_id or not year_id: + if not model_id_param or not year_id: return jsonify({'error': 'model_id and year_id required'}), 400 + try: + model_ids = [int(x) for x in model_id_param.split(',') if x] + except ValueError: + return jsonify({'error': 'model_id must be a comma-separated list of integers'}), 400 + if not model_ids: + return jsonify({'error': 'model_id required'}), 400 def _do(master, tenant, branch_id): mye_ids = catalog_service._get_mye_ids_with_parts(tenant, tenant_id=g.tenant_id, master_conn=master) if tenant else None - data = catalog_service.get_engines(master, model_id, year_id, mye_ids=mye_ids) + data = catalog_service.get_engines(master, model_ids, year_id, mye_ids=mye_ids) return jsonify({'data': data}) return _with_conns(_do) diff --git a/pos/services/catalog_service.py b/pos/services/catalog_service.py index 7332a63..b74037a 100644 --- a/pos/services/catalog_service.py +++ b/pos/services/catalog_service.py @@ -285,20 +285,24 @@ def get_models(master_conn, brand_id, year_id=None, brand_name=None, mye_ids=Non # Filter to North America models only, add clean display name, deduplicate filtered = [r for r in rows if is_na_model(brand_name, r[1])] - # Group by (display_name, raw name) so distinct body-style variants - # (e.g. AVEO vs AVEO SALOON) remain selectable. - seen = set() - results = [] + # Group by display_name so body-style/generation variants + # (e.g. AVEO Saloon, AVEO Hatchback) are shown as a single model. + groups = {} for r in filtered: display = _clean_model_name(r[1]) - key = (display, r[1]) - if key not in seen: - seen.add(key) - results.append({ - 'id_model': r[0], - 'name_model': r[1], - 'display_name': display, - }) + groups.setdefault(display, []).append(r) + + results = [] + for display, variants in groups.items(): + # Sort by raw model id ascending; first becomes the canonical id. + variants.sort(key=lambda x: x[0]) + canonical = variants[0] + results.append({ + 'id_model': canonical[0], + 'name_model': canonical[1], + 'display_name': display, + 'variant_ids': [v[0] for v in variants], + }) # Sort by display_name results.sort(key=lambda x: x['display_name']) @@ -306,34 +310,37 @@ def get_models(master_conn, brand_id, year_id=None, brand_name=None, mye_ids=Non def get_years(master_conn, model_id, mye_ids=None): - """Get distinct years for a model via MYE (fast, no vehicle_parts scan). Ordered DESC.""" + """Get distinct years for a model (or list of model variants) via MYE. + Ordered DESC.""" cur = master_conn.cursor() + model_ids = model_id if isinstance(model_id, (list, tuple, set)) else [model_id] if mye_ids: cur.execute(""" SELECT DISTINCT y.id_year, y.year_car FROM years y JOIN model_year_engine mye ON mye.year_id = y.id_year - WHERE mye.model_id = %s AND mye.id_mye = ANY(%s) + WHERE mye.model_id = ANY(%s) AND mye.id_mye = ANY(%s) ORDER BY y.year_car DESC - """, (model_id, mye_ids)) + """, (list(model_ids), mye_ids)) else: cur.execute(""" SELECT DISTINCT y.id_year, y.year_car FROM years y JOIN model_year_engine mye ON mye.year_id = y.id_year - WHERE mye.model_id = %s + WHERE mye.model_id = ANY(%s) ORDER BY y.year_car DESC - """, (model_id,)) + """, (list(model_ids),)) rows = cur.fetchall() cur.close() return [{'id_year': r[0], 'year_car': r[1]} for r in rows] def get_engines(master_conn, model_id, year_id, mye_ids=None): - """Get MYE entries (engine + trim) for a model+year combo.""" + """Get MYE entries (engine + trim) for a model (or list of variants) + year combo.""" cur = master_conn.cursor() + model_ids = model_id if isinstance(model_id, (list, tuple, set)) else [model_id] mye_filter = "" - params = [model_id, year_id] + params = [list(model_ids), year_id] if mye_ids: mye_filter = " AND mye.id_mye = ANY(%s)" params.append(mye_ids) @@ -341,7 +348,7 @@ def get_engines(master_conn, model_id, year_id, mye_ids=None): SELECT mye.id_mye, e.name_engine, mye.trim_level FROM model_year_engine mye JOIN engines e ON e.id_engine = mye.engine_id - WHERE mye.model_id = %s AND mye.year_id = %s{mye_filter} + WHERE mye.model_id = ANY(%s) AND mye.year_id = %s{mye_filter} ORDER BY e.name_engine, mye.trim_level """, tuple(params)) rows = cur.fetchall() diff --git a/pos/static/js/catalog.js b/pos/static/js/catalog.js index fd7e798..e6304e4 100644 --- a/pos/static/js/catalog.js +++ b/pos/static/js/catalog.js @@ -427,6 +427,12 @@ }); } + function modelIdsParam(model) { + if (!model) return ''; + if (model.variant_ids && model.variant_ids.length) return model.variant_ids.join(','); + return String(model.id); + } + function loadModels() { nav.level = 'models'; pushNavState(); @@ -440,14 +446,15 @@ if (!data || !data.data || !data.data.length) { showEmpty('Sin modelos', 'No hay modelos con partes para ' + nav.brand.name); return; } navGrid.className = 'nav-grid'; navGrid.innerHTML = data.data.map(function (m) { - return '