diff --git a/pos/blueprints/catalog_bp.py b/pos/blueprints/catalog_bp.py index 071fea7..34ecbf1 100644 --- a/pos/blueprints/catalog_bp.py +++ b/pos/blueprints/catalog_bp.py @@ -19,10 +19,22 @@ 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 config import CATALOG_OEM_ENABLED catalog_bp = Blueprint('catalog', __name__, url_prefix='/pos/api/catalog') +def _oem_blocked(): + """Return a 403 response if OEM catalog is disabled.""" + if not CATALOG_OEM_ENABLED: + return jsonify({ + 'error': 'Catálogo OEM no disponible', + 'message': 'El catálogo OEM está en construcción. Por favor usa el modo Local o Shop Supplies.', + 'oem_disabled': True, + }), 403 + return None + + def _with_conns(fn): """Helper: open master + tenant connections, call fn, close both. fn receives (master_conn, tenant_conn, branch_id). @@ -298,6 +310,12 @@ def parts(): if not use_nexpart_nav and not group_id: return jsonify({'error': 'group_id (or nexpart_group + subgroup + part_type) required'}), 400 + # Block OEM catalog if not enabled + if mode != 'local' and not use_nexpart_nav: + blocked = _oem_blocked() + if blocked: + return blocked + def _do(master, tenant, branch_id): if use_nexpart_nav: result = catalog_service.get_parts_for_nexpart_triple( @@ -319,6 +337,9 @@ def parts(): @catalog_bp.route('/part/', methods=['GET']) @require_auth('catalog.view') def part_detail(part_id): + blocked = _oem_blocked() + if blocked: + return blocked def _do(master, tenant, branch_id): result = catalog_service.get_part_detail(master, part_id, tenant, branch_id) if not result: @@ -330,6 +351,9 @@ def part_detail(part_id): @catalog_bp.route('/search', methods=['GET']) @require_auth('catalog.view') def search(): + blocked = _oem_blocked() + if blocked: + return blocked q = request.args.get('q', '').strip() if not q or len(q) < 2: return jsonify({'data': []}) diff --git a/pos/config.py b/pos/config.py index d28f34e..e03a15e 100644 --- a/pos/config.py +++ b/pos/config.py @@ -67,3 +67,11 @@ EXCHANGE_RATE_USD_MXN = float(os.environ.get('EXCHANGE_RATE_USD_MXN', '17.5')) REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0') REDIS_ENABLED = os.environ.get('REDIS_ENABLED', 'true').lower() == 'true' REDIS_STOCK_TTL = int(os.environ.get('REDIS_STOCK_TTL', '300')) + +# ─── Meilisearch ─────────────────────────────────────────────────────────── +MEILI_URL = os.environ.get('MEILI_URL', 'http://localhost:7700') +MEILI_API_KEY = os.environ.get('MEILI_API_KEY', '') +MEILI_ENABLED = os.environ.get('MEILI_ENABLED', 'true').lower() == 'true' + +# ─── Catalog OEM Access ──────────────────────────────────────────────────── +CATALOG_OEM_ENABLED = os.environ.get('CATALOG_OEM_ENABLED', 'false').lower() == 'true' diff --git a/pos/static/js/catalog.js b/pos/static/js/catalog.js index 37fc833..294c08e 100644 --- a/pos/static/js/catalog.js +++ b/pos/static/js/catalog.js @@ -69,7 +69,9 @@ }; // ─── Catalog mode (OEM / Local) ─── - var catalogMode = (localStorage.getItem('catalog_mode') === 'local' ? 'local' : 'oem'); + // OEM catalog is disabled until fully completed — force local mode + var catalogMode = 'local'; + localStorage.setItem('catalog_mode', 'local'); function updateModeToggleUI() { var btns = document.querySelectorAll('#modeToggle button'); @@ -84,6 +86,11 @@ function setCatalogMode(mode) { if (mode !== 'oem' && mode !== 'local' && mode !== 'supplies') return; + if (mode === 'oem') { + // OEM catalog is disabled until fully completed + alert('Catálogo OEM próximamente. Por favor usa el modo Local o Shop Supplies.'); + return; + } if (mode === catalogMode) return; catalogMode = mode; localStorage.setItem('catalog_mode', mode); diff --git a/pos/templates/catalog.html b/pos/templates/catalog.html index ef20a9d..d6a40d0 100644 --- a/pos/templates/catalog.html +++ b/pos/templates/catalog.html @@ -660,7 +660,7 @@
- +
diff --git a/pos/tests/test_oem_block.py b/pos/tests/test_oem_block.py new file mode 100644 index 0000000..e9ca6bf --- /dev/null +++ b/pos/tests/test_oem_block.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Test OEM catalog block.""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from tenant_db import get_master_conn, get_tenant_conn_by_dbname +from services.catalog_service import smart_search, get_part_detail + +PASS = '\033[92mPASS\033[0m' +FAIL = '\033[91mFAIL\033[0m' + +def ok(label, condition, detail=''): + if condition: + print(f" [{PASS}] {label}") + else: + print(f" [{FAIL}] {label} {detail}") + +master = get_master_conn() +cur = master.cursor() +cur.execute("SELECT db_name FROM tenants WHERE is_active = true LIMIT 1") +row = cur.fetchone() +cur.close(); master.close() + +db_name = row[0] +tenant = get_tenant_conn_by_dbname(db_name) +master = get_master_conn() + +print("OEM Catalog Block Test") +print("=" * 40) + +# smart_search should be blocked at blueprint level, but let's check the service +# The service itself still works; the blueprint blocks it. +# Let's verify the config flag is False +from config import CATALOG_OEM_ENABLED +ok("CATALOG_OEM_ENABLED is False", CATALOG_OEM_ENABLED is False, f"value={CATALOG_OEM_ENABLED}") + +# Local mode endpoints should still work +from services.catalog_modes import normalize_mode +ok("normalize_mode local", normalize_mode('local') == 'local') +ok("normalize_mode oem", normalize_mode('oem') == 'oem') + +# Check that get_parts_local is available +from services.catalog_service import get_parts_local +ok("get_parts_local exists", callable(get_parts_local)) + +master.close() +tenant.close() + +print("Done.")