- Add GET/PUT /pos/api/config/modules endpoints in POS config_bp.py - Update sidebar.js to filter nav items based on enabled modules - Add Modules section to POS config.html with toggles for WhatsApp, Marketplace, MercadoLibre - Add module load/save logic to POS config.js - Preload modules in app-init.js for sidebar caching - Add tenant module management to Instance Manager - get_tenant_modules / update_tenant_modules in tenant_service.py - GET/PUT /api/tenants/<id>/modules endpoints in tenants_bp.py - Add modules modal to manager index.html - Add module editing UI and logic to manager.js - Add toggle-switch CSS to manager.css
141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
"""
|
|
WhatsApp Customer Service — identificación y vinculación de clientes.
|
|
|
|
Funciones para buscar, crear y vincular clientes desde el flujo de WhatsApp.
|
|
"""
|
|
|
|
import re
|
|
|
|
|
|
def find_customer_by_phone(phone, tenant_conn):
|
|
"""Buscar cliente por número de teléfono exacto o parcial."""
|
|
if not tenant_conn or not phone:
|
|
return []
|
|
cur = tenant_conn.cursor()
|
|
# Limpiar phone de prefijos internacionales para búsqueda flexible
|
|
clean = phone.replace('+52', '').replace('52', '').lstrip('1')
|
|
cur.execute("""
|
|
SELECT id, name, phone, address, rfc
|
|
FROM customers
|
|
WHERE phone = %s OR phone LIKE %s OR phone LIKE %s
|
|
LIMIT 5
|
|
""", (phone, f'%{clean}', f'%{clean[-10:]}' if len(clean) >= 10 else f'%{clean}'))
|
|
rows = cur.fetchall()
|
|
cur.close()
|
|
return [{'id': r[0], 'name': r[1], 'phone': r[2], 'address': r[3], 'rfc': r[4]} for r in rows]
|
|
|
|
|
|
def find_customer_by_name(name, tenant_conn):
|
|
"""Buscar cliente por nombre (ILIKE)."""
|
|
if not tenant_conn or not name:
|
|
return []
|
|
cur = tenant_conn.cursor()
|
|
# Buscar por nombre completo o primer palabra
|
|
first_word = name.split()[0] if name else name
|
|
cur.execute("""
|
|
SELECT id, name, phone, address, rfc
|
|
FROM customers
|
|
WHERE name ILIKE %s OR name ILIKE %s
|
|
LIMIT 5
|
|
""", (f'%{name}%', f'%{first_word}%'))
|
|
rows = cur.fetchall()
|
|
cur.close()
|
|
return [{'id': r[0], 'name': r[1], 'phone': r[2], 'address': r[3], 'rfc': r[4]} for r in rows]
|
|
|
|
|
|
def search_customers(query, tenant_conn):
|
|
"""Buscar por teléfono o nombre."""
|
|
if not tenant_conn or not query:
|
|
return []
|
|
# Detectar si es número de teléfono
|
|
digits = re.sub(r'\D', '', query)
|
|
if len(digits) >= 7:
|
|
by_phone = find_customer_by_phone(digits, tenant_conn)
|
|
if by_phone:
|
|
return by_phone
|
|
return find_customer_by_name(query, tenant_conn)
|
|
|
|
|
|
def get_customer_by_id(tenant_conn, customer_id):
|
|
"""Obtener cliente por ID."""
|
|
if not tenant_conn or not customer_id:
|
|
return None
|
|
cur = tenant_conn.cursor()
|
|
cur.execute("""
|
|
SELECT id, name, phone, address, rfc, vehicle_info
|
|
FROM customers WHERE id = %s
|
|
""", (customer_id,))
|
|
row = cur.fetchone()
|
|
cur.close()
|
|
if row:
|
|
return {
|
|
'id': row[0], 'name': row[1], 'phone': row[2],
|
|
'address': row[3], 'rfc': row[4], 'vehicle_info': row[5]
|
|
}
|
|
return None
|
|
|
|
|
|
def create_customer(tenant_conn, phone, name, email=None, address=None, rfc=None):
|
|
"""Crear cliente nuevo desde WhatsApp."""
|
|
if not tenant_conn:
|
|
return None
|
|
cur = tenant_conn.cursor()
|
|
cur.execute("""
|
|
INSERT INTO customers (name, phone, email, address, rfc, is_active, created_at)
|
|
VALUES (%s, %s, %s, %s, %s, TRUE, NOW())
|
|
RETURNING id
|
|
""", (name, phone, email, address, rfc))
|
|
cid = cur.fetchone()[0]
|
|
tenant_conn.commit()
|
|
cur.close()
|
|
return cid
|
|
|
|
|
|
def link_wa_customer(phone, customer_id, tenant_conn):
|
|
"""Vincular número WA a cliente permanentemente."""
|
|
if not tenant_conn or not phone or not customer_id:
|
|
return
|
|
cur = tenant_conn.cursor()
|
|
cur.execute("""
|
|
INSERT INTO wa_customer_links (phone, customer_id, updated_at)
|
|
VALUES (%s, %s, NOW())
|
|
ON CONFLICT (phone) DO UPDATE SET customer_id = EXCLUDED.customer_id, updated_at = NOW()
|
|
""", (phone, customer_id))
|
|
tenant_conn.commit()
|
|
cur.close()
|
|
|
|
|
|
def get_linked_customer(phone, tenant_conn):
|
|
"""Obtener customer_id vinculado a un número WA."""
|
|
if not tenant_conn or not phone:
|
|
return None
|
|
cur = tenant_conn.cursor()
|
|
cur.execute("SELECT customer_id FROM wa_customer_links WHERE phone = %s", (phone,))
|
|
row = cur.fetchone()
|
|
cur.close()
|
|
return row[0] if row else None
|
|
|
|
|
|
def get_customer_address(tenant_conn, customer_id):
|
|
"""Obtener dirección del cliente."""
|
|
if not tenant_conn or not customer_id:
|
|
return None
|
|
cur = tenant_conn.cursor()
|
|
cur.execute("SELECT address FROM customers WHERE id = %s", (customer_id,))
|
|
row = cur.fetchone()
|
|
cur.close()
|
|
return row[0] if row and row[0] else None
|
|
|
|
|
|
def update_customer_address(tenant_conn, customer_id, address):
|
|
"""Actualizar dirección del cliente."""
|
|
if not tenant_conn or not customer_id or not address:
|
|
return
|
|
cur = tenant_conn.cursor()
|
|
cur.execute(
|
|
"UPDATE customers SET address = %s WHERE id = %s",
|
|
(address, customer_id)
|
|
)
|
|
tenant_conn.commit()
|
|
cur.close()
|