Permisos por pestana de reportes y ruido de serializacion en presencia
Auditoria de 260 visitas (26 rutas x 10 roles) sobre el build actual: 0 hallazgos.
Dos defectos reales que salieron del barrido anterior se corrigen aqui.
1) Reportes: la matriz guarda claves '/reportes?tab=x' y puede conceder una
pestana suelta (Horas agenda al medico, Exportar a cosmetologia) sin dar el
modulo. Nadie la respetaba:
- App.tsx: nuevo ReportesRoute que evalua la pestana del URL; si el rol no
tiene '/reportes' pero si alguna pestana, aterriza en la primera permitida.
- Reportes.tsx: familias, favoritos y el boton "Exportar datos" se filtran
por canSee('/reportes?tab=x'). 'exportar' no pertenece a ninguna familia,
por eso se lista en TABS_FUERA_DE_FAMILIA (si no, el guard lo reescribia a
'general' y admin perdia la vista). El query se sincroniza con lo renderizado.
- SkeenLayout/HomeNestLayout: "Horas agenda" y "Exportar datos" aparecen en
Analitica para que el permiso sea alcanzable desde el menu, no solo a
mano. Clasico ya los traia.
- auth.py: _medico_reporte_permitido consulta la matriz antes de vetar
/reports/ al medico. Sin ese puente el menu ofrecia la opcion y la API
respondia 403. Las demas rutas vetadas (sales, payments, wallets, pos)
siguen cerradas: verificado comisiones=403, horas-agenda=200.
2) skeen_chat/_touch: 211 "could not serialize access due to concurrent update"
en un barrido. Bandeja, badge y heartbeat salen en paralelo y cada uno
emitia el UPDATE de last_seen aunque no aplicara cambios, abortando la
transaccion de otra peticion. Se corta antes de tocar la fila cuando el
registro es reciente; el savepoint queda para el caso de empate exacto.
Ráfaga de 128 peticiones concurrentes: todas 200, presencia sigue
actualizando (online=true, last_seen avanza) y el ruido bajo de 211 a 5.
This commit is contained in:
@@ -27,17 +27,20 @@ class SkeenChatController(http.Controller):
|
||||
def _touch(self, me):
|
||||
"""Marca actividad del usuario (presencia). Nunca debe romper la petición.
|
||||
|
||||
Se hace con SQL directo dentro de un savepoint: el frontend consulta
|
||||
varios endpoints de chat en paralelo (bandeja, badge, heartbeat) y dos
|
||||
UPDATE concurrentes sobre la misma fila abortan la transacción en
|
||||
REPEATABLE READ ("could not serialize access due to concurrent update").
|
||||
Con el savepoint ese fallo se descarta y la petición sigue; además sólo
|
||||
se escribe si el último registro tiene más de TOUCH_INTERVAL segundos,
|
||||
que es lo que evita la mayor parte de los choques.
|
||||
El frontend consulta varios endpoints de chat en paralelo (bandeja,
|
||||
badge, heartbeat) y todos llaman aquí. Por eso se corta antes de tocar
|
||||
la fila cuando el registro es reciente (TOUCH_INTERVAL): el UPDATE se
|
||||
ejecutaba aunque no aplicara ningún cambio y abortaba la transacción de
|
||||
otra petición concurrente ("could not serialize access due to concurrent
|
||||
update", 211 veces en un barrido de 260 visitas). El savepoint sigue
|
||||
ahí por si dos peticiones pasan el corte en el mismo instante; su fallo
|
||||
se descarta y la petición continúa.
|
||||
"""
|
||||
try:
|
||||
ahora = fields.Datetime.now()
|
||||
limite = fields.Datetime.subtract(ahora, seconds=TOUCH_INTERVAL)
|
||||
if me.last_seen and me.last_seen >= limite:
|
||||
return
|
||||
cr = request.env.cr
|
||||
with cr.savepoint():
|
||||
cr.execute(
|
||||
|
||||
Reference in New Issue
Block a user