# /home/Autopartes/pos/blueprints/catalog_bp.py """Catalog blueprint: TecDoc vehicle navigation with local stock enrichment. Endpoints (all under /pos/api/catalog): GET /brands — vehicle brands with parts GET /models?brand_id= — models for a brand GET /years?model_id= — years for a model GET /engines?model_id=&year_id= — engines for model+year GET /categories?mye_id= — part categories for vehicle GET /groups?mye_id=&category_id= — part subcategories for vehicle+category GET /parts?mye_id=&group_id= — parts with local stock enrichment GET /part/ — full part detail (stock + bodegas + alternatives) GET /search?q= — smart search (part number or text) """ from flask import Blueprint, request, jsonify, g from middleware import require_auth from tenant_db import get_master_conn, get_tenant_conn from services import catalog_service catalog_bp = Blueprint('catalog', __name__, url_prefix='/pos/api/catalog') def _with_conns(fn): """Helper: open master + tenant connections, call fn, close both. fn receives (master_conn, tenant_conn, branch_id). """ master = None tenant = None try: master = get_master_conn() tenant = get_tenant_conn(g.tenant_id) branch_id = request.args.get('branch_id', g.branch_id) return fn(master, tenant, branch_id) except Exception as e: return jsonify({'error': str(e)}), 500 finally: if master: try: master.close() except: pass if tenant: try: tenant.close() except: pass def _master_only(fn): """Helper: open only master connection for hierarchy endpoints.""" master = None try: master = get_master_conn() return fn(master) except Exception as e: return jsonify({'error': str(e)}), 500 finally: if master: try: master.close() except: pass # ─── Hierarchy navigation (master DB only) ─── @catalog_bp.route('/brands', methods=['GET']) @require_auth('catalog.view') def brands(): def _do(master): data = catalog_service.get_brands(master) return jsonify({'data': data}) return _master_only(_do) @catalog_bp.route('/models', methods=['GET']) @require_auth('catalog.view') def models(): brand_id = request.args.get('brand_id', type=int) if not brand_id: return jsonify({'error': 'brand_id required'}), 400 def _do(master): data = catalog_service.get_models(master, brand_id) return jsonify({'data': data}) return _master_only(_do) @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: return jsonify({'error': 'model_id required'}), 400 def _do(master): data = catalog_service.get_years(master, model_id) return jsonify({'data': data}) return _master_only(_do) @catalog_bp.route('/years-all', methods=['GET']) @require_auth('catalog.view') def years_all(): """Get all available years (for vehicle selector dropdown).""" def _do(master): cur = master.cursor() cur.execute("SELECT DISTINCT id_year, year_car FROM years ORDER BY year_car DESC") rows = cur.fetchall() cur.close() return jsonify({'data': [{'id_year': r[0], 'year_car': r[1]} for r in rows]}) return _master_only(_do) @catalog_bp.route('/engines', methods=['GET']) @require_auth('catalog.view') def engines(): model_id = request.args.get('model_id', type=int) year_id = request.args.get('year_id', type=int) if not model_id or not year_id: return jsonify({'error': 'model_id and year_id required'}), 400 def _do(master): data = catalog_service.get_engines(master, model_id, year_id) return jsonify({'data': data}) return _master_only(_do) @catalog_bp.route('/categories', methods=['GET']) @require_auth('catalog.view') def categories(): mye_id = request.args.get('mye_id', type=int) if not mye_id: return jsonify({'error': 'mye_id required'}), 400 def _do(master): data = catalog_service.get_categories(master, mye_id) return jsonify({'data': data}) return _master_only(_do) @catalog_bp.route('/groups', methods=['GET']) @require_auth('catalog.view') def groups(): mye_id = request.args.get('mye_id', type=int) category_id = request.args.get('category_id', type=int) if not mye_id or not category_id: return jsonify({'error': 'mye_id and category_id required'}), 400 def _do(master): data = catalog_service.get_groups(master, mye_id, category_id) return jsonify({'data': data}) return _master_only(_do) # ─── Parts with stock enrichment (master + tenant) ─── @catalog_bp.route('/parts', methods=['GET']) @require_auth('catalog.view') def parts(): mye_id = request.args.get('mye_id', type=int) group_id = request.args.get('group_id', type=int) page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 30, type=int) if not mye_id or not group_id: return jsonify({'error': 'mye_id and group_id required'}), 400 def _do(master, tenant, branch_id): result = catalog_service.get_parts(master, mye_id, group_id, tenant, branch_id, page, per_page) return jsonify(result) return _with_conns(_do) @catalog_bp.route('/part/', methods=['GET']) @require_auth('catalog.view') def part_detail(part_id): def _do(master, tenant, branch_id): result = catalog_service.get_part_detail(master, part_id, tenant, branch_id) if not result: return jsonify({'error': 'Part not found'}), 404 return jsonify(result) return _with_conns(_do) @catalog_bp.route('/search', methods=['GET']) @require_auth('catalog.view') def search(): q = request.args.get('q', '').strip() if not q or len(q) < 2: return jsonify({'data': []}) limit = request.args.get('limit', 50, type=int) def _do(master, tenant, branch_id): data = catalog_service.smart_search(master, q, tenant, branch_id, limit) return jsonify({'data': data}) return _with_conns(_do)