Punto 13: alta/baja de médicos desde el módulo Médicos
- Columna 'Estado' con switch Activo/Baja (confirmación previa) y checkbox 'Mostrar dados de baja' para poder reactivar. - GET /doctors solo devuelve activos; include_inactive=1 para la pantalla de Médicos. Cada médico expone su campo 'active'. - Reglas en modelo: un empleado dado de baja no admite citas nuevas (skeen.cita), ni asignación de pacientes (res.partner.primary_doctor_id), ni líneas de venta (skeen.venta.line.prescribed_by_id). El historial existente no se toca: los constraints solo corren cuando cambia la asignación. - ValidationError se responde 400 con mensaje legible en pacientes y ventas. - fix: badges de estado de cita en agenda y dashboard usan la etiqueta de la clínica en lugar del valor técnico.
This commit is contained in:
@@ -406,6 +406,21 @@ class SkeenCita(models.Model):
|
||||
if cita.duration and (cita.duration < 5 or cita.duration > 480):
|
||||
raise ValidationError(_('La duración debe estar entre 5 y 480 minutos'))
|
||||
|
||||
@api.constrains('doctor_id')
|
||||
def _check_doctor_activo(self):
|
||||
"""Un médico dado de baja no puede recibir citas nuevas.
|
||||
|
||||
Solo corre cuando cambia `doctor_id`, así que el historial de un médico
|
||||
desactivado sigue intacto y editable (estado, pagos, notas).
|
||||
"""
|
||||
for cita in self:
|
||||
if cita.doctor_id and not cita.doctor_id.active:
|
||||
raise ValidationError(_(
|
||||
'%(nombre)s está dado de baja y no se le pueden asignar '
|
||||
'citas. Reactívalo en el módulo de Médicos o elige otro '
|
||||
'especialista.'
|
||||
) % {'nombre': cita.doctor_id.name})
|
||||
|
||||
def get_available_slots(self, date, servicio_id, doctor_id=None, duration=None):
|
||||
"""Retorna slots disponibles para una fecha y servicio.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
@@ -183,6 +184,21 @@ class ResPartner(models.Model):
|
||||
vals['patient_id'] = self.env['ir.sequence'].next_by_code('skeen.patient') or 'PAT-001'
|
||||
return super(ResPartner, self).create(vals_list)
|
||||
|
||||
@api.constrains('primary_doctor_id')
|
||||
def _check_primary_doctor_activo(self):
|
||||
"""Un médico dado de baja no admite pacientes nuevos.
|
||||
|
||||
Solo corre cuando cambia la asignación, así que los pacientes que ya
|
||||
estaban ligados a ese médico conservan su historial.
|
||||
"""
|
||||
for partner in self:
|
||||
if partner.primary_doctor_id and not partner.primary_doctor_id.active:
|
||||
raise ValidationError(
|
||||
'%s está dado de baja y no se le pueden asignar pacientes. '
|
||||
'Reactívalo en Médicos o elige otro especialista.'
|
||||
% partner.primary_doctor_id.name
|
||||
)
|
||||
|
||||
def action_view_appointments(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
|
||||
@@ -360,6 +360,17 @@ class SkeenVentaLine(models.Model):
|
||||
if bool(line.service_id) == bool(line.item_id):
|
||||
raise ValidationError(_('La línea debe tener un servicio O un artículo, no ambos.'))
|
||||
|
||||
@api.constrains('prescribed_by_id')
|
||||
def _check_prescribed_by_activo(self):
|
||||
"""Las ventas no se le asignan a un médico dado de baja (ni a él ni a
|
||||
sus comisiones). Solo corre al cambiar la asignación."""
|
||||
for line in self:
|
||||
if line.prescribed_by_id and not line.prescribed_by_id.active:
|
||||
raise ValidationError(_(
|
||||
'%(nombre)s está dado de baja y no se le pueden asignar '
|
||||
'ventas.'
|
||||
) % {'nombre': line.prescribed_by_id.name})
|
||||
|
||||
@api.depends('quantity', 'price_unit')
|
||||
def _compute_subtotal(self):
|
||||
for line in self:
|
||||
|
||||
@@ -1917,6 +1917,9 @@ class SkeenFrontendController(http.Controller):
|
||||
vals['image_1920'] = data['photo']
|
||||
patient = request.env['res.partner'].sudo().create(vals)
|
||||
return json_response({'status': 'success', 'patient': self._patient_to_dict(patient)}, 201)
|
||||
except ValidationError as e:
|
||||
# Reglas de negocio (p. ej. médico dado de baja): mensaje legible
|
||||
return json_response({'status': 'error', 'message': e.args[0] if e.args else str(e)}, 400)
|
||||
except Exception as e:
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@@ -1985,6 +1988,8 @@ class SkeenFrontendController(http.Controller):
|
||||
# El rastro principal es la auditoría; el chatter es extra
|
||||
pass
|
||||
return json_response({'status': 'success', 'patient': self._patient_to_dict(patient)})
|
||||
except ValidationError as e:
|
||||
return json_response({'status': 'error', 'message': e.args[0] if e.args else str(e)}, 400)
|
||||
except Exception as e:
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@@ -3170,7 +3175,12 @@ class SkeenFrontendController(http.Controller):
|
||||
# de dinero por médico son del módulo Médicos (admin/recepción).
|
||||
fu = current_user(request.env)
|
||||
hide_money = bool(fu and fu.role == 'medico')
|
||||
doctors = request.env['hr.employee'].sudo().search([('active', '=', True)], limit=100)
|
||||
# Por defecto solo el personal activo: así desaparecen de la agenda,
|
||||
# del selector de médico de pacientes y del selector de ventas.
|
||||
# La pantalla de Médicos pide include_inactive=1 para poder reactivar.
|
||||
domain = [] if str(kw.get('include_inactive')).lower() in ('1', 'true') else [('active', '=', True)]
|
||||
doctors = request.env['hr.employee'].sudo().with_context(active_test=False).search(
|
||||
domain, order='active desc, name', limit=300)
|
||||
# Pacientes asignados por médico (primary_doctor_id)
|
||||
counts = {}
|
||||
for g in request.env['res.partner'].sudo().read_group(
|
||||
@@ -3237,6 +3247,7 @@ class SkeenFrontendController(http.Controller):
|
||||
'doctors': [{
|
||||
'id': d.id,
|
||||
'name': d.name,
|
||||
'active': d.active,
|
||||
'job_title': d.job_title or '',
|
||||
'work_phone': d.work_phone or '',
|
||||
'work_email': d.work_email or '',
|
||||
@@ -3285,6 +3296,10 @@ class SkeenFrontendController(http.Controller):
|
||||
if data['staff_type'] not in ('doctor', 'cosmetologa'):
|
||||
return json_response({'status': 'error', 'message': "staff_type debe ser 'doctor' o 'cosmetologa'"}, 400)
|
||||
vals['staff_type'] = data['staff_type']
|
||||
if 'active' in data:
|
||||
# Desactivar oculta al médico de la agenda, de la asignación de
|
||||
# pacientes y del selector de ventas; no borra su historial.
|
||||
vals['active'] = bool(data.get('active'))
|
||||
if vals:
|
||||
previos = {k: getattr(doctor, k) for k in vals}
|
||||
doctor.write(vals)
|
||||
@@ -4012,6 +4027,8 @@ class SkeenFrontendController(http.Controller):
|
||||
if data.get('state') == 'confirmed':
|
||||
sale.action_confirm()
|
||||
return json_response({'status': 'success', 'sale': self._sale_to_dict(sale)}, 201)
|
||||
except ValidationError as e:
|
||||
return json_response({'status': 'error', 'message': e.args[0] if e.args else str(e)}, 400)
|
||||
except Exception as e:
|
||||
return json_response({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user