feat: module toggles in POS config and Instance Manager
- 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
This commit is contained in:
140
pos/services/wa_customer.py
Normal file
140
pos/services/wa_customer.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user