Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
This commit is contained in:
@@ -19,11 +19,32 @@ from tenant_db import get_master_conn, get_tenant_conn
|
||||
from services import catalog_service
|
||||
from services.vin_decoder import decode_vin
|
||||
from services.plate_lookup import search_plate, register_plate, is_valid_mexican_plate, normalize_plate
|
||||
from services.inventory_vehicle_compat import get_inventory_by_vehicle
|
||||
from config import CATALOG_OEM_ENABLED
|
||||
|
||||
catalog_bp = Blueprint('catalog', __name__, url_prefix='/pos/api/catalog')
|
||||
|
||||
|
||||
# ─── Guard: tablas TecDoc retiradas (junio 2026) ──────────────────────────
|
||||
_TECDOC_TABLES_CACHE = {'checked': False, 'exists': False}
|
||||
|
||||
|
||||
def _tecdoc_available(conn):
|
||||
"""True si las tablas TecDoc del catálogo OEM existen en la maestra."""
|
||||
if _TECDOC_TABLES_CACHE['checked']:
|
||||
return _TECDOC_TABLES_CACHE['exists']
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT 1 FROM pg_tables WHERE schemaname='public' AND tablename='parts' LIMIT 1")
|
||||
exists = cur.fetchone() is not None
|
||||
cur.close()
|
||||
except Exception:
|
||||
exists = False
|
||||
_TECDOC_TABLES_CACHE['checked'] = True
|
||||
_TECDOC_TABLES_CACHE['exists'] = exists
|
||||
return exists
|
||||
|
||||
|
||||
def _oem_blocked():
|
||||
"""Return a 403 response if OEM catalog is disabled."""
|
||||
if not CATALOG_OEM_ENABLED:
|
||||
@@ -66,6 +87,9 @@ def _with_conns(fn):
|
||||
branch_id = request.args.get('branch_id', g.branch_id)
|
||||
return fn(master, tenant, branch_id)
|
||||
except Exception as e:
|
||||
if _is_retired_schema_error(e):
|
||||
return jsonify({'data': [], 'oem_disabled': True,
|
||||
'error': 'Función no disponible (esquema TecDoc retirado)'}, 200), 200
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
if master:
|
||||
@@ -76,6 +100,15 @@ def _with_conns(fn):
|
||||
except: pass
|
||||
|
||||
|
||||
def _is_retired_schema_error(e):
|
||||
"""True si el error es por tablas TecDoc retiradas del esquema."""
|
||||
try:
|
||||
from psycopg2.errors import UndefinedTable, UndefinedColumn
|
||||
return isinstance(e, (UndefinedTable, UndefinedColumn))
|
||||
except Exception:
|
||||
return 'does not exist' in str(e)
|
||||
|
||||
|
||||
def _master_only(fn):
|
||||
"""Helper: open only master connection for hierarchy endpoints."""
|
||||
master = None
|
||||
@@ -83,6 +116,9 @@ def _master_only(fn):
|
||||
master = get_master_conn()
|
||||
return fn(master)
|
||||
except Exception as e:
|
||||
if _is_retired_schema_error(e):
|
||||
return jsonify({'data': [], 'oem_disabled': True,
|
||||
'error': 'Función no disponible (esquema TecDoc retirado)'}, 200), 200
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
if master:
|
||||
@@ -487,6 +523,33 @@ def decode_vin_route(vin):
|
||||
|
||||
# ─── Plate Lookup ───
|
||||
|
||||
|
||||
|
||||
@catalog_bp.route('/vehicle-inventory', methods=['GET'])
|
||||
@require_auth('catalog.view')
|
||||
def vehicle_inventory():
|
||||
"""Local inventory items compatible with a vehicle (model_year_engine_id)."""
|
||||
mye_id = request.args.get('mye_id', type=int)
|
||||
if not mye_id:
|
||||
return jsonify({'error': 'mye_id requerido'}), 400
|
||||
tenant = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
rows = get_inventory_by_vehicle(tenant, None, mye_id,
|
||||
branch_id=getattr(g, 'branch_id', None))
|
||||
items = [{
|
||||
'id': r[0], 'part_number': r[1], 'name': r[2], 'brand': r[3] or '',
|
||||
'price_1': float(r[4] or 0), 'price_2': float(r[5] or 0),
|
||||
'price_3': float(r[6] or 0), 'image_url': r[7] or '',
|
||||
'stock': int(r[9] or 0),
|
||||
} for r in rows]
|
||||
return jsonify({'data': items})
|
||||
finally:
|
||||
try:
|
||||
tenant.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@catalog_bp.route('/plate/<plate>', methods=['GET'])
|
||||
@require_auth('catalog.view')
|
||||
def plate_lookup(plate):
|
||||
@@ -541,6 +604,9 @@ def plate_lookup(plate):
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
if _is_retired_schema_error(e):
|
||||
return jsonify({'data': [], 'oem_disabled': True,
|
||||
'error': 'Función no disponible (esquema TecDoc retirado)'}, 200), 200
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
if tenant:
|
||||
@@ -576,6 +642,9 @@ def plate_register():
|
||||
)
|
||||
return jsonify({'id': rec_id, 'message': 'Placa registrada.'})
|
||||
except Exception as e:
|
||||
if _is_retired_schema_error(e):
|
||||
return jsonify({'data': [], 'oem_disabled': True,
|
||||
'error': 'Función no disponible (esquema TecDoc retirado)'}, 200), 200
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
if tenant:
|
||||
|
||||
Reference in New Issue
Block a user