- Expose loadInventoryStats globally so inventory.js can call it after CRUD
- Fix token key: use pos_token (not access_token) to match auth scheme
- After successful POST /items: close modal, clear form inputs, reload stats
- Bump inventory.js cache-bust query param v3 -> v4
- Add POS_INTERNAL_URL config for cross-VM API calls
- create_demo now calls POS /internal/whatsapp-bridge after tenant creation
- delete_tenant now destroys bridge container before dropping DB
- Graceful fallback if bridge provisioning fails
- Add Dockerfile.whatsapp-bridge with Baileys + env var support
- Modify whatsapp-bridge-server.js to accept PORT, TENANT_ID, WEBHOOK_BASE
- Add internal_bp.py with endpoints to provision/destroy bridges via Docker
- Register internal_bp in app.py
- Each tenant gets isolated container, port, and volume
- Refactor whatsapp_service.py to accept bridge_url parameter
- whatsapp_bp.py: remove hardcoded tenant_id=11, use g.tenant_id
- whatsapp_bp.py: webhook now accepts ?tenant_id param with fallback
- config_bp.py: add GET/PUT /config/whatsapp endpoints
- Each tenant can now have its own Baileys bridge URL and settings
- Complete Flask-based control panel for multi-tenant POS instances
- Dashboard with global stats, system health, and recent demos
- Demo provisioning in 1 click with auto-expiration tracking
- Tenant management: activate/deactivate, reset data, delete
- Health monitoring: PostgreSQL, Redis, disk, memory, systemd services
- Migration orchestration UI for running schema updates across all tenants
- JWT authentication with manager_users table
- Dark theme SPA frontend with real-time search and actions
- systemd service file included
Brand catalog now follows the same navigation as the regular catalog:
1. Brands -> 2. Models -> 3. Years -> 4. Engines -> 5. Categories -> 6. Parts
Backend:
- Add /mye-parts endpoint for MYE-specific parts with category filter
- Uses existing /models, /years, /engines, /categories endpoints
Frontend:
- Complete rewrite of brand-catalog.js with breadcrumb navigation
- State machine: brands -> models -> years -> engines -> categories -> parts
- Search and pagination preserved at parts level
- Breadcrumb allows jumping back to any previous step
Add updatefound listener in catalog.html that reloads the page
automatically when a new service worker is activated. This ensures
users get the latest HTML and JS without manual hard refresh.
- Bump cache to nexus-pos-v6 to force invalidation
- HTML pages now use network-first instead of cache-first
This ensures users always get the latest HTML with correct
JS/CSS references (?v=3) instead of stale cached HTML
- Remove HTML pages from APP_SHELL precache (only static assets)
- Keep cache-first for JS/CSS/images
- Add #brandCatalogSearch container in HTML for search inputs
- Move brand search input out of renderBrandList so it persists while typing
- Move parts search input out of renderPartsList so it persists while typing
- Reset now clears search container
- Bump JS cache bust to v=3
Update service worker cache name to nexus-pos-v5 to force cache
invalidation. Add brand-catalog.js to APP_SHELL precache list.
This should resolve stale cached JS causing parse errors.
/vehicle-brands now uses get_brands_for_mode('oem') to return the same
36 North American brands (Mexico/USA/Canada) as the regular catalog flow,
instead of all 619 brands in the database.
brand-catalog.js was missing Authorization header on fetch calls,
causing 401 Unauthorized errors. Now reads pos_token from localStorage
and includes Bearer token in every request. Also handles 401 responses
by redirecting to /pos/login. Bump JS cache bust to v=2.
Backend:
- Add 'search' param to /brand-parts endpoint (filters oem_part_number and name via ILIKE)
- Keep count query accurate with search filter
Frontend (brand-catalog.js):
- Brand search input: filters 619 brands locally while typing
- Parts pagination: Previous/Next buttons with page counter (50 per page)
- Parts search within category: search input + Enter key triggers backend search
- Visual polish: stock badges, empty-state messages, responsive layout
- Loading states and breadcrumbs improved
Remove broken location blocks for static files that had no proxy_pass,
which caused all .js/.css files to return 404 or HTML.
Add explicit /pos/static/ location with proxy_pass + cache headers.
Update nexus-pos.conf in repo to match live config.
- Add QWEN (qwen3.6) as primary AI backend with short system prompt
- Hermes remains as fallback with 45s timeout
- Increase QWEN timeout to 35s, max_tokens to 4000
- Add conversation history loading from whatsapp_messages (last 4 msgs)
- Persist detected vehicle in whatsapp_sessions table
- Add 'limpiar chat' / 'nuevo chat' / 'reset' commands to clear history
- Fix CSS conflict: rename whatsapp chat-panel classes to wa-chat-panel
- Fix JS ID conflicts with chat.js widget (waChatPanel, waChatMessages, etc.)
- Improve no-stock response: conversational with alternatives
- Split search_query by | for multi-part lookups
- Add DEMO_PROMPTS.md and DEMO_PROMPTS_V2.md
- Added save_qwen_fitment() in inventory_vehicle_compat.py to centralize
inserting QWEN results into inventory_vehicle_compat
- Simplified inventory_bp.py create_item() and auto_match_item_vehicles()
to use the centralized function, removing duplicated INSERT logic
inventory_vehicle_compat.get_compatibility was trying to JOIN tenant
inventory_vehicle_compat with master tables (model_year_engine, brands,
models, years, engines) on a single tenant connection. Those tables only
exist in the master DB, causing the query to fail silently.
Fix: split into two queries:
1. Fetch MYE IDs from tenant's inventory_vehicle_compat
2. Resolve vehicle details from master DB via ANY(%s)
3. Merge results
Also fixes the argument mismatch: inventory_bp passed (tenant, master,
item_id) but the function only accepted 2 args.
Replaced simple exact-match with 8-layer fallback strategy:
1. Exact normalized part number (parts.oem_part_number)
2. Exact normalized aftermarket part number
3. Exact normalized cross-reference number
4. Partial ILIKE match on OEM numbers
5. Partial ILIKE match on aftermarket numbers
6. Partial ILIKE match on cross-reference numbers
7. Separator-stripped fallback (KYB-343412 → KYB343412)
8. Name-based search on parts.name_part / parts.name_es
and aftermarket_parts.name_aftermarket_parts when no part_number hit
Brand-aware filtering: when brand hint is provided and not 'GENERAL',
only returns MYEs for vehicles of that brand.
Limits: max 20 part IDs per layer, max 200 MYEs total.
Test: BPR5ES + TOYOTA → matched True, 2 parts, 200 MYEs inserted.
- tenant_db.py: add rollback() before returning conn to pool to prevent
'idle in transaction (aborted)' state that exhausts the pool
- tenant_db.py: increase pool maxconn from 10 to 20 for better concurrency
- inventory_vehicle_compat.py: fix column name cross_ref_number ->
cross_reference_number to match actual schema
Nginx auto-serves .min.js when .js is requested with try_files.
The browser had the old file cached with 6M expiry. Adding ?v=2
forces clients to fetch the new version with autoMatchCompat exposed.
- catalog_bp.py: /search and /part/<id> no longer blocked by CATALOG_OEM_ENABLED
These endpoints query the master parts DB and enrich with local stock;
they should work in both local and OEM modes.
- inventory.js: expose autoMatchCompat and removeCompat to window for
onclick handlers in dynamically generated HTML.
- Regenerated inventory.min.js
- Added all brands with vehicles >= 1980 relevant to Mexico-USA-Canada
- Covers: American, Japanese, Korean, German, UK, Italian, French,
Swedish, Spanish, Chinese (with MX presence), Indian, and commercial
- All 96 brands verified against master DB with year >= 1980
- Fixed column names: brands.name_brand, models.name_model, engines.name_engine
- Added fuzzy model matching with ILIKE %%pattern%% for TecDoc-style names
- Removed erroneous double cur.fetchone() that always returned None
- Added retry logic (3 attempts) for QWEN API empty responses
- Added fallback engine-less query when engine description doesn't match DB
- Protected _extract_json against None input
- bnpl_bp.py: APLAZO/Kueski/Clip application workflow (mock)
- erp_bp.py: Aspel/CONTPAQi/SAP/Odoo sync jobs (mock)
- whatsapp_cloud_bp.py: Meta Cloud API webhook, messages, templates
- supplier_portal_bp.py: demand by zone/branch and top-parts analytics
- app.py: register all new blueprints
The chat.js init() template did not include #chatTtsToggle, causing
a runtime TypeError when hasTTS was true. Added the toggle button
inside .chat-header-actions, matching chat-public.js structure.
Regenerated chat.min.js.