Files
Autoparts-DB/pos/blueprints/public_bp.py
consultoria-as ff45905b49 feat(whatsapp): QWEN primary AI backend, Hermes fallback, conversation history, vehicle persistence, demo prompts
- 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
2026-05-06 20:27:14 +00:00

107 lines
4.2 KiB
Python

"""Public blueprint — unauthenticated routes for shared content.
These routes live outside the /pos/api prefix so they can be accessed
by customers without login.
"""
import jwt
from flask import Blueprint, request, jsonify, render_template_string
from tenant_db import get_tenant_conn
from config import JWT_SECRET
from blueprints.pos_bp import PUBLIC_QUOTE_TEMPLATE
public_bp = Blueprint('public', __name__)
@public_bp.route('/public/quote/<token>', methods=['GET'])
def public_quote(token):
"""Unauthenticated public view of a quotation."""
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])
if payload.get('type') != 'public_quote':
return jsonify({'error': 'Invalid token type'}), 400
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Quote expired'}), 410
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 400
conn = get_tenant_conn(payload['tenant_id'])
cur = conn.cursor()
cur.execute("""
SELECT q.id, q.subtotal, q.tax_total, q.total, q.valid_until,
q.created_at, q.notes, q.customer_id, q.currency, q.exchange_rate,
q.status,
c.name as customer_name, c.phone as customer_phone, c.email as customer_email,
e.name as employee_name
FROM quotations q
LEFT JOIN customers c ON q.customer_id = c.id
LEFT JOIN employees e ON q.employee_id = e.id
WHERE q.id = %s
""", (payload['quot_id'],))
row = cur.fetchone()
if not row:
cur.close(); conn.close()
return jsonify({'error': 'Quotation not found'}), 404
cols = ['id', 'subtotal', 'tax_total', 'total', 'valid_until', 'created_at',
'notes', 'customer_id', 'currency', 'exchange_rate', 'status',
'customer_name', 'customer_phone', 'customer_email', 'employee_name']
quot = dict(zip(cols, row))
for k in ('subtotal', 'tax_total', 'total', 'exchange_rate'):
if quot.get(k) is not None:
quot[k] = float(quot[k])
if quot.get('created_at'):
quot['created_at'] = str(quot['created_at'])
if quot.get('valid_until'):
quot['valid_until'] = str(quot['valid_until'])
cur.execute("""
SELECT part_number, name, quantity, unit_price, discount_pct, tax_rate, subtotal
FROM quotation_items WHERE quotation_id = %s ORDER BY id
""", (payload['quot_id'],))
items = []
for r in cur.fetchall():
items.append({
'part_number': r[0], 'name': r[1], 'quantity': r[2],
'unit_price': float(r[3]) if r[3] else 0,
'discount_pct': float(r[4]) if r[4] else 0,
'tax_rate': float(r[5]) if r[5] else 0,
'subtotal': float(r[6]) if r[6] else 0,
})
cur.close(); conn.close()
html = render_template_string(PUBLIC_QUOTE_TEMPLATE,
quot=quot, items=items, host=request.host_url.rstrip('/'),
token=token)
return html, 200, {'Content-Type': 'text/html; charset=utf-8'}
@public_bp.route('/public/quote/<token>/accept', methods=['POST'])
def public_quote_accept(token):
"""Customer accepts a public quote."""
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])
if payload.get('type') != 'public_quote':
return jsonify({'error': 'Invalid token type'}), 400
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Quote expired'}), 410
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 400
conn = get_tenant_conn(payload['tenant_id'])
cur = conn.cursor()
cur.execute("SELECT status FROM quotations WHERE id = %s", (payload['quot_id'],))
row = cur.fetchone()
if not row:
cur.close(); conn.close()
return jsonify({'error': 'Quotation not found'}), 404
if row[0] != 'active':
cur.close(); conn.close()
return jsonify({'error': 'Quotation is no longer active'}), 400
cur.execute("UPDATE quotations SET status = 'converted' WHERE id = %s",
(payload['quot_id'],))
conn.commit()
cur.close(); conn.close()
return jsonify({'message': 'Cotizacion aceptada. Un asesor se pondra en contacto contigo.'})