392 lines
14 KiB
Markdown
392 lines
14 KiB
Markdown
# 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
|
|
<!-- base_groups.xml -->
|
|
<record id="group_user" model="res.groups"> <!-- Usuario interno -->
|
|
<record id="group_portal" model="res.groups"> <!-- Usuario portal -->
|
|
<record id="group_public" model="res.groups"> <!-- Usuario publico -->
|
|
<record id="group_system" model="res.groups"> <!-- Admin tecnico -->
|
|
<record id="group_erp_manager" model="res.groups"> <!-- Settings admin -->
|
|
```
|
|
|
|
**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
|
|
<!-- Odoo: Filtro automatico por company -->
|
|
<record id="res_partner_rule" model="ir.rule">
|
|
<field name="name">res.partner: multi-company</field>
|
|
<field name="model_id" ref="base.model_res_partner"/>
|
|
<field name="domain_force">
|
|
['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
|
|
</field>
|
|
</record>
|
|
```
|
|
|
|
**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*
|