white-label: brand de la clinica por configuracion (fase 1 de instancia sin branding SKEEN)
- backend: GET/PUT /skeen/frontend/v1/brand leyendo ir.config_parameter skeen.clinic.brand (JSON: nombre, logo_claro, logo_oscuro, pie) con defaults SKEEN; GET publico (el login lo necesita sin sesion), PUT admin con auditoria. frontend_users.xml: 'Administrador SKEEN' -> 'Administrador' - frontend: ClinicBrand + BRAND_DEFAULT en odoo.ts; brand y setBrand en el contexto de auth (fetch al boot + cache localStorage + document.title) - Login/layouts/mensajes/exports leen el brand en vez de strings SKEEN: Login.tsx, SkeenLayout, HomeNestLayout, Cumpleanos (wa + export), Pacientes/Ventas/Cortes/Reportes/Kpis/Alertas/AgendaPrint (titulos), useClinicaPrint (fallback nombre), Login hace GET publico al montar - Configuracion > Clinica: tarjeta 'Identidad de la clinica' (solo admin) para editar nombre/pie/logos con preview - no renombrado (invisible al usuario): rutas API /skeen/*, claves localStorage, nombres de modulos, parametros skeen.* Verificacion API: 11/11 tests OK (defaults, PUT merge, campo invalido ignorado, restore, admin neutral). Deploy: build + rsync + Odoo reiniciado
This commit is contained in:
@@ -6244,6 +6244,79 @@ class SkeenFrontendController(http.Controller):
|
||||
except Exception as e:
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
# ============================================================
|
||||
# Brand de la clínica (nombre, logos, pie) — white-label
|
||||
# ============================================================
|
||||
BRAND_KEY = 'skeen.clinic.brand'
|
||||
|
||||
def _brand_dict(self):
|
||||
"""Brand vigente: lo guardado en ir.config_parameter o defaults SKEEN.
|
||||
|
||||
Este endpoint es lo que permite desplegar la plataforma para otra
|
||||
clínica sin tocar código: la instancia nueva solo cambia este
|
||||
parámetro y sube sus logos. Los campos:
|
||||
nombre - nombre comercial (login, títulos, mensajes, tickets)
|
||||
logo_claro - logo para fondos oscuros (login SKEEN, sidebar)
|
||||
logo_oscuro - logo para fondos claros (tema HomeNest)
|
||||
pie - línea del pie (login / documentos impresos)
|
||||
"""
|
||||
defaults = {
|
||||
'nombre': 'SKEEN Derma Experts',
|
||||
'logo_claro': '/skeen-brand/logos/Logo%20Completo%20Blanco.png',
|
||||
'logo_oscuro': '/skeen-brand/logos/Logo%20Completo%20Negro.png',
|
||||
'pie': 'SKEEN Derma Experts · Playas de Rosarito, B.C.',
|
||||
}
|
||||
raw = request.env['ir.config_parameter'].sudo().get_param(self.BRAND_KEY)
|
||||
if raw:
|
||||
try:
|
||||
guardado = json.loads(raw)
|
||||
if isinstance(guardado, dict):
|
||||
defaults.update({k: str(v) for k, v in guardado.items()
|
||||
if k in defaults and v is not None and str(v).strip()})
|
||||
except Exception:
|
||||
pass # JSON roto: se sirven los defaults
|
||||
return defaults
|
||||
|
||||
@http.route('/skeen/frontend/v1/brand', type='http',
|
||||
auth='none', methods=['GET', 'OPTIONS'], csrf=False)
|
||||
def get_brand(self, **kw):
|
||||
"""Brand de la clínica. PÚBLICO: el login (sin sesión) lo necesita
|
||||
para pintar nombre/logo de la clínica antes de autenticar. Solo expone
|
||||
nombre y URLs de logos, nada sensible."""
|
||||
try:
|
||||
return json_response({'status': 'success', 'brand': self._brand_dict()})
|
||||
except Exception as e:
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route('/skeen/frontend/v1/brand', type='http',
|
||||
auth='none', methods=['PUT', 'OPTIONS'], csrf=False)
|
||||
@require_role('admin')
|
||||
def put_brand(self, **kw):
|
||||
"""Guarda el brand. Solo admin del sistema; queda en la bitácora."""
|
||||
try:
|
||||
data = _parse_json_body()
|
||||
brand = data.get('brand')
|
||||
if not isinstance(brand, dict):
|
||||
return json_response({'status': 'error',
|
||||
'message': 'brand debe ser un objeto'}, 400)
|
||||
antes = self._brand_dict()
|
||||
limpio = {k: str(brand[k]).strip() for k in
|
||||
('nombre', 'logo_claro', 'logo_oscuro', 'pie')
|
||||
if brand.get(k) is not None and str(brand.get(k)).strip()}
|
||||
request.env['ir.config_parameter'].sudo().set_param(
|
||||
self.BRAND_KEY, json.dumps(limpio, ensure_ascii=False))
|
||||
nuevo = self._brand_dict()
|
||||
self._auditar('clinica', 'brand', 'brand',
|
||||
old_value=json.dumps(antes, ensure_ascii=False)[:500],
|
||||
new_value=json.dumps(nuevo, ensure_ascii=False)[:500],
|
||||
entity_label='Identidad de la clínica (brand)',
|
||||
notes=(data.get('motivo') or 'actualizado').strip())
|
||||
return json_response({'status': 'success', 'brand': nuevo,
|
||||
'message': 'Identidad de la clínica actualizada'})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
# ============================================================
|
||||
# Diseño de tickets (Configuración › Tickets)
|
||||
# ============================================================
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Cámbiala en cuanto se tenga pantalla de usuarios. -->
|
||||
<record id="seed_admin" model="skeen.frontend.user">
|
||||
<field name="login">admin</field>
|
||||
<field name="name">Administrador SKEEN</field>
|
||||
<field name="name">Administrador</field>
|
||||
<field name="password_hash">scrypt:32768:8:1$qUN8LJrWj2F6a5Fe$699e6d72d42004abb0fb401843c2ccb497eccc39600ea2e7160eda276148fbc246c3a1e777eb3d44161cfe1cd86bc490e35c062cabf8c3d753cd170c478ae06a</field>
|
||||
<field name="role">admin</field>
|
||||
<field name="active" eval="True"/>
|
||||
|
||||
Reference in New Issue
Block a user