Asistencia Geo — Documentación

05 — Plan de integración con Odoo (HR Attendance)

Objetivo: que el Sistema de Asistencia Georeferenciado sea la fuente de verdad del registro en campo y Odoo consuma esos datos en hr.attendance para nómina, sin duplicar ni perder eventos. Conexión por XML-RPC (JSON-RPC compatible) — tecnología nativa de Odoo, sin costo.

1. Arquitectura de integración

flowchart LR
  Eventos[attendance_events] --> Outbox[odoo_sync<br/>pending]
  Outbox --> Worker[Worker async<br/>odoo_client.py]
  Worker -->|create/write hr.attendance| Odoo[Odoo]
  Worker -->|reintento backoff| Outbox
  Odoo -->|cron fallback| API[GET /attendance/export]

Patrón outbox + idempotencia:

  1. Al guardar un check-in/out se inserta una fila en odoo_sync con status = pending.
  2. Un worker asíncrono (APScheduler / Celery) toma filas pending, llama a Odoo y marca sent (con odoo_attendance_id) o error (con last_error, reintentos con backoff).
  3. Si Odoo está caído, los eventos no se pierden: quedan en pending y se reintentan.

2. Modelo de datos en Odoo

# asistencia_geo_odoo/models/hr_attendance.py
from odoo import models, fields

class HrAttendance(models.Model):
    _inherit = "hr.attendance"

    x_lat = fields.Float("Latitud")
    x_lng = fields.Float("Longitud")
    x_accuracy_m = fields.Float("Precisión (m)")
    x_geofence_status = fields.Selection([
        ("inside", "Dentro"), ("outside", "Fuera"), ("not_required", "No aplica")])
    x_mock_detected = fields.Boolean("Mock detectado")
    x_root_jailbreak = fields.Boolean("Root/Jailbreak")
    x_server_time = fields.Datetime("Hora del servidor")
    x_source_event_id = fields.Char("ID evento origen (idempotencia)")

3. Mapeo de entidades

Sistema local Odoo
users.odoo_employee_id hr.employee.id
attendance_events.event_type = check_in hr.attendance.check_in
attendance_events.event_type = check_out hr.attendance.check_out
attendance_events.server_time hr.attendance.check_in/check_out
attendance_events.idempotency_key x_source_event_id (dedupe)

4. Sincronización (check-in → Odoo)

backend/app/services/odoo_client.py (resumen del flujo):

def push_check_in(event, employee):
    odoo = odoorpc.ODOO(url, port=8069)
    odoo.login(db, user, api_key)
    attendance_id = odoo.env["hr.attendance"].create({
        "employee_id": employee.odoo_employee_id,
        "check_in": event.server_time.isoformat(),
        "x_lat": event.lat, "x_lng": event.lng,
        "x_geofence_status": event.geofence_status.value,
        "x_mock_detected": event.mock_detected,
        "x_root_jailbreak": event.root_jailbreak_detected,
        "x_server_time": event.server_time.isoformat(),
        "x_source_event_id": event.idempotency_key,
    })
    return attendance_id

5. Cron de importación inversa (fallback)

Para robustez, el módulo Odoo incluye un cron que, cada N minutos, consulta GET /api/v1/attendance/export?since=<last_sync> del backend y crea/actualiza hr.attendance. Cubre escenarios donde el worker saliente no pueda correr.

<!-- asistencia_geo_odoo/data/ir_cron_data.xml -->
<record id="cron_import_attendance" model="ir.cron">
  <field name="name">Importar asistencia georreferenciada</field>
  <field name="model_id" ref="model_hr_attendance"/>
  <field name="interval_number">10</field>
  <field name="interval_type">minutes</field>
  <field name="code">model._cron_import_geo_attendance()</field>
</record>

6. Configuración

Variables de entorno del backend:

ODOO_URL=http://odoo.local
ODOO_PORT=8069
ODOO_DB=acme
ODOO_USER=attendance_bot
ODOO_API_KEY=xxxx

7. Plan de despliegue (fases)

  1. Fase 1 — Credenciales y mapeo: crear usuario API en Odoo con permisos sobre hr.attendance; rellenar users.odoo_employee_id.
  2. Fase 2 — Worker saliente: activar odoo_client + outbox; sincronizar histórico con backfill.
  3. Fase 3 — Módulo custom: instalar asistencia_geo_odoo para ver geolocalización/mock en Odoo.
  4. Fase 4 — Cron inverso y reconciliación: panel de reconciliación odoo_sync vs hr.attendance.
  5. Fase 5 — Pruebas y corte: período paralelo (doble registro) antes de apagar el método manual.

8. Manejo de errores