Files
SKEEN-Proyecto/odoo-addons/skeen_whatsapp/controllers/main.py
Consultoría Alcaraz Salazar a718592291 Initial commit: SKEEN Derma Experts - Sistema Integral de Gestión Clínica
- 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/
2026-07-20 07:44:23 +00:00

183 lines
7.6 KiB
Python

# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
import json
class SkeenWhatsAppController(http.Controller):
"""Controladores API para integración con Hermes/WACRM"""
@http.route('/skeen/api/v1/available_slots', type='json', auth='api_key', methods=['POST'], csrf=False)
def get_available_slots(self, service_id, date_from, date_to, **kw):
"""Consultar slots disponibles para un servicio en un rango de fechas"""
try:
servicio = request.env['skeen.servicio'].browse(int(service_id))
if not servicio.exists():
return {'status': 'error', 'message': 'Servicio no encontrado'}
slots = []
from datetime import datetime, timedelta
current_date = datetime.strptime(date_from, '%Y-%m-%d').date()
end_date = datetime.strptime(date_to, '%Y-%m-%d').date()
while current_date <= end_date:
day_slots = request.env['skeen.cita'].get_available_slots(current_date, int(service_id))
for slot in day_slots:
slots.append({
'date': current_date.strftime('%Y-%m-%d'),
'time': slot['time_str'],
'datetime': f"{current_date.strftime('%Y-%m-%d')} {slot['time_str']}",
})
current_date += timedelta(days=1)
return {'status': 'success', 'slots': slots}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/create_appointment', type='json', auth='api_key', methods=['POST'], csrf=False)
def create_appointment(self, phone, name, service_id, date, time, **kw):
"""Crear una nueva cita"""
try:
# Buscar o crear paciente
partner = request.env['res.partner'].search([('phone', '=', phone)], limit=1)
if not partner:
partner = request.env['res.partner'].create({
'name': name,
'phone': phone,
'is_patient': True,
'source': 'whatsapp',
})
# Convertir time a float
time_parts = time.split(':')
time_float = float(time_parts[0]) + float(time_parts[1]) / 60.0
# Crear cita
cita = request.env['skeen.cita'].create({
'partner_id': partner.id,
'servicio_id': int(service_id),
'date': date,
'time': time_float,
'state': 'confirmed',
})
return {
'status': 'success',
'booking_id': cita.id,
'reference': cita.name,
'patient_id': partner.patient_id,
}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/patient_balance', type='json', auth='api_key', methods=['POST'], csrf=False)
def get_patient_balance(self, phone, **kw):
"""Obtener saldo pendiente y monedero del paciente"""
try:
partner = request.env['res.partner'].search([('phone', '=', phone)], limit=1)
if not partner:
return {'status': 'error', 'message': 'Paciente no encontrado'}
# Citas pendientes de pago
pending_citas = request.env['skeen.cita'].search([
('partner_id', '=', partner.id),
('payment_state', '!=', 'paid'),
('state', 'in', ('confirmed', 'arrived', 'in_progress', 'done')),
])
pending_balance = sum(c.servicio_price for c in pending_citas)
# Monedero
monedero = request.env['skeen.monedero'].search([('partner_id', '=', partner.id)], limit=1)
return {
'status': 'success',
'pending_balance': pending_balance,
'pending_invoices': [{
'id': c.id,
'reference': c.name,
'service': c.servicio_id.name,
'amount': c.servicio_price,
'date': c.date.strftime('%Y-%m-%d'),
} for c in pending_citas],
'wallet_points': monedero.points if monedero else 0,
'wallet_mxn': monedero.equivalent_mxn if monedero else 0,
}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/wallet_redeem', type='json', auth='api_key', methods=['POST'], csrf=False)
def redeem_wallet(self, phone, points, appointment_id=None, **kw):
"""Redimir puntos del monedero"""
try:
partner = request.env['res.partner'].search([('phone', '=', phone)], limit=1)
if not partner:
return {'status': 'error', 'message': 'Paciente no encontrado'}
monedero = request.env['skeen.monedero'].search([('partner_id', '=', partner.id)], limit=1)
if not monedero:
return {'status': 'error', 'message': 'Monedero no encontrado'}
monedero.redeem_points(int(points))
return {
'status': 'success',
'remaining_points': monedero.points,
'remaining_mxn': monedero.equivalent_mxn,
}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/cancel_appointment', type='json', auth='api_key', methods=['POST'], csrf=False)
def cancel_appointment(self, phone, booking_id=None, **kw):
"""Cancelar una cita"""
try:
domain = [
('partner_id.phone', '=', phone),
('state', 'in', ('pending', 'confirmed')),
]
if booking_id:
domain.append(('id', '=', int(booking_id)))
cita = request.env['skeen.cita'].search(domain, order='date asc', limit=1)
if not cita:
return {'status': 'error', 'message': 'No se encontró cita activa'}
cita.write({'state': 'cancelled'})
return {
'status': 'success',
'cancelled_booking': cita.name,
}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/services', type='json', auth='api_key', methods=['GET', 'POST'], csrf=False)
def list_services(self, **kw):
"""Listar todos los servicios disponibles"""
try:
services = request.env['skeen.servicio'].search([('active', '=', True)])
return {
'status': 'success',
'services': [{
'id': s.id,
'code': s.code,
'name': s.name,
'category': s.category,
'price': s.price,
'package_price': s.package_price,
'package_sessions': s.package_sessions,
'package_notes': s.package_notes or '',
'duration_min': s.duration_min,
'description': s.description,
} for s in services],
}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/skeen/api/v1/health', type='json', auth='none', methods=['GET'], csrf=False)
def health_check(self, **kw):
"""Health check para monitoreo"""
return {'status': 'ok', 'service': 'skeen-odoo-api', 'version': '1.0.0'}