- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
126 lines
3.7 KiB
Python
Executable File
126 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Sincroniza pacientes de Odoo → WACRM (Supabase self-hosted).
|
|
Inserta/actualiza contactos en la tabla public.contacts.
|
|
|
|
Uso:
|
|
source /root/odoo-venv/bin/activate && python3 /root/migracion/sync_odoo_to_wacrm.py
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ODOO_PATH = Path('/root/skeen-odoo')
|
|
sys.path.insert(0, str(ODOO_PATH))
|
|
|
|
import odoo
|
|
from odoo import api, SUPERUSER_ID
|
|
import psycopg2
|
|
from psycopg2.extras import execute_values
|
|
|
|
# Configuración de Supabase / WACRM
|
|
SUPABASE_HOST = 'localhost'
|
|
SUPABASE_PORT = 6543
|
|
SUPABASE_USER = 'postgres.your-tenant-id'
|
|
SUPABASE_PASSWORD = '8bbf201d9d60488a1e86e5396b31e22d'
|
|
SUPABASE_DB = 'postgres'
|
|
|
|
WACRM_ACCOUNT_ID = 'a2dc0db3-dea0-4bd8-8733-1d62975c5f89'
|
|
WACRM_OWNER_USER_ID = '00000000-0000-0000-0000-000000000001'
|
|
|
|
|
|
def get_odoo_env(database='skeen_odoo'):
|
|
odoo.tools.config.parse_config(['-c', '/root/skeen-odoo/odoo.conf'])
|
|
db = odoo.sql_db.db_connect(database)
|
|
cr = db.cursor()
|
|
return api.Environment(cr, SUPERUSER_ID, {}), cr
|
|
|
|
|
|
def get_wacrm_connection():
|
|
return psycopg2.connect(
|
|
host=SUPABASE_HOST,
|
|
port=SUPABASE_PORT,
|
|
user=SUPABASE_USER,
|
|
password=SUPABASE_PASSWORD,
|
|
dbname=SUPABASE_DB,
|
|
)
|
|
|
|
|
|
def normalize_phone(phone):
|
|
digits = ''.join(c for c in (phone or '') if c.isdigit())
|
|
if digits and not digits.startswith('52') and len(digits) == 10:
|
|
digits = '52' + digits
|
|
return digits
|
|
|
|
|
|
def sync_contacts(limit=None):
|
|
env, cr = get_odoo_env()
|
|
try:
|
|
domain = [('is_patient', '=', True), ('phone', '!=', False), ('phone', '!=', '')]
|
|
partners = env['res.partner'].sudo().search(domain)
|
|
if limit:
|
|
partners = partners[:limit]
|
|
|
|
rows = []
|
|
for p in partners:
|
|
phone = normalize_phone(p.phone)
|
|
if not phone:
|
|
continue
|
|
rows.append((
|
|
WACRM_OWNER_USER_ID,
|
|
phone,
|
|
p.name or 'Paciente',
|
|
p.email or '',
|
|
'', # company
|
|
'', # avatar_url
|
|
WACRM_ACCOUNT_ID,
|
|
))
|
|
|
|
if not rows:
|
|
print('No hay pacientes para sincronizar')
|
|
return
|
|
|
|
conn = get_wacrm_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
# UPSERT por (account_id, phone_normalized)
|
|
execute_values(cur, """
|
|
INSERT INTO public.contacts
|
|
(user_id, phone, name, email, company, avatar_url, account_id)
|
|
VALUES %s
|
|
ON CONFLICT (account_id, phone_normalized) WHERE phone_normalized <> ''
|
|
DO UPDATE SET
|
|
name = EXCLUDED.name,
|
|
email = EXCLUDED.email,
|
|
company = EXCLUDED.company,
|
|
avatar_url = EXCLUDED.avatar_url,
|
|
user_id = EXCLUDED.user_id,
|
|
updated_at = NOW()
|
|
RETURNING id, phone
|
|
""", rows, page_size=1000)
|
|
|
|
result = cur.fetchall()
|
|
phone_to_id = {phone: str(cid) for cid, phone in result}
|
|
|
|
# Actualizar referencia en Odoo
|
|
for p in partners:
|
|
phone = normalize_phone(p.phone)
|
|
if phone in phone_to_id:
|
|
p.write({
|
|
'wacrm_synced': True,
|
|
'wacrm_contact_id': phone_to_id[phone],
|
|
})
|
|
|
|
conn.commit()
|
|
print(f'Sincronizados {len(rows)} contactos a WACRM')
|
|
finally:
|
|
conn.close()
|
|
finally:
|
|
cr.commit()
|
|
cr.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sync_contacts()
|