Barra de 4 dropdowns arriba del catalogo que se habilitan en cascada. Al completar los 4, muestra categorias y partes para ese vehiculo. Boton de limpiar para resetear. Endpoint /years-all para cargar anos. Estilos con design system tokens (ambos temas). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
185 lines
6.2 KiB
Python
185 lines
6.2 KiB
Python
# /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/<part_id> — 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/<int:part_id>', 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)
|