segunda instancia white-label (clinica2) + fix de orden de menus en instalacion limpia
Infra (todo convive en la misma VPS, mismo addons_path read-only): - infra/clinica2/: odoo.conf (puerto 8070, db clinica2_odoo, data_dir propio), odoo-clinica2.service (sin env vars de WACRM/Supabase/Hermes), nginx-vhost (80 con server_name clinica2.consultoria-as.com + 8880 LAN, /api/odoo -> 8070, /wacrm/ cortado con 403), postinstall.py (brand neutro, menu WACRM vacio, admin con password propio y must_change_password) - docs/segunda-instancia.md: mapa completo de la instalacion, pasos para reproducir (instancia 3+), pendiente del hostname en dashboard del tunel (remotamente administrado), aislamiento de datos escenario B - frontend/public/brand/: logos SVG neutros + favicon para la instancia 2 Fix de menus (aplica a todas las instalaciones limpias, una sola pasada): - menu_skeen_root se mueve a skeen_pacientes (modulo raiz de la jerarquia); en bases existentes se transfirio el ownership del xmlid (ir_model_data) asi actualizar no duplica el menu - menu_skeen_citas (padre) se declara en skeen_citas/views/menus.xml que carga antes que bloqueo/lista_espera; menu_views.xml conserva las entradas con action (cargan despues de cita_views, donde se define la accion) - referencias actualizadas: pacientes(catalogos/recetas), monedero, ventas, inventario, visitas -> skeen_pacientes.menu_skeen_root Verificacion: produccion (skeen_odoo) actualizada sin duplicar menus y API intacta; instalacion limpia clinica2_odoo 9 modulos OK; 15/15 tests end-to-end (brand neutro, login admin, WACRM oculto+403, datos vacios)
This commit is contained in:
91
docs/segunda-instancia.md
Normal file
91
docs/segunda-instancia.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Segunda instancia white-label (sin branding SKEEN)
|
||||||
|
|
||||||
|
Segunda clínica sobre el mismo código y la misma VPS. La marca no vive en el
|
||||||
|
código: sale del parámetro `skeen.clinic.brand` (ver `GET /skeen/frontend/v1/brand`),
|
||||||
|
así que esta y futuras instancias son solo infra + config.
|
||||||
|
|
||||||
|
## Mapa de la instalación
|
||||||
|
|
||||||
|
| Componente | SKEEN (instancia 1) | Clínica 2 (instancia white-label) |
|
||||||
|
|---|---|---|
|
||||||
|
| Odoo HTTP | 8069 | **8070** |
|
||||||
|
| Odoo longpolling | 8072 | **8073** |
|
||||||
|
| Base de datos | `skeen_odoo` | **`clinica2_odoo`** |
|
||||||
|
| Conf | `/root/skeen-odoo/odoo.conf` | **`/root/clinica2-odoo.conf`** |
|
||||||
|
| Systemd | `odoo-skeen.service` | **`odoo-clinica2.service`** |
|
||||||
|
| Frontend | `/var/www/skeen-frontend` | **`/var/www/clinica2-frontend`** |
|
||||||
|
| Nginx | vhost `skeen-crm` (80) | **vhost `clinica2` (80 + 8880)** |
|
||||||
|
| Acceso LAN | http://192.168.10.114 | **http://192.168.10.114:8880** |
|
||||||
|
| Acceso público | skeen.consultoria-as.com | **clinica2.consultoria-as.com (pendiente: dashboard del túnel)** |
|
||||||
|
| WhatsApp (WACRM) | integrado | **oculto + bloqueado (escenario B)** |
|
||||||
|
|
||||||
|
`addons_path` se comparte (read-only): `/root/skeen-odoo/addons`. Un solo
|
||||||
|
código, N bases de datos.
|
||||||
|
|
||||||
|
## Cómo se levantó (orden de pasos)
|
||||||
|
|
||||||
|
Los archivos canónicos están en `infra/clinica2/`:
|
||||||
|
|
||||||
|
1. `odoo.conf` — copia del de SKEEN con `db_name=clinica2_odoo`, `http_port=8070`,
|
||||||
|
`longpolling_port=8073`, `data_dir=/var/lib/odoo-clinica2`.
|
||||||
|
2. Crear la base (el rol `skeen` no tiene CREATEDB; se crea como `postgres`):
|
||||||
|
`CREATE DATABASE clinica2_odoo OWNER skeen ...`
|
||||||
|
3. Instalar módulos limpio (sin demo):
|
||||||
|
`odoo-bin -c /root/clinica2-odoo.conf -d clinica2_odoo --without-demo=all --init=skeen_whatsapp,skeen_chat,skeen_visitas --stop-after-init --no-http`
|
||||||
|
(skeen_whatsapp arrastra citas/pacientes/ventas/pagos/monedero; visitas
|
||||||
|
arrastra inventario).
|
||||||
|
4. `postinstall.py` — brand neutro (`skeen.clinic.brand`), menú WACRM vacío
|
||||||
|
para todos (`skeen.menu_roles`), password de admin propio +
|
||||||
|
`must_change_password`, empresa renombrada.
|
||||||
|
5. `systemctl enable --now odoo-clinica2`.
|
||||||
|
6. `nginx-vhost` → `/etc/nginx/sites-enabled/clinica2` + `nginx -t && reload`.
|
||||||
|
7. Frontend: mismo build de SKEEN (`rsync -a dist/ /var/www/clinica2-frontend/`)
|
||||||
|
más `public/brand/` (logos SVG neutros + favicon, versionados en el repo).
|
||||||
|
|
||||||
|
## Pendiente para acceso público (único paso manual)
|
||||||
|
|
||||||
|
El túnel cloudflared corre con `--token` (remotamente administrado): el
|
||||||
|
`config.yml` local NO aplica. En el dashboard de Cloudflare (Zero Trust →
|
||||||
|
Networks → Tunnels → f2517233…) agregar el ingress:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- hostname: clinica2.consultoria-as.com
|
||||||
|
service: http://localhost:80
|
||||||
|
```
|
||||||
|
|
||||||
|
El vhost nginx ya escucha en el 80 con ese `server_name`; al agregar el
|
||||||
|
hostname el túnel enruta solo. El 8880 queda como acceso LAN permanente.
|
||||||
|
|
||||||
|
## Aislamiento de datos (escenario B: sin WhatsApp)
|
||||||
|
|
||||||
|
- Sin `SUPABASE_URL` / `WACRM_API_*` / `HERMES_BRIDGE_URL` en la unit systemd
|
||||||
|
(el backend respondería "Falta configuración").
|
||||||
|
- Aun así, el backend tiene fallback a `localhost:3000`; por eso el vhost
|
||||||
|
nginx corta `/api/odoo/skeen/frontend/v1/wacrm/` con `return 403;` — doble
|
||||||
|
candado, la instancia 2 no puede leer ni escribir las conversaciones de
|
||||||
|
SKEEN aunque alguien llame la API a mano.
|
||||||
|
- Menú WACRM oculto para todos los roles vía matriz (`/wacrm/*: []`).
|
||||||
|
|
||||||
|
Para activar WhatsApp en la instancia 2 (escenario A) se necesita: número
|
||||||
|
propio de WhatsApp Business + decisión de WACRM multi-clínica (estimar aparte).
|
||||||
|
|
||||||
|
## Fix de menús que esta fase destapó (aplica a TODAS las instancias)
|
||||||
|
|
||||||
|
Las vistas XML referenciaban `skeen_citas.menu_skeen_root`, pero `skeen_citas`
|
||||||
|
depende de `skeen_pacientes`: en instalación limpia de una sola pasada el
|
||||||
|
xmlid aún no existe y la carga falla (la base de SKEEN se construyó por fases
|
||||||
|
y por eso nunca explotó). Se movió el menú raíz a `skeen_pacientes` (módulo
|
||||||
|
raíz de la jerarquía) y todas las referencias apuntan a
|
||||||
|
`skeen_pacientes.menu_skeen_root`. En bases existentes se transfirió el
|
||||||
|
ownership del xmlid (`UPDATE ir_model_data SET module='skeen_pacientes' …`),
|
||||||
|
así actualizar no duplica el menú. Mismo tratamiento para el menú padre de
|
||||||
|
Citas (`skeen_citas/views/menus.xml` carga antes que bloqueos/lista de espera).
|
||||||
|
|
||||||
|
## Operación habitual
|
||||||
|
|
||||||
|
- Logs: `tail -f /tmp/odoo2.log` (instancia 2) vs `/tmp/odoo.log` (SKEEN).
|
||||||
|
- Ambas instancias comparten código: un `git pull` + rebuild afecta el build
|
||||||
|
de ambas solo si se vuelven a copiar los `dist/` (cada uno tiene el suyo).
|
||||||
|
- Para una tercera clínica: repetir pasos 1–7 con `clinica3` (puertos 8074/8075,
|
||||||
|
db `clinica3_odoo`, vhost propio) y setear su brand desde
|
||||||
|
Configuración → Clínica o con `PUT /brand`.
|
||||||
4
frontend/public/brand/favicon.svg
Normal file
4
frontend/public/brand/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||||
|
<rect x="2" y="2" width="44" height="44" rx="10" fill="#1f3a4d"/>
|
||||||
|
<path d="M24 13v22M13 24h22" stroke="#ffffff" stroke-width="8" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 249 B |
5
frontend/public/brand/logo-blanco.svg
Normal file
5
frontend/public/brand/logo-blanco.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 48" width="220" height="48">
|
||||||
|
<rect x="2" y="8" width="32" height="32" rx="8" fill="#ffffff"/>
|
||||||
|
<path d="M18 15v18M9 24h18" stroke="#4f7c9b" stroke-width="6" stroke-linecap="round"/>
|
||||||
|
<text x="42" y="31" font-family="Arial, sans-serif" font-size="20" font-weight="700" fill="#ffffff" letter-spacing="2">CLÍNICA</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 386 B |
5
frontend/public/brand/logo-negro.svg
Normal file
5
frontend/public/brand/logo-negro.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 48" width="220" height="48">
|
||||||
|
<rect x="2" y="8" width="32" height="32" rx="8" fill="#1f3a4d"/>
|
||||||
|
<path d="M18 15v18M9 24h18" stroke="#ffffff" stroke-width="6" stroke-linecap="round"/>
|
||||||
|
<text x="42" y="31" font-family="Arial, sans-serif" font-size="20" font-weight="700" fill="#1f3a4d" letter-spacing="2">CLÍNICA</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 386 B |
47
infra/clinica2/nginx-vhost
Normal file
47
infra/clinica2/nginx-vhost
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Segunda instancia (white-label) — frontend React + API Odoo en puerto 8070.
|
||||||
|
#
|
||||||
|
# Acceso LAN inmediato: http://192.168.10.114:8880
|
||||||
|
# Acceso publico: requiere agregar el hostname en el dashboard del tunel
|
||||||
|
# cloudflared (remotamente administrado, sin config local):
|
||||||
|
# ingress: - hostname: clinica2.consultoria-as.com
|
||||||
|
# service: http://localhost:80
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen 8880;
|
||||||
|
server_name clinica2.consultoria-as.com;
|
||||||
|
|
||||||
|
# Frontend React (mismo build que SKEEN; el brand sale del API /brand)
|
||||||
|
location / {
|
||||||
|
root /var/www/clinica2-frontend;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Odoo API para el frontend
|
||||||
|
location /api/odoo/ {
|
||||||
|
proxy_pass http://127.0.0.1:8070/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backend Odoo (administracion directa, opcional)
|
||||||
|
location /web/ {
|
||||||
|
proxy_pass http://127.0.0.1:8070/web/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Escenario B: la instancia 2 no tiene WhatsApp. Aunque el backend tenga
|
||||||
|
# fallback a localhost:3000, aqui se corta la ruta: 403 fijo, sin excepciones.
|
||||||
|
location /api/odoo/skeen/frontend/v1/wacrm/ {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
infra/clinica2/odoo-clinica2.service
Normal file
19
infra/clinica2/odoo-clinica2.service
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Odoo CLINICA2 (skeen-odoo venv, instancia white-label)
|
||||||
|
After=network.target postgresql.service
|
||||||
|
Wants=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/root/skeen-odoo
|
||||||
|
# Sin SUPABASE_URL / WACRM_API_* / HERMES_BRIDGE_URL a proposito: la instancia
|
||||||
|
# 2 arranca en escenario B (sin WhatsApp). Los endpoints WACRM responden
|
||||||
|
# "Falta configuracion" en vez de tocar la instancia compartida de SKEEN.
|
||||||
|
ExecStart=/root/odoo-venv/bin/python /root/skeen-odoo/odoo-bin -c /root/clinica2-odoo.conf --logfile=/tmp/odoo2.log
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
TimeoutStartSec=120
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
55
infra/clinica2/odoo.conf
Normal file
55
infra/clinica2/odoo.conf
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
[options]
|
||||||
|
addons_path = /root/skeen-odoo/addons,/root/skeen-odoo/odoo/addons
|
||||||
|
admin_passwd = skeen_admin_2026
|
||||||
|
csv_internal_sep = ,
|
||||||
|
data_dir = /var/lib/odoo-clinica2
|
||||||
|
db_host = localhost
|
||||||
|
db_maxconn = 64
|
||||||
|
db_name = clinica2_odoo
|
||||||
|
db_password = skeen_dev_2026
|
||||||
|
db_port = 5432
|
||||||
|
db_sslmode = prefer
|
||||||
|
db_user = skeen
|
||||||
|
demo = {}
|
||||||
|
email_from = False
|
||||||
|
http_enable = True
|
||||||
|
http_interface =
|
||||||
|
http_port = 8070
|
||||||
|
import_partial =
|
||||||
|
limit_memory_hard = 2684354560
|
||||||
|
limit_memory_soft = 2147483648
|
||||||
|
limit_request = 8192
|
||||||
|
limit_time_cpu = 60
|
||||||
|
limit_time_real = 120
|
||||||
|
limit_time_real_cron = -1
|
||||||
|
list_db = True
|
||||||
|
log_db = False
|
||||||
|
log_db_level = warning
|
||||||
|
log_handler = :INFO
|
||||||
|
log_level = info
|
||||||
|
logfile =
|
||||||
|
longpolling_port = 8073
|
||||||
|
max_cron_threads = 2
|
||||||
|
osv_memory_age_limit = False
|
||||||
|
osv_memory_count_limit = False
|
||||||
|
pg_path =
|
||||||
|
pidfile =
|
||||||
|
proxy_mode = True
|
||||||
|
reportgz = False
|
||||||
|
screencasts =
|
||||||
|
screenshots = /tmp/odoo_screenshots_clinica2
|
||||||
|
server_wide_modules = base,web
|
||||||
|
smtp_password = False
|
||||||
|
smtp_port = 25
|
||||||
|
smtp_server = localhost
|
||||||
|
smtp_ssl = False
|
||||||
|
smtp_ssl_certificate_filename = False
|
||||||
|
smtp_ssl_private_key_filename = False
|
||||||
|
smtp_user = False
|
||||||
|
test_file =
|
||||||
|
test_tags = None
|
||||||
|
translate_modules = ['all']
|
||||||
|
unaccent = False
|
||||||
|
upgrade_path =
|
||||||
|
without_demo = False
|
||||||
|
workers = 0
|
||||||
66
infra/clinica2/postinstall.py
Normal file
66
infra/clinica2/postinstall.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Post-instalación de la instancia clinica2_odoo (white-label, sin WACRM).
|
||||||
|
|
||||||
|
- Brand neutro (skeen.clinic.brand) apuntando a logos genericos servidos por
|
||||||
|
el nginx de la instancia (no SKEEN).
|
||||||
|
- skeen.menu_roles con /wacrm/* en lista vacia: modulo WhatsApp oculto para
|
||||||
|
todos (escenario B: la clinica nueva aun no tiene numero de WhatsApp).
|
||||||
|
- Admin con password distinto a la instancia SKEEN y must_change_password.
|
||||||
|
- Empresa principal renombrada al nombre neutro.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, '/root/skeen-odoo')
|
||||||
|
|
||||||
|
import odoo
|
||||||
|
from odoo.tools import config
|
||||||
|
|
||||||
|
config.parse_config([
|
||||||
|
'-c', '/root/clinica2-odoo.conf',
|
||||||
|
'--no-http',
|
||||||
|
'--log-level', 'error',
|
||||||
|
])
|
||||||
|
|
||||||
|
odoo.service.server.load_server_wide_modules()
|
||||||
|
registry = odoo.registry(config['db_name'])
|
||||||
|
|
||||||
|
BRAND = {
|
||||||
|
'nombre': 'Clínica',
|
||||||
|
'logo_claro': '/brand/logo-blanco.svg',
|
||||||
|
'logo_oscuro': '/brand/logo-negro.svg',
|
||||||
|
'pie': 'Sistema de gestión clínica',
|
||||||
|
}
|
||||||
|
|
||||||
|
ADMIN_PASSWORD = 'Clinica2Admin2026!'
|
||||||
|
|
||||||
|
with registry.cursor() as cr:
|
||||||
|
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||||
|
ICP = env['ir.config_parameter'].sudo()
|
||||||
|
|
||||||
|
# 1) Brand neutro
|
||||||
|
ICP.set_param('skeen.clinic.brand', json.dumps(BRAND, ensure_ascii=False))
|
||||||
|
print('brand:', ICP.get_param('skeen.clinic.brand'))
|
||||||
|
|
||||||
|
# 2) Ocultar WACRM para todos (lista vacia = ningun rol)
|
||||||
|
roles = json.loads(ICP.get_param('skeen.menu_roles') or '{}')
|
||||||
|
roles['/wacrm/messages'] = []
|
||||||
|
roles['/wacrm/leads'] = []
|
||||||
|
ICP.set_param('skeen.menu_roles', json.dumps(roles, ensure_ascii=False))
|
||||||
|
print('menu_roles wacrm:', roles.get('/wacrm/messages'), roles.get('/wacrm/leads'))
|
||||||
|
|
||||||
|
# 3) Admin: password propio de la instancia + cambio forzado
|
||||||
|
admin = env['skeen.frontend.user'].sudo().search([('login', '=', 'admin')], limit=1)
|
||||||
|
admin.set_password(ADMIN_PASSWORD)
|
||||||
|
admin.write({'must_change_password': True})
|
||||||
|
print('admin:', admin.login, 'must_change_password:', admin.must_change_password)
|
||||||
|
|
||||||
|
# 4) Empresa principal renombrada (el backend Odoo la muestra)
|
||||||
|
company = env['res.company'].sudo().search([], limit=1, order='id')
|
||||||
|
if company:
|
||||||
|
company.write({'name': 'Clínica'})
|
||||||
|
print('company:', company.name)
|
||||||
|
|
||||||
|
cr.commit()
|
||||||
|
print('POST-INSTALL OK')
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/sequences.xml',
|
'data/sequences.xml',
|
||||||
|
# Menú padre primero: las vistas de abajo cuelgan de él.
|
||||||
|
'views/menus.xml',
|
||||||
'views/cita_views.xml',
|
'views/cita_views.xml',
|
||||||
'views/bloqueo_views.xml',
|
'views/bloqueo_views.xml',
|
||||||
'views/lista_espera_views.xml',
|
'views/lista_espera_views.xml',
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Menú principal -->
|
<!-- Menú Citas y sus entradas con acción. El padre (menu_skeen_citas) se
|
||||||
<menuitem id="menu_skeen_root" name="SKEEN" sequence="10"/>
|
declara en menus.xml, que carga antes que las vistas que lo usan. -->
|
||||||
|
|
||||||
<!-- Menú Citas -->
|
|
||||||
<menuitem id="menu_skeen_citas" name="Citas" parent="menu_skeen_root" sequence="10"/>
|
|
||||||
<menuitem id="menu_skeen_citas_list" name="Todas las Citas" parent="menu_skeen_citas"
|
<menuitem id="menu_skeen_citas_list" name="Todas las Citas" parent="menu_skeen_citas"
|
||||||
action="action_skeen_cita_tree" sequence="10"/>
|
action="action_skeen_cita_tree" sequence="10"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
10
odoo-addons/skeen_citas/views/menus.xml
Normal file
10
odoo-addons/skeen_citas/views/menus.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Menú padre de Citas, declarado ANTES que las vistas que cuelgan de él
|
||||||
|
(bloqueos, lista de espera). Sin action a propósito: las entradas con
|
||||||
|
acción viven en menu_views.xml, que carga después de cita_views.xml
|
||||||
|
(donde se define action_skeen_cita_tree). -->
|
||||||
|
|
||||||
|
<!-- Menú Citas -->
|
||||||
|
<menuitem id="menu_skeen_citas" name="Citas" parent="skeen_pacientes.menu_skeen_root" sequence="10"/>
|
||||||
|
</odoo>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menús (bajo el menú raíz SKEEN) -->
|
<!-- Menús (bajo el menú raíz SKEEN) -->
|
||||||
<menuitem id="menu_skeen_inventario" name="Inventario" parent="skeen_citas.menu_skeen_root" sequence="40"/>
|
<menuitem id="menu_skeen_inventario" name="Inventario" parent="skeen_pacientes.menu_skeen_root" sequence="40"/>
|
||||||
<menuitem id="menu_skeen_inventario_items" name="Artículos" parent="menu_skeen_inventario"
|
<menuitem id="menu_skeen_inventario_items" name="Artículos" parent="menu_skeen_inventario"
|
||||||
action="action_skeen_inventario_item" sequence="10"/>
|
action="action_skeen_inventario_item" sequence="10"/>
|
||||||
<menuitem id="menu_skeen_inventario_moves" name="Movimientos" parent="menu_skeen_inventario"
|
<menuitem id="menu_skeen_inventario_moves" name="Movimientos" parent="menu_skeen_inventario"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Menú Monedero -->
|
<!-- Menú Monedero -->
|
||||||
<menuitem id="menu_skeen_monedero" name="Monedero" parent="skeen_citas.menu_skeen_root" sequence="20"/>
|
<menuitem id="menu_skeen_monedero" name="Monedero" parent="skeen_pacientes.menu_skeen_root" sequence="20"/>
|
||||||
|
|
||||||
<menuitem id="menu_skeen_monedero_list" name="Monederos" parent="menu_skeen_monedero"
|
<menuitem id="menu_skeen_monedero_list" name="Monederos" parent="menu_skeen_monedero"
|
||||||
action="action_skeen_monedero_tree" sequence="10"/>
|
action="action_skeen_monedero_tree" sequence="10"/>
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
'depends': ['base', 'contacts', 'hr'],
|
'depends': ['base', 'contacts', 'hr'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
# El menú raíz se declara primero: las vistas de abajo lo usan como
|
||||||
|
# parent y en una instalación limpia este módulo carga antes que
|
||||||
|
# skeen_citas (dueño histórico del xmlid).
|
||||||
|
'views/menu_views.xml',
|
||||||
'views/paciente_views.xml',
|
'views/paciente_views.xml',
|
||||||
'views/receta_views.xml',
|
'views/receta_views.xml',
|
||||||
'views/catalogos_views.xml',
|
'views/catalogos_views.xml',
|
||||||
|
|||||||
@@ -69,8 +69,8 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menús (bajo el menú raíz SKEEN de skeen_citas) -->
|
<!-- Menús (bajo el menú raíz SKEEN de skeen_citas) -->
|
||||||
<menuitem id="menu_skeen_diagnosticos" name="Diagnósticos" parent="skeen_citas.menu_skeen_root"
|
<menuitem id="menu_skeen_diagnosticos" name="Diagnósticos" parent="skeen_pacientes.menu_skeen_root"
|
||||||
action="action_skeen_diagnostico_tree" sequence="17"/>
|
action="action_skeen_diagnostico_tree" sequence="17"/>
|
||||||
<menuitem id="menu_skeen_procedimientos" name="Procedimientos" parent="skeen_citas.menu_skeen_root"
|
<menuitem id="menu_skeen_procedimientos" name="Procedimientos" parent="skeen_pacientes.menu_skeen_root"
|
||||||
action="action_skeen_procedimiento_tree" sequence="18"/>
|
action="action_skeen_procedimiento_tree" sequence="18"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
13
odoo-addons/skeen_pacientes/views/menu_views.xml
Normal file
13
odoo-addons/skeen_pacientes/views/menu_views.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Menú raíz de la aplicación. Vive en skeen_pacientes (el módulo raíz de
|
||||||
|
la jerarquía: todos los demás módulos skeen_* dependen de él, directa o
|
||||||
|
transitivamente) para que exista antes que cualquier otro menú que lo
|
||||||
|
use como parent en una instalación limpia de una sola pasada.
|
||||||
|
|
||||||
|
Las referencias cruzadas usan el xmlid completo
|
||||||
|
skeen_pacientes.menu_skeen_root. En bases existentes el ownership del
|
||||||
|
xmlid se transfirió de skeen_citas a skeen_pacientes (ver migración
|
||||||
|
ir_model_data), así que actualizar no duplica el menú. -->
|
||||||
|
<menuitem id="menu_skeen_root" name="SKEEN" sequence="10"/>
|
||||||
|
</odoo>
|
||||||
@@ -39,6 +39,6 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menú Recetas (bajo el menú raíz SKEEN de skeen_citas) -->
|
<!-- Menú Recetas (bajo el menú raíz SKEEN de skeen_citas) -->
|
||||||
<menuitem id="menu_skeen_recetas" name="Recetas" parent="skeen_citas.menu_skeen_root"
|
<menuitem id="menu_skeen_recetas" name="Recetas" parent="skeen_pacientes.menu_skeen_root"
|
||||||
action="action_skeen_receta_tree" sequence="16"/>
|
action="action_skeen_receta_tree" sequence="16"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data>
|
<data>
|
||||||
<menuitem id="menu_skeen_ventas_root" name="Ventas" parent="skeen_citas.menu_skeen_root" sequence="30" />
|
<menuitem id="menu_skeen_ventas_root" name="Ventas" parent="skeen_pacientes.menu_skeen_root" sequence="30" />
|
||||||
<menuitem id="menu_skeen_venta" name="Ventas" parent="menu_skeen_ventas_root" action="action_skeen_venta" sequence="10" />
|
<menuitem id="menu_skeen_venta" name="Ventas" parent="menu_skeen_ventas_root" action="action_skeen_venta" sequence="10" />
|
||||||
<menuitem id="menu_skeen_corte_caja" name="Cortes de Caja" parent="menu_skeen_ventas_root" action="action_skeen_corte_caja" sequence="20" />
|
<menuitem id="menu_skeen_corte_caja" name="Cortes de Caja" parent="menu_skeen_ventas_root" action="action_skeen_corte_caja" sequence="20" />
|
||||||
<menuitem id="menu_skeen_paquete" name="Paquetes" parent="menu_skeen_ventas_root" action="action_skeen_paquete" sequence="30" />
|
<menuitem id="menu_skeen_paquete" name="Paquetes" parent="menu_skeen_ventas_root" action="action_skeen_paquete" sequence="30" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Menú Visitas (bajo el menú raíz SKEEN de skeen_citas) -->
|
<!-- Menú Visitas (bajo el menú raíz SKEEN de skeen_citas) -->
|
||||||
<menuitem id="menu_skeen_visitas" name="Visitas" parent="skeen_citas.menu_skeen_root" sequence="15"/>
|
<menuitem id="menu_skeen_visitas" name="Visitas" parent="skeen_pacientes.menu_skeen_root" sequence="15"/>
|
||||||
<menuitem id="menu_skeen_visitas_list" name="Todas las Visitas" parent="menu_skeen_visitas"
|
<menuitem id="menu_skeen_visitas_list" name="Todas las Visitas" parent="menu_skeen_visitas"
|
||||||
action="action_skeen_visita_tree" sequence="10"/>
|
action="action_skeen_visita_tree" sequence="10"/>
|
||||||
<menuitem id="menu_skeen_visitas_hoy" name="Visitas de Hoy" parent="menu_skeen_visitas"
|
<menuitem id="menu_skeen_visitas_hoy" name="Visitas de Hoy" parent="menu_skeen_visitas"
|
||||||
|
|||||||
Reference in New Issue
Block a user