feat: complete session — catalog, marketplace, WhatsApp, peer-to-peer, install scripts
Major features: - Pixel-Perfect glassmorphism design (landing + POS + public catalog) - OEM/Local catalog toggle with Nexpart taxonomy (14 groups, 108 subgroups, 558 part types) - Marketplace B2B Phase 1 (bodegas, POs, status machine, WA+email notifications) - Peer-to-peer inventory (multi-instance, LAN discovery) - WhatsApp: photo→Vision AI, voice→Whisper, conversational quotations - Smart unified search (VIN/plate/part_number/keyword auto-detect) - Shop Supplies tab (vehicle-independent parts) - Chatbot AI fallback chain (5 models) + response cache - CSV inventory import tool + setup_instance.sh installer - Tablet-responsive CSS + sidebar toggle - Filters, export CSV, employee edit, business data save - Quotation system (WA→POS) with auto-print on confirmation - Live stats on landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
95
pos/blueprints/peer_bp.py
Normal file
95
pos/blueprints/peer_bp.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Peer API — public endpoints for inter-instance communication.
|
||||
|
||||
These endpoints do NOT require auth (they're called machine-to-machine by
|
||||
other Nexus instances on the network). They expose read-only inventory data
|
||||
so the marketplace can aggregate stock across the whole Nexus network.
|
||||
|
||||
Routes:
|
||||
GET /pos/api/peer/health — instance status + inventory count
|
||||
GET /pos/api/peer/inventory — search this instance's inventory
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
from tenant_db import get_tenant_conn
|
||||
from services import peer_service
|
||||
|
||||
peer_bp = Blueprint('peer', __name__, url_prefix='/pos/api/peer')
|
||||
|
||||
|
||||
# ─── Which tenant to use for the peer endpoint? ──────────────────────────
|
||||
# In production each instance serves one tenant. For the demo, we hardcode
|
||||
# tenant_id=11 (the demo refaccionaria). This will be read from a config
|
||||
# file in the future when each instance has exactly 1 active tenant.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
def _get_local_tenant_id():
|
||||
"""Read the local tenant ID from peers.json or fall back to 11."""
|
||||
try:
|
||||
cfg_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'peers.json')
|
||||
with open(cfg_path, 'r') as f:
|
||||
cfg = json.load(f)
|
||||
return cfg.get('tenant_id', 11)
|
||||
except Exception:
|
||||
return 11
|
||||
|
||||
|
||||
@peer_bp.route('/health', methods=['GET'])
|
||||
def peer_health():
|
||||
"""Public health check — no auth. Returns instance name + basic stats."""
|
||||
tenant_id = _get_local_tenant_id()
|
||||
inventory_count = 0
|
||||
try:
|
||||
conn = get_tenant_conn(tenant_id)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) FROM inventory i
|
||||
WHERE i.is_active = TRUE
|
||||
AND COALESCE((SELECT SUM(quantity) FROM inventory_operations WHERE inventory_id = i.id), 0) > 0
|
||||
""")
|
||||
inventory_count = cur.fetchone()[0]
|
||||
cur.close()
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f'[peer] health check DB error: {e}')
|
||||
|
||||
return jsonify({
|
||||
'status': 'ok',
|
||||
'instance_name': peer_service.get_instance_name(),
|
||||
'instance_id': peer_service.get_instance_id(),
|
||||
'inventory_count': inventory_count,
|
||||
'peer_count': len(peer_service.get_peers()),
|
||||
})
|
||||
|
||||
|
||||
@peer_bp.route('/inventory', methods=['GET'])
|
||||
def peer_inventory():
|
||||
"""Public inventory search — no auth.
|
||||
|
||||
Called by other Nexus instances to see what this refaccionaria has in stock.
|
||||
Returns minimal data: part number, name, brand, price, stock hint.
|
||||
Does NOT expose exact stock quantities (competitive info).
|
||||
|
||||
Query params:
|
||||
q: search term (optional — without it, returns popular/all items)
|
||||
limit: max results (default 50, max 200)
|
||||
"""
|
||||
q = request.args.get('q', '').strip() or None
|
||||
limit = min(request.args.get('limit', 50, type=int), 200)
|
||||
|
||||
tenant_id = _get_local_tenant_id()
|
||||
try:
|
||||
conn = get_tenant_conn(tenant_id)
|
||||
data = peer_service.get_local_inventory(conn, query=q, limit=limit)
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f'[peer] inventory query error: {e}')
|
||||
data = []
|
||||
|
||||
return jsonify({
|
||||
'instance_name': peer_service.get_instance_name(),
|
||||
'data': data,
|
||||
'count': len(data),
|
||||
})
|
||||
Reference in New Issue
Block a user