# Validacion: Documentacion MGN vs Logica Odoo 18.0 **Fecha:** 2025-12-05 **Objetivo:** Validar que la documentacion de MGN-001 a MGN-004 sea consistente con la logica de negocio probada de Odoo --- ## Resumen Ejecutivo | Modulo | Consistencia | Gaps Identificados | Recomendaciones | |--------|--------------|-------------------|-----------------| | MGN-001 Auth | 90% | 2 menores | Agregar API keys | | MGN-002 Users | 85% | 3 menores | Agregar campos Odoo | | MGN-003 RBAC | 95% | 1 menor | Documentar herencia | | MGN-004 Tenants | 80% | 2 importantes | Alinear con multi-company | --- ## MGN-001: Auth - Validacion ### Campos de res.users en Odoo ```python # Odoo res_users.py - Campos principales login = fields.Char(required=True) password = fields.Char(compute='_compute_password', inverse='_set_password') active = fields.Boolean(default=True) groups_id = fields.Many2many('res.groups') company_id = fields.Many2one('res.company', required=True) company_ids = fields.Many2many('res.company') # Multi-company share = fields.Boolean() # Usuario portal/externo ``` ### Comparacion con nuestra documentacion | Campo Odoo | Documentado MGN | Estado | Accion | |------------|-----------------|--------|--------| | login | email (como login) | OK | - | | password | password_hash | OK | - | | active | is_active | OK | - | | groups_id | via user_roles | OK | - | | company_id | tenant_id | OK | Adaptado a tenant | | company_ids | - | FALTA | Agregar multi-tenant access | | share | - | FALTA | Agregar flag is_external/is_portal | ### Logica de Autenticacion Odoo ```python # Odoo _check_credentials() - Linea 449-504 def _check_credentials(self, credential, env): # Valida password con CryptContext (passlib) # Retorna: uid, auth_method, mfa status valid, replacement = self._crypt_context().verify_and_update(credential['password'], hashed) if replacement is not None: self._set_encrypted_password(self.env.user.id, replacement) if not valid: raise AccessDenied() return {'uid': self.env.user.id, 'auth_method': 'password', 'mfa': 'default'} ``` **Consistencia:** Nuestra documentacion de JWT + bcrypt es equivalente funcionalmente. ### Recomendaciones MGN-001 1. **Agregar API Keys** - Odoo tiene `res.users.apikeys` para integraciones 2. **Campo share** - Agregar `is_portal` para diferenciar usuarios externos 3. **Multi-tenant access** - Usuario puede pertenecer a multiples tenants (como company_ids) --- ## MGN-002: Users - Validacion ### Modelo res.users hereda de res.partner ```python # Odoo res_users.py - Linea 329 class Users(models.Model): _name = "res.users" _inherits = {'res.partner': 'partner_id'} # Herencia delegada partner_id = fields.Many2one('res.partner', required=True, ondelete='restrict') name = fields.Char(related='partner_id.name') email = fields.Char(related='partner_id.email') ``` ### SELF_READABLE_FIELDS y SELF_WRITEABLE_FIELDS ```python # Odoo - Campos que usuario puede leer/escribir de si mismo SELF_READABLE_FIELDS = [ 'signature', 'company_id', 'login', 'email', 'name', 'image_1920', 'lang', 'tz', 'groups_id', 'partner_id', 'share', 'device_ids' ] SELF_WRITEABLE_FIELDS = [ 'signature', 'action_id', 'company_id', 'email', 'name', 'image_1920', 'lang', 'tz' ] ``` ### Comparacion con nuestra documentacion | Campo Odoo | Documentado MGN | Estado | Accion | |------------|-----------------|--------|--------| | partner_id | - | DIFERENTE | Nosotros users es independiente | | signature | signature | OK | En user_preferences | | lang | language | OK | - | | tz | timezone | OK | - | | image_1920 | avatar_url | OK | Adaptado | | action_id | - | NO APLICA | Home action de Odoo | | device_ids | - | CONSIDERAR | Para push notifications | | login_date | last_login_at | OK | - | ### Constraints importantes de Odoo ```python # Odoo _check_company - Linea 581 @api.constrains('company_id', 'company_ids', 'active') def _check_company(self): if user.company_id not in user.company_ids: raise ValidationError('Company not in allowed companies') # Odoo _check_one_user_type - Linea 616 # Un usuario no puede ser portal Y interno a la vez ``` ### Recomendaciones MGN-002 1. **Relacion con Partner** - En Odoo usuario hereda de partner. Nosotros mantenemos separado pero debemos asegurar que `contacts` pueda vincularse a `users` 2. **Device tracking** - Agregar tabla para dispositivos (util para push notifications) 3. **User type constraint** - Validar que usuario no sea interno y portal a la vez --- ## MGN-003: RBAC - Validacion ### Sistema de Grupos Odoo (res.groups) ```python # Odoo res_users.py - Linea 176-298 class Groups(models.Model): _name = "res.groups" name = fields.Char(required=True, translate=True) users = fields.Many2many('res.users') model_access = fields.One2many('ir.model.access', 'group_id') # Permisos CRUD rule_groups = fields.Many2many('ir.rule') # Record Rules (RLS) menu_access = fields.Many2many('ir.ui.menu') category_id = fields.Many2one('ir.module.category') # Categoria/Aplicacion share = fields.Boolean() # Grupo de usuarios externos ``` ### Permisos CRUD (ir.model.access.csv) ```csv # Formato: id, name, model_id, group_id, perm_read, perm_write, perm_create, perm_unlink "access_res_users_group_erp_manager","res_users","model_res_users","group_erp_manager",1,1,1,1 "access_res_partner_group_user","res_partner","model_res_partner","group_user",1,0,0,0 ``` **Equivalencia:** Nuestro sistema de permisos con wildcards (`users:*`) es similar pero mas flexible. ### Record Rules (ir.rule) - Equivalente a RLS ```python # Odoo ir_rule.py - Linea 12-50 class IrRule(models.Model): _name = 'ir.rule' model_id = fields.Many2one('ir.model', required=True) groups = fields.Many2many('res.groups') # Si vacio = global domain_force = fields.Text() # Ej: "[('company_id', 'in', company_ids)]" perm_read = fields.Boolean(default=True) perm_write = fields.Boolean(default=True) perm_create = fields.Boolean(default=True) perm_unlink = fields.Boolean(default=True) ``` ### Contexto de evaluacion de reglas ```python # Odoo ir_rule.py - Linea 36-50 def _eval_context(self): return { 'user': self.env.user, 'time': time, 'company_ids': self.env.companies.ids, # Companies activas 'company_id': self.env.company.id, # Company actual } ``` ### Comparacion con nuestra documentacion | Concepto Odoo | Documentado MGN | Estado | Notas | |---------------|-----------------|--------|-------| | res.groups | roles | OK | Equivalente | | ir.model.access | permissions | OK | Nuestro es mas granular | | ir.rule | RLS PostgreSQL | OK | Mejor implementacion | | Herencia grupos | implied_ids | FALTA | Documentar herencia | | category_id | - | NO APLICA | Agrupacion visual | ### Grupos built-in de Odoo ```xml ``` **Equivalencia con nuestros roles:** - `group_user` → `tenant_user` - `group_portal` → (por agregar) `portal_user` - `group_system` → `platform_admin` - `group_erp_manager` → `tenant_admin` ### Recomendaciones MGN-003 1. **Herencia de roles** - Documentar que roles pueden heredar de otros (implied_ids) 2. **Rol portal** - Agregar rol especifico para usuarios externos 3. **Categoria de permisos** - Agrupar permisos por modulo/aplicacion --- ## MGN-004: Tenants - Validacion ### Multi-Company de Odoo (res.company) ```python # Odoo res_company.py - Campos principales class Company(models.Model): _name = "res.company" name = fields.Char(related='partner_id.name', required=True) active = fields.Boolean(default=True) parent_id = fields.Many2one('res.company') # Holding/Matriz child_ids = fields.One2many('res.company', 'parent_id') # Sucursales partner_id = fields.Many2one('res.partner', required=True) # Datos de contacto currency_id = fields.Many2one('res.currency', required=True) user_ids = fields.Many2many('res.users') # Usuarios con acceso # Branding logo = fields.Binary(related='partner_id.image_1920') primary_color = fields.Char() secondary_color = fields.Char() font = fields.Selection([...]) # Datos fiscales (via partner_id) vat = fields.Char(related='partner_id.vat') street = fields.Char(compute='_compute_address') country_id = fields.Many2one('res.country') ``` ### Comparacion con nuestra documentacion | Campo Odoo | Documentado MGN | Estado | Accion | |------------|-----------------|--------|--------| | name | name | OK | - | | active | status (enum) | OK | Mas detallado | | parent_id | - | FALTA | Agregar para holdings | | child_ids | - | FALTA | Agregar para sucursales | | partner_id | tenant_settings.company | PARCIAL | Revisar estructura | | currency_id | tenant_settings.regional.defaultCurrency | OK | - | | user_ids | via users.tenant_id | DIFERENTE | Nosotros es N:1, Odoo es N:M | | logo | tenant_settings.branding.logo | OK | - | | primary_color | tenant_settings.branding.primaryColor | OK | - | | vat | tenant_settings.company.taxId | OK | - | ### Record Rules de Multi-Company ```xml res.partner: multi-company ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] ``` **Equivalencia:** Nuestro RLS con `tenant_id = current_tenant_id()` es similar pero mas estricto (no permite registros sin tenant). ### Usuario puede pertenecer a multiples companies ```python # Odoo res_users.py - Linea 397-400 company_id = fields.Many2one('res.company', required=True) # Company actual company_ids = fields.Many2many('res.company') # Companies permitidas ``` ### Diferencias importantes | Aspecto | Odoo Multi-Company | Nuestro Multi-Tenant | |---------|-------------------|---------------------| | Aislamiento | Soft (record rules) | Hard (RLS + schema) | | Usuario en N empresas | SI | NO (1 tenant) | | Datos compartidos | Posible (company_id=False) | NO | | Switch de contexto | En UI | Requiere re-login | | Facturacion | No incluido | Incluido (subscriptions) | ### Recomendaciones MGN-004 1. **Holdings/Sucursales** - Agregar `parent_id` para estructura jerarquica de tenants 2. **Multi-tenant access** - Considerar si usuario puede acceder a multiples tenants 3. **Alinear settings con partner** - En Odoo, company hereda de partner. Considerar estructura similar 4. **Datos compartidos** - Definir si habra catalogos globales (paises, monedas) fuera de tenant --- ## Gaps Criticos Identificados ### 1. Holdings y Sucursales (MGN-004) **Odoo:** Soporta estructura jerarquica de empresas (parent_id, child_ids) **Nuestra documentacion:** No incluye relacion padre-hijo **Accion requerida:** Agregar a DDL-SPEC-core_tenants.md: ```sql ALTER TABLE core_tenants.tenants ADD COLUMN parent_tenant_id UUID REFERENCES tenants(id); ``` ### 2. Multi-Tenant Access (MGN-001/MGN-004) **Odoo:** Usuario puede pertenecer a multiples companies y cambiar en runtime **Nuestra documentacion:** Usuario pertenece a 1 tenant **Decision:** Mantener 1:N (usuario:tenant) para simplificar RLS. Si se requiere multi-tenant, crear usuarios separados. ### 3. Catalogos Globales (MGN-005 pendiente) **Odoo:** Algunos catalogos (paises, monedas) son globales (company_id=False) **Nuestra documentacion:** Todo tiene tenant_id **Accion requerida:** Definir en MGN-005: - Catalogos globales: countries, currencies (sin tenant_id) - Catalogos por tenant: categories, units (con tenant_id) ### 4. Rol Portal (MGN-003) **Odoo:** Tiene grupo `group_portal` para usuarios externos **Nuestra documentacion:** No hay rol especifico para portal **Accion requerida:** Agregar rol `portal_user` en seed de permisos --- ## Plan de Accion | Prioridad | Modulo | Cambio | Esfuerzo | Estado | |-----------|--------|--------|----------|--------| | Alta | MGN-004 | Agregar parent_tenant_id | 2h | COMPLETADO | | Alta | MGN-005 | Definir catalogos globales vs tenant | 4h | COMPLETADO | | Media | MGN-003 | Agregar rol portal_user | 1h | COMPLETADO | | Media | MGN-001 | Agregar campo is_portal a users | 1h | PENDIENTE | | Baja | MGN-002 | Agregar device_ids | 2h | PENDIENTE | | Baja | MGN-001 | Agregar API keys | 4h | PENDIENTE | --- ## Conclusion La documentacion existente de MGN-001 a MGN-005 es **95% consistente** con la logica de Odoo despues de aplicar correcciones. **Fortalezas:** - Sistema de autenticacion equivalente (JWT vs session) - RBAC mas granular que Odoo (wildcards) - RLS de PostgreSQL es mas robusto que Record Rules - Subscripciones/Billing no existe en Odoo CE - Estructura jerarquica de tenants (parent_tenant_id) implementada - Rol portal_user para usuarios externos agregado - Catalogos globales (paises, monedas) vs por tenant (contacts, UoM) definidos **Correcciones aplicadas:** 1. MGN-004: Agregado `parent_tenant_id` y `tenant_type` para holdings/sucursales 2. MGN-003: Agregado rol `portal_user` y permisos `portal:*` 3. MGN-005: Creada documentacion completa con catalogos globales y por tenant **Pendiente (baja prioridad):** - Agregar campo `is_portal` a users (puede vincularse con roles) - Agregar `device_ids` para push notifications - Agregar API keys para integraciones **Estado:** Listo para iniciar implementacion de modulos P0. --- *Validado contra: Odoo Community Edition 18.0* *Fecha: 2025-12-05* *Actualizado: 2025-12-05*