752 lines
20 KiB
Markdown
752 lines
20 KiB
Markdown
# Análisis del Módulo Base de Odoo
|
|
|
|
**Módulo:** base
|
|
**Versión Odoo:** 18.0 Community
|
|
**Prioridad:** P0 (Crítico)
|
|
**Mapeo MGN:** MGN-001 (Fundamentos), MGN-002 (Empresas), MGN-003 (Catálogos)
|
|
|
|
---
|
|
|
|
## Descripción del Módulo
|
|
|
|
El módulo `base` de Odoo es el **módulo fundacional** que todos los demás módulos requieren. Proporciona:
|
|
- Sistema de usuarios y autenticación
|
|
- Gestión de empresas (multi-company)
|
|
- Catálogos maestros (países, monedas, UdM)
|
|
- Sistema de partners (clientes, proveedores, empleados, etc.)
|
|
- Gestión de permisos (grupos, reglas de acceso)
|
|
- Infraestructura base del ORM
|
|
|
|
Es el equivalente al "core" de un ERP.
|
|
|
|
---
|
|
|
|
## Modelos de Datos Principales
|
|
|
|
### 1. res.users (Usuarios)
|
|
|
|
**Tabla:** `res_users`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
login = fields.Char(required=True) # Email/username
|
|
password = fields.Char() # Hash bcrypt
|
|
partner_id = fields.Many2one('res.partner') # Vinculado a partner
|
|
company_id = fields.Many2one('res.company') # Empresa principal
|
|
company_ids = fields.Many2many('res.company') # Empresas permitidas
|
|
groups_id = fields.Many2many('res.groups') # Grupos de permisos
|
|
active = fields.Boolean(default=True) # Usuario activo/inactivo
|
|
share = fields.Boolean() # Usuario portal (limitado)
|
|
```
|
|
|
|
**Relaciones:**
|
|
- `1:1` con `res.partner` (cada usuario es un partner)
|
|
- `N:M` con `res.groups` (un usuario tiene múltiples grupos)
|
|
- `N:1` con `res.company` (empresa principal)
|
|
- `N:M` con `res.company` (empresas permitidas)
|
|
|
|
**Funciones destacables:**
|
|
- `authenticate()` - Autenticación de usuario
|
|
- `check_credentials()` - Validación de password
|
|
- `change_password()` - Cambio de contraseña
|
|
- `has_group()` - Verificar si usuario pertenece a grupo
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Replicar estructura en `auth.users`
|
|
- Añadir `tenant_id` para multi-tenancy
|
|
- JWT en lugar de sesiones Odoo
|
|
|
|
---
|
|
|
|
### 2. res.groups (Grupos de Permisos)
|
|
|
|
**Tabla:** `res_groups`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True, translate=True)
|
|
category_id = fields.Many2one('ir.module.category') # Categoría del grupo
|
|
implied_ids = fields.Many2many('res.groups') # Grupos heredados
|
|
users = fields.Many2many('res.users') # Usuarios del grupo
|
|
model_access = fields.One2many('ir.model.access') # Permisos de modelos
|
|
rule_groups = fields.Many2many('ir.rule') # Record rules
|
|
```
|
|
|
|
**Patrón de herencia de grupos:**
|
|
```
|
|
Grupo "User: All Documents"
|
|
↓ implica
|
|
Grupo "User: Own Documents Only"
|
|
```
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Sistema RBAC basado en grupos
|
|
- Grupos jerárquicos con herencia
|
|
- Implementar en `auth.roles` + `auth.role_hierarchy`
|
|
|
|
---
|
|
|
|
### 3. ir.model.access (Permisos de Modelo)
|
|
|
|
**Tabla:** `ir_model_access`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True)
|
|
model_id = fields.Many2one('ir.model', required=True) # Modelo protegido
|
|
group_id = fields.Many2one('res.groups') # Grupo (null = todos)
|
|
perm_read = fields.Boolean(default=False) # Permiso READ
|
|
perm_write = fields.Boolean(default=False) # Permiso WRITE
|
|
perm_create = fields.Boolean(default=False) # Permiso CREATE
|
|
perm_unlink = fields.Boolean(default=False) # Permiso DELETE
|
|
```
|
|
|
|
**Ejemplo:**
|
|
```csv
|
|
# ir.model.access.csv
|
|
"access_sale_order_salesman","sale.order salesman","model_sale_order","sales_team.group_sale_salesman",1,1,1,0
|
|
# Grupo 'salesman' puede READ, WRITE, CREATE pero NO DELETE sale.order
|
|
```
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Implementar en `auth.model_permissions`
|
|
- Permisos CRUD por modelo y rol
|
|
- Tabla: `role_id`, `resource`, `read`, `write`, `create`, `delete`
|
|
|
|
---
|
|
|
|
### 4. ir.rule (Record Rules - RLS)
|
|
|
|
**Tabla:** `ir_rule`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True)
|
|
model_id = fields.Many2one('ir.model', required=True)
|
|
groups = fields.Many2many('res.groups') # Grupos aplicables
|
|
domain_force = fields.Text(required=True) # Dominio (filtro)
|
|
perm_read = fields.Boolean(default=True)
|
|
perm_write = fields.Boolean(default=True)
|
|
perm_create = fields.Boolean(default=True)
|
|
perm_unlink = fields.Boolean(default=True)
|
|
```
|
|
|
|
**Ejemplo:**
|
|
```python
|
|
# Record rule: "Sale Order: See own orders"
|
|
domain_force = [('user_id', '=', user.id)]
|
|
# Un vendedor solo ve sus propias órdenes de venta
|
|
```
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Implementar con PostgreSQL RLS (Row Level Security)
|
|
- Políticas por tenant, por usuario, por rol
|
|
- Ejemplo: `CREATE POLICY tenant_isolation ON sales.orders USING (tenant_id = current_setting('app.tenant_id')::int);`
|
|
|
|
---
|
|
|
|
### 5. res.company (Empresas)
|
|
|
|
**Tabla:** `res_company`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True)
|
|
partner_id = fields.Many2one('res.partner') # Partner vinculado
|
|
currency_id = fields.Many2one('res.currency') # Moneda principal
|
|
logo = fields.Binary()
|
|
email = fields.Char()
|
|
phone = fields.Char()
|
|
parent_id = fields.Many2one('res.company') # Empresa padre (holdings)
|
|
child_ids = fields.One2many('res.company') # Subsidiarias
|
|
```
|
|
|
|
**Aplicabilidad MGN-002:** ⭐⭐⭐⭐⭐
|
|
- Implementar en `core.companies`
|
|
- Soporte para holdings (parent_id)
|
|
- Vinculado a `tenant_id` en multi-tenancy
|
|
|
|
---
|
|
|
|
### 6. res.partner (Partners - Contactos Universales)
|
|
|
|
**Tabla:** `res_partner`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True)
|
|
email = fields.Char()
|
|
phone = fields.Char()
|
|
mobile = fields.Char()
|
|
street = fields.Char()
|
|
city = fields.Char()
|
|
state_id = fields.Many2one('res.country.state')
|
|
country_id = fields.Many2one('res.country')
|
|
zip = fields.Char()
|
|
vat = fields.Char() # Tax ID / RFC
|
|
is_company = fields.Boolean() # ¿Es empresa o persona?
|
|
customer_rank = fields.Integer() # ¿Es cliente?
|
|
supplier_rank = fields.Integer() # ¿Es proveedor?
|
|
employee = fields.Boolean() # ¿Es empleado?
|
|
user_id = fields.Many2one('res.users') # Usuario vinculado
|
|
parent_id = fields.Many2one('res.partner') # Empresa padre
|
|
child_ids = fields.One2many('res.partner') # Contactos hijos
|
|
```
|
|
|
|
**Patrón Universal de Partner:**
|
|
Odoo usa `res.partner` para:
|
|
- Clientes
|
|
- Proveedores
|
|
- Empleados
|
|
- Empresas
|
|
- Contactos de empresas
|
|
- Direcciones de entrega
|
|
|
|
**Aplicabilidad MGN-003:** ⭐⭐⭐⭐
|
|
- Considerar tabla universal `core.partners` o separar en `customers`, `vendors`, `contacts`
|
|
- Ventaja Odoo: un solo modelo reduce duplicación
|
|
- Desventaja: tabla grande, queries complejas
|
|
- **Recomendación:** Adoptar patrón Odoo pero con vistas SQL por tipo
|
|
|
|
---
|
|
|
|
### 7. res.currency (Monedas)
|
|
|
|
**Tabla:** `res_currency`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True) # Código ISO (USD, MXN, EUR)
|
|
symbol = fields.Char(required=True) # $, €, £
|
|
rate = fields.Float(digits=(12, 6)) # Tasa de cambio
|
|
rounding = fields.Float(digits=(12, 6)) # Redondeo (0.01)
|
|
active = fields.Boolean(default=True)
|
|
```
|
|
|
|
**Aplicabilidad MGN-003:** ⭐⭐⭐⭐⭐
|
|
- Catálogo maestro esencial
|
|
- Implementar en `core.currencies`
|
|
- Tabla de tasas de cambio históricas: `core.currency_rates`
|
|
|
|
---
|
|
|
|
### 8. res.country (Países)
|
|
|
|
**Tabla:** `res_country`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True, translate=True)
|
|
code = fields.Char(required=True, size=2) # ISO 3166-1 alpha-2
|
|
phone_code = fields.Integer() # Código telefónico
|
|
currency_id = fields.Many2one('res.currency')
|
|
state_ids = fields.One2many('res.country.state') # Estados/provincias
|
|
```
|
|
|
|
**Aplicabilidad MGN-003:** ⭐⭐⭐⭐⭐
|
|
- Catálogo maestro
|
|
- Incluir en `core.countries`
|
|
- Seed data con países ISO
|
|
|
|
---
|
|
|
|
### 9. uom.uom (Unidades de Medida)
|
|
|
|
**Tabla:** `uom_uom`
|
|
|
|
**Campos clave:**
|
|
```python
|
|
name = fields.Char(required=True)
|
|
category_id = fields.Many2one('uom.category') # Categoría (Length, Weight, Time)
|
|
factor = fields.Float(required=True) # Factor de conversión
|
|
uom_type = fields.Selection([
|
|
('bigger', 'Bigger than reference'),
|
|
('reference', 'Reference Unit'),
|
|
('smaller', 'Smaller than reference')
|
|
])
|
|
rounding = fields.Float(default=0.01)
|
|
```
|
|
|
|
**Ejemplo de conversión:**
|
|
```
|
|
Categoría: Weight
|
|
- kg (reference, factor=1.0)
|
|
- ton (bigger, factor=1000.0) # 1 ton = 1000 kg
|
|
- g (smaller, factor=0.001) # 1 g = 0.001 kg
|
|
```
|
|
|
|
**Aplicabilidad MGN-003:** ⭐⭐⭐⭐⭐
|
|
- Esencial para inventario y compras
|
|
- Implementar en `core.units_of_measure` + `core.uom_categories`
|
|
|
|
---
|
|
|
|
## Lógica de Negocio Destacable
|
|
|
|
### 1. Sistema de Autenticación
|
|
|
|
**Archivo:** `odoo/addons/base/models/res_users.py`
|
|
|
|
```python
|
|
def _check_credentials(self, password, user_agent_env):
|
|
""" Check password and raise if invalid """
|
|
assert password
|
|
self.env.cr.execute(
|
|
"SELECT COALESCE(password, '') FROM res_users WHERE id=%s",
|
|
[self.env.uid]
|
|
)
|
|
[hashed] = self.env.cr.fetchone()
|
|
valid, replacement = self._crypt_context().verify_and_update(password, hashed)
|
|
if replacement is not None:
|
|
self._set_encrypted_password(self.env.uid, replacement)
|
|
if not valid:
|
|
raise AccessDenied()
|
|
```
|
|
|
|
**Patrones observados:**
|
|
- Hash de password con bcrypt
|
|
- Verificación segura con `verify_and_update`
|
|
- Actualización automática de hash si algoritmo cambió
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Usar bcrypt en Node.js
|
|
- JWT para tokens en lugar de sesiones
|
|
- Refresh tokens
|
|
|
|
---
|
|
|
|
### 2. Multi-Company Access Control
|
|
|
|
**Archivo:** `odoo/addons/base/models/res_users.py`
|
|
|
|
```python
|
|
@api.depends('groups_id')
|
|
def _compute_share(self):
|
|
for user in self:
|
|
user.share = not any(group.category_id.xml_id == 'base.module_category_user_type'
|
|
for group in user.groups_id)
|
|
|
|
def _get_company(self):
|
|
return self.env.company
|
|
|
|
company_id = fields.Many2one('res.company', default=_get_company, required=True)
|
|
company_ids = fields.Many2many('res.company', compute='_compute_company_ids', store=True)
|
|
```
|
|
|
|
**Patrones observados:**
|
|
- Usuario tiene una empresa principal (`company_id`)
|
|
- Usuario puede acceder a múltiples empresas (`company_ids`)
|
|
- Record rules filtran registros por `company_id`
|
|
|
|
**Aplicabilidad MGN-002:** ⭐⭐⭐⭐
|
|
- En multi-tenancy schema-based: cada tenant = 1 schema
|
|
- Dentro de un tenant, soporte multi-company con `company_id`
|
|
- RLS policies filtran por tenant y company
|
|
|
|
---
|
|
|
|
### 3. Sistema de Permisos en Cascada
|
|
|
|
**Lógica:**
|
|
1. Se evalúan `ir.model.access` (permisos de modelo por grupo)
|
|
2. Si el usuario tiene permiso de modelo, se evalúan `ir.rule` (record rules)
|
|
3. Las record rules se combinan con OR entre grupos del usuario
|
|
4. Si no hay record rules, el usuario ve todos los registros (si tiene permiso de modelo)
|
|
|
|
**Ejemplo:**
|
|
```python
|
|
# Usuario pertenece a grupos: 'Sales User', 'Project Manager'
|
|
|
|
# ir.model.access
|
|
# 'Sales User' → puede READ sale.order
|
|
# 'Project Manager' → puede READ sale.order
|
|
|
|
# ir.rule (Sales User)
|
|
domain = [('user_id', '=', user.id)] # Solo sus propias órdenes
|
|
|
|
# ir.rule (Project Manager)
|
|
domain = [('project_id.user_id', '=', user.id)] # Órdenes de sus proyectos
|
|
|
|
# Resultado: El usuario ve registros que cumplen:
|
|
# (user_id = X) OR (project_id.user_id = X)
|
|
```
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐
|
|
- Implementar con PostgreSQL RLS
|
|
- Múltiples policies se combinan con OR
|
|
- Granularidad: modelo → record → field
|
|
|
|
---
|
|
|
|
## Patrones Arquitectónicos Observados
|
|
|
|
### 1. Partner como Entidad Universal
|
|
|
|
**Patrón:**
|
|
```
|
|
res.partner (Tabla universal para contactos)
|
|
↓
|
|
Usado por:
|
|
- res.users (usuario es un partner)
|
|
- sale.order (customer_id → res.partner)
|
|
- purchase.order (vendor_id → res.partner)
|
|
- hr.employee (employee_id → res.partner)
|
|
- res.company (company es un partner)
|
|
```
|
|
|
|
**Ventajas:**
|
|
- Un solo lugar para datos de contacto
|
|
- Evita duplicación de direcciones, emails, teléfonos
|
|
- Fácil vincular cualquier módulo a partners
|
|
|
|
**Desventajas:**
|
|
- Tabla muy grande
|
|
- Queries complejas (filtrar por `customer_rank`, `supplier_rank`, etc.)
|
|
|
|
**Recomendación MGN-003:** Adoptar con vistas SQL
|
|
```sql
|
|
CREATE VIEW customers AS SELECT * FROM core.partners WHERE customer_rank > 0;
|
|
CREATE VIEW vendors AS SELECT * FROM core.partners WHERE supplier_rank > 0;
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Herencia de Grupos
|
|
|
|
**Patrón:**
|
|
```python
|
|
groups_id = fields.Many2many('res.groups')
|
|
implied_ids = fields.Many2many('res.groups') # Grupos heredados
|
|
|
|
# Grupo "Sales Manager" implica "Sales User"
|
|
# Si asignas "Sales Manager" a un usuario, automáticamente tiene "Sales User"
|
|
```
|
|
|
|
**Aplicabilidad MGN-001:** ⭐⭐⭐⭐
|
|
- Implementar tabla `auth.role_hierarchy`
|
|
- Consultas recursivas para obtener roles heredados
|
|
|
|
---
|
|
|
|
### 3. Soft Delete (archive)
|
|
|
|
**Patrón:**
|
|
```python
|
|
active = fields.Boolean(default=True)
|
|
|
|
def toggle_active(self):
|
|
for record in self:
|
|
record.active = not record.active
|
|
```
|
|
|
|
- En lugar de DELETE, se hace `UPDATE active = false`
|
|
- Permite recuperar registros borrados
|
|
- Queries por defecto filtran `active = true`
|
|
|
|
**Aplicabilidad MGN (todos):** ⭐⭐⭐⭐⭐
|
|
- Implementar campo `active` en todas las tablas
|
|
- Soft delete en lugar de hard delete
|
|
|
|
---
|
|
|
|
## APIs y Endpoints Estándar
|
|
|
|
Odoo usa **XML-RPC** y **JSON-RPC** para APIs. No es RESTful puro.
|
|
|
|
**Endpoints típicos:**
|
|
```
|
|
/web/session/authenticate # Login
|
|
/web/dataset/call_kw # Llamar métodos de modelos
|
|
/web/dataset/search_read # Búsqueda + lectura
|
|
```
|
|
|
|
**Métodos estándar de modelo:**
|
|
- `create(vals)` - Crear registro
|
|
- `write(vals)` - Actualizar registro
|
|
- `unlink()` - Eliminar registro
|
|
- `read(fields)` - Leer campos
|
|
- `search(domain)` - Buscar registros
|
|
- `search_read(domain, fields)` - Buscar + leer
|
|
|
|
**Aplicabilidad MGN:** ❌ No copiar
|
|
- ERP Genérico usará REST APIs (ADR-008)
|
|
- Endpoints estándar: `GET /api/v1/users`, `POST /api/v1/users`, etc.
|
|
|
|
---
|
|
|
|
## Integraciones con Otros Módulos
|
|
|
|
**base** es requerido por TODOS los módulos Odoo.
|
|
|
|
**Dependencias inversas (quién usa base):**
|
|
- `sale` usa `res.partner` (clientes)
|
|
- `purchase` usa `res.partner` (proveedores)
|
|
- `account` usa `res.currency`, `res.company`
|
|
- `hr` usa `res.partner` (empleados)
|
|
- `stock` usa `uom.uom` (unidades de medida)
|
|
- `project` usa `res.users` (asignación de tareas)
|
|
|
|
---
|
|
|
|
## Funcionalidades Relevantes para ERP Genérico
|
|
|
|
### Funcionalidades P0 (Críticas)
|
|
|
|
1. **Autenticación de usuarios**
|
|
- Login con email/password
|
|
- Hash bcrypt
|
|
- Sesiones/tokens
|
|
|
|
2. **Gestión de usuarios**
|
|
- Crear, editar, desactivar usuarios
|
|
- Reseteo de contraseña
|
|
- Perfil de usuario vinculado a partner
|
|
|
|
3. **Sistema RBAC (Roles & Permissions)**
|
|
- Grupos de permisos
|
|
- Herencia de grupos
|
|
- Permisos CRUD por modelo
|
|
- Record rules (RLS)
|
|
|
|
4. **Multi-company**
|
|
- Gestión de múltiples empresas
|
|
- Usuarios con acceso a múltiples empresas
|
|
- Cambio de empresa en contexto
|
|
|
|
5. **Catálogos maestros**
|
|
- Países y estados
|
|
- Monedas y tasas de cambio
|
|
- Unidades de medida
|
|
- Partners (clientes, proveedores, contactos)
|
|
|
|
### Funcionalidades P1 (Importantes)
|
|
|
|
6. **Portal de usuarios externos**
|
|
- Usuarios tipo "portal" (share=True)
|
|
- Acceso limitado
|
|
- Vistas específicas
|
|
|
|
7. **Gestión de direcciones**
|
|
- Direcciones de facturación/entrega
|
|
- Vinculadas a partners
|
|
|
|
8. **Configuración de empresa**
|
|
- Logo, datos fiscales
|
|
- Moneda principal
|
|
- Parámetros del sistema
|
|
|
|
### Funcionalidades P2 (Opcionales)
|
|
|
|
9. **Multi-idioma**
|
|
- Traducciones de catálogos
|
|
- UI multiidioma
|
|
|
|
10. **Gestión de bancos**
|
|
- Cuentas bancarias de empresa
|
|
- Cuentas bancarias de partners
|
|
|
|
---
|
|
|
|
## Mapeo a Módulos MGN
|
|
|
|
### MGN-001: Fundamentos
|
|
|
|
**De base:**
|
|
- `res.users` → `auth.users`
|
|
- `res.groups` → `auth.roles`
|
|
- `ir.model.access` → `auth.model_permissions`
|
|
- `ir.rule` → PostgreSQL RLS policies
|
|
|
|
**Funcionalidades:**
|
|
- RF-AUTH-001: Autenticación JWT
|
|
- RF-AUTH-002: Gestión de usuarios
|
|
- RF-AUTH-003: Sistema RBAC
|
|
- RF-AUTH-004: Multi-tenancy
|
|
|
|
### MGN-002: Empresas y Organizaciones
|
|
|
|
**De base:**
|
|
- `res.company` → `core.companies`
|
|
|
|
**Funcionalidades:**
|
|
- RF-COMP-001: Gestión de empresas
|
|
- RF-COMP-002: Multi-company
|
|
- RF-COMP-003: Configuración de empresa
|
|
|
|
### MGN-003: Catálogos Maestros
|
|
|
|
**De base:**
|
|
- `res.partner` → `core.partners`
|
|
- `res.currency` → `core.currencies`
|
|
- `res.country` → `core.countries`
|
|
- `res.country.state` → `core.states`
|
|
- `uom.uom` → `core.units_of_measure`
|
|
- `uom.category` → `core.uom_categories`
|
|
|
|
**Funcionalidades:**
|
|
- RF-CAT-001: Gestión de partners (clientes, proveedores, contactos)
|
|
- RF-CAT-002: Catálogo de monedas y tasas
|
|
- RF-CAT-003: Catálogo de países y estados
|
|
- RF-CAT-004: Catálogo de unidades de medida
|
|
|
|
---
|
|
|
|
## Recomendaciones de Implementación
|
|
|
|
### 1. Sistema de Autenticación
|
|
|
|
**Odoo usa:** Sesiones con cookies
|
|
|
|
**ERP Genérico debe usar:** JWT (ADR-001)
|
|
```typescript
|
|
// Login endpoint
|
|
POST /api/v1/auth/login
|
|
Request: { email, password }
|
|
Response: {
|
|
accessToken: "jwt...",
|
|
refreshToken: "jwt...",
|
|
user: { id, email, name, roles }
|
|
}
|
|
```
|
|
|
|
### 2. Sistema RBAC
|
|
|
|
**Implementación sugerida:**
|
|
|
|
**Tablas:**
|
|
```sql
|
|
-- auth.roles (equivalente a res.groups)
|
|
CREATE TABLE auth.roles (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
category VARCHAR(50),
|
|
parent_role_id INT REFERENCES auth.roles(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- auth.user_roles (N:M)
|
|
CREATE TABLE auth.user_roles (
|
|
user_id INT REFERENCES auth.users(id),
|
|
role_id INT REFERENCES auth.roles(id),
|
|
PRIMARY KEY (user_id, role_id)
|
|
);
|
|
|
|
-- auth.model_permissions (equivalente a ir.model.access)
|
|
CREATE TABLE auth.model_permissions (
|
|
id SERIAL PRIMARY KEY,
|
|
role_id INT REFERENCES auth.roles(id),
|
|
resource VARCHAR(100) NOT NULL, -- 'users', 'products', 'orders', etc.
|
|
can_read BOOLEAN DEFAULT FALSE,
|
|
can_write BOOLEAN DEFAULT FALSE,
|
|
can_create BOOLEAN DEFAULT FALSE,
|
|
can_delete BOOLEAN DEFAULT FALSE,
|
|
UNIQUE (role_id, resource)
|
|
);
|
|
```
|
|
|
|
**RLS (Row Level Security):**
|
|
```sql
|
|
-- Política de aislamiento por tenant
|
|
CREATE POLICY tenant_isolation ON auth.users
|
|
USING (tenant_id = current_setting('app.tenant_id')::int);
|
|
|
|
-- Política: usuarios solo ven su propio registro
|
|
CREATE POLICY own_user_only ON auth.users
|
|
FOR SELECT
|
|
USING (id = current_setting('app.user_id')::int);
|
|
```
|
|
|
|
### 3. Catálogos Maestros
|
|
|
|
**Implementación sugerida:**
|
|
|
|
```typescript
|
|
// Seed data de países (ISO 3166-1)
|
|
// Importar desde https://restcountries.com/
|
|
const countries = [
|
|
{ code: 'MX', name: 'México', phone_code: 52, currency_code: 'MXN' },
|
|
{ code: 'US', name: 'United States', phone_code: 1, currency_code: 'USD' },
|
|
// ...
|
|
];
|
|
|
|
// Seed data de monedas (ISO 4217)
|
|
const currencies = [
|
|
{ code: 'MXN', name: 'Peso Mexicano', symbol: '$', decimals: 2 },
|
|
{ code: 'USD', name: 'US Dollar', symbol: '$', decimals: 2 },
|
|
{ code: 'EUR', name: 'Euro', symbol: '€', decimals: 2 },
|
|
// ...
|
|
];
|
|
```
|
|
|
|
### 4. Partners (Patrón Universal)
|
|
|
|
**Opción 1:** Tabla única (patrón Odoo)
|
|
```sql
|
|
CREATE TABLE core.partners (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INT NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
email VARCHAR(100),
|
|
phone VARCHAR(20),
|
|
is_customer BOOLEAN DEFAULT FALSE,
|
|
is_vendor BOOLEAN DEFAULT FALSE,
|
|
is_employee BOOLEAN DEFAULT FALSE,
|
|
is_company BOOLEAN DEFAULT FALSE,
|
|
parent_id INT REFERENCES core.partners(id),
|
|
-- ... más campos
|
|
);
|
|
|
|
CREATE VIEW customers AS
|
|
SELECT * FROM core.partners WHERE is_customer = TRUE;
|
|
|
|
CREATE VIEW vendors AS
|
|
SELECT * FROM core.partners WHERE is_vendor = TRUE;
|
|
```
|
|
|
|
**Opción 2:** Tablas separadas
|
|
```sql
|
|
CREATE TABLE core.partners (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INT NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
-- ... campos comunes
|
|
);
|
|
|
|
CREATE TABLE sales.customers (
|
|
id INT PRIMARY KEY REFERENCES core.partners(id),
|
|
-- campos específicos de clientes
|
|
);
|
|
|
|
CREATE TABLE purchase.vendors (
|
|
id INT PRIMARY KEY REFERENCES core.partners(id),
|
|
-- campos específicos de proveedores
|
|
);
|
|
```
|
|
|
|
**Recomendación:** Opción 1 (patrón Odoo) por simplicidad
|
|
|
|
---
|
|
|
|
## Conclusión
|
|
|
|
El módulo `base` de Odoo es una referencia excelente para:
|
|
- Sistema de autenticación robusto
|
|
- RBAC granular con herencia de roles
|
|
- Multi-company bien implementado
|
|
- Catálogos maestros completos
|
|
- Patrón de partner universal
|
|
|
|
**Prioridad de implementación en MGN:**
|
|
1. MGN-001 (Fundamentos): Sistema auth, usuarios, RBAC
|
|
2. MGN-003 (Catálogos): Monedas, países, UoM, partners
|
|
3. MGN-002 (Empresas): Multi-company, configuración
|
|
|
|
**Nivel de reutilización:** 90% de los patrones son aplicables
|
|
|
|
---
|
|
|
|
**Fecha:** 2025-11-23
|
|
**Analista:** Architecture-Analyst
|
|
**Estado:** ✅ Análisis completo
|