# /home/Autopartes/pos/blueprints/diagrams_bp.py """Diagrams blueprint: exploded-view SVG diagrams with clickable hotspots. Endpoints (all under /pos/api/diagrams): GET / -- list all available diagrams (optionally filter by mye_id or group_id) GET / -- get diagram detail with hotspots GET //svg -- serve the SVG image file GET /for-vehicle?mye_id= -- diagrams linked to a specific vehicle (MYE) """ from flask import Blueprint, request, jsonify, send_from_directory, current_app from middleware import require_auth from tenant_db import get_master_conn import os diagrams_bp = Blueprint('diagrams', __name__, url_prefix='/pos/api/diagrams') def _master_only(fn): """Helper: open only master connection.""" 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 Exception: pass @diagrams_bp.route('/', methods=['GET']) @require_auth('catalog.view') def list_diagrams(): """List diagrams, optionally filtered by mye_id or group_id.""" mye_id = request.args.get('mye_id', type=int) group_id = request.args.get('group_id', type=int) category_id = request.args.get('category_id', type=int) def _do(master): cur = master.cursor() if mye_id: # Diagrams linked to a specific vehicle cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.name_part_category, pc.name_es AS category_name_es, vd.notes FROM diagrams d JOIN vehicle_diagrams vd ON vd.diagram_id = d.id_diagram JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id WHERE vd.model_year_engine_id = %s ORDER BY d.display_order, d.name_es, d.name_diagram """, (mye_id,)) elif group_id: cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.name_part_category, pc.name_es AS category_name_es, NULL AS notes FROM diagrams d JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id WHERE d.group_id = %s ORDER BY d.display_order, d.name_es """, (group_id,)) elif category_id: cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.name_part_category, pc.name_es AS category_name_es, NULL AS notes FROM diagrams d JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id WHERE pg.category_id = %s ORDER BY d.display_order, d.name_es """, (category_id,)) else: # All diagrams cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.name_part_category, pc.name_es AS category_name_es, NULL AS notes FROM diagrams d JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id ORDER BY pc.name_part_category, d.display_order, d.name_es LIMIT 200 """) rows = cur.fetchall() data = [] for r in rows: data.append({ 'id_diagram': r[0], 'name': r[1], 'name_es': r[2] or r[1], 'group_id': r[3], 'image_path': r[4], 'display_order': r[5], 'group_name': r[6], 'group_name_es': r[7] or r[6], 'category_name': r[8], 'category_name_es': r[9] or r[8], 'notes': r[10], }) cur.close() return jsonify({'data': data, 'count': len(data)}) return _master_only(_do) @diagrams_bp.route('/', methods=['GET']) @require_auth('catalog.view') def get_diagram(diagram_id): """Get a single diagram with its hotspots.""" def _do(master): cur = master.cursor() # Diagram info cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.id_part_category, pc.name_part_category, pc.name_es AS category_name_es FROM diagrams d JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id WHERE d.id_diagram = %s """, (diagram_id,)) row = cur.fetchone() if not row: cur.close() return jsonify({'error': 'Diagram not found'}), 404 diagram = { 'id_diagram': row[0], 'name': row[1], 'name_es': row[2] or row[1], 'group_id': row[3], 'image_path': row[4], 'display_order': row[5], 'group_name': row[6], 'group_name_es': row[7] or row[6], 'category_id': row[8], 'category_name': row[9], 'category_name_es': row[10] or row[9], } # Hotspots cur.execute(""" SELECT h.id_dgr_hotspot, h.callout_number, h.part_id, h.coords, s.name_shape, p.oem_part_number, p.name_part, p.name_es AS part_name_es, p.description, p.description_es FROM diagram_hotspots h LEFT JOIN shapes s ON s.id_shape = h.id_shape LEFT JOIN parts p ON p.id_part = h.part_id WHERE h.diagram_id = %s ORDER BY h.callout_number """, (diagram_id,)) hotspots = [] for h in cur.fetchall(): hotspots.append({ 'id': h[0], 'callout_number': h[1], 'part_id': h[2], 'coords': h[3], 'shape': h[4] or 'rect', 'part_number': h[5], 'part_name': h[6], 'part_name_es': h[7] or h[6], 'description': h[8], 'description_es': h[9] or h[8], }) diagram['hotspots'] = hotspots # Linked vehicles count cur.execute( "SELECT count(*) FROM vehicle_diagrams WHERE diagram_id = %s", (diagram_id,) ) diagram['vehicle_count'] = cur.fetchone()[0] cur.close() return jsonify(diagram) return _master_only(_do) @diagrams_bp.route('//svg', methods=['GET']) @require_auth('catalog.view') def get_diagram_svg(diagram_id): """Serve the SVG image file for a diagram.""" def _do(master): cur = master.cursor() cur.execute( "SELECT image_path FROM diagrams WHERE id_diagram = %s", (diagram_id,) ) row = cur.fetchone() cur.close() if not row: return jsonify({'error': 'Diagram not found'}), 404 image_path = row[0] static_dir = os.path.join(current_app.root_path, 'static') full_path = os.path.join(static_dir, image_path) if not os.path.isfile(full_path): return jsonify({'error': 'SVG file not found'}), 404 directory = os.path.dirname(full_path) filename = os.path.basename(full_path) return send_from_directory(directory, filename, mimetype='image/svg+xml') return _master_only(_do) @diagrams_bp.route('/for-vehicle', methods=['GET']) @require_auth('catalog.view') def diagrams_for_vehicle(): """Get diagrams linked to a specific vehicle MYE, grouped by category.""" mye_id = request.args.get('mye_id', type=int) if not mye_id: return jsonify({'error': 'mye_id required'}), 400 def _do(master): cur = master.cursor() cur.execute(""" SELECT d.id_diagram, d.name_diagram, d.name_es, d.group_id, d.image_path, d.display_order, pg.name_part_group, pg.name_es AS group_name_es, pc.id_part_category, pc.name_part_category, pc.name_es AS category_name_es, (SELECT count(*) FROM diagram_hotspots dh WHERE dh.diagram_id = d.id_diagram) AS hotspot_count FROM diagrams d JOIN vehicle_diagrams vd ON vd.diagram_id = d.id_diagram JOIN part_groups pg ON pg.id_part_group = d.group_id JOIN part_categories pc ON pc.id_part_category = pg.category_id WHERE vd.model_year_engine_id = %s ORDER BY pc.name_part_category, d.display_order, d.name_es """, (mye_id,)) rows = cur.fetchall() # Group by category categories = {} for r in rows: cat_id = r[8] if cat_id not in categories: categories[cat_id] = { 'category_id': cat_id, 'category_name': r[9], 'category_name_es': r[10] or r[9], 'diagrams': [], } categories[cat_id]['diagrams'].append({ 'id_diagram': r[0], 'name': r[1], 'name_es': r[2] or r[1], 'group_id': r[3], 'image_path': r[4], 'display_order': r[5], 'group_name': r[6], 'group_name_es': r[7] or r[6], 'hotspot_count': r[11], }) cur.close() return jsonify({ 'data': list(categories.values()), 'total_diagrams': len(rows), }) return _master_only(_do)