161 lines
5.8 KiB
Python
161 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import models, fields, api
|
|
from odoo.exceptions import ValidationError
|
|
|
|
DIAS = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo']
|
|
DIAS_ABREV = ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom']
|
|
|
|
|
|
def _hora_str(h):
|
|
"""9.5 -> '9:30'"""
|
|
h = float(h or 0.0)
|
|
return '%d:%02d' % (int(h), int(round((h - int(h)) * 60)))
|
|
|
|
|
|
class HrEmployeeHorario(models.Model):
|
|
"""Línea de horario semanal del médico.
|
|
|
|
Un registro por día que el médico atiende. Un empleado SIN registros se
|
|
considera sin horario configurado (la agenda se comporta como siempre).
|
|
"""
|
|
_name = 'hr.employee.horario'
|
|
_description = 'Horario semanal del médico'
|
|
_order = 'day_of_week'
|
|
|
|
employee_id = fields.Many2one('hr.employee', string='Médico', required=True,
|
|
ondelete='cascade', index=True)
|
|
day_of_week = fields.Selection([
|
|
('0', 'Lunes'),
|
|
('1', 'Martes'),
|
|
('2', 'Miércoles'),
|
|
('3', 'Jueves'),
|
|
('4', 'Viernes'),
|
|
('5', 'Sábado'),
|
|
('6', 'Domingo'),
|
|
], string='Día', required=True)
|
|
hour_from = fields.Float(string='Hora inicio', required=True, default=9.0)
|
|
hour_to = fields.Float(string='Hora fin', required=True, default=14.0)
|
|
|
|
day_label = fields.Char(string='Día (texto)', compute='_compute_day_label')
|
|
|
|
_sql_constraints = [
|
|
('unique_employee_day', 'unique(employee_id, day_of_week)',
|
|
'Ya hay un horario registrado para ese día.'),
|
|
]
|
|
|
|
@api.depends('day_of_week')
|
|
def _compute_day_label(self):
|
|
for rec in self:
|
|
rec.day_label = DIAS[int(rec.day_of_week or 0)]
|
|
|
|
@api.constrains('hour_from', 'hour_to')
|
|
def _check_horas(self):
|
|
for rec in self:
|
|
if rec.hour_to <= rec.hour_from:
|
|
raise ValidationError('La hora fin debe ser mayor a la hora inicio.')
|
|
|
|
|
|
class HrEmployee(models.Model):
|
|
_inherit = 'hr.employee'
|
|
|
|
commission_pct = fields.Float(
|
|
string='% Comisión',
|
|
default=0.0,
|
|
help='Porcentaje de comisión sobre la venta de artículos recetados por este médico.',
|
|
)
|
|
|
|
staff_type = fields.Selection(
|
|
[('doctor', 'Doctor'), ('cosmetologa', 'Cosmetóloga')],
|
|
string='Tipo de Staff',
|
|
default='doctor',
|
|
help='Tipo de personal de la agenda: Doctor o Cosmetóloga.',
|
|
)
|
|
|
|
monthly_goal = fields.Float(
|
|
string='Meta mensual',
|
|
default=0.0,
|
|
help='Objetivo de venta del mes (m.n.) para el tablero del médico.',
|
|
)
|
|
|
|
horario_ids = fields.One2many(
|
|
'hr.employee.horario', 'employee_id', string='Horario semanal',
|
|
help='Días y horas que atiende. Si está vacío, la agenda no restringe horarios.',
|
|
)
|
|
tiene_horario = fields.Boolean(
|
|
string='Con horario configurado', compute='_compute_tiene_horario',
|
|
)
|
|
horario_resumen = fields.Char(
|
|
string='Horario', compute='_compute_horario_resumen',
|
|
help='Resumen legible (p. ej. "Lun-Vie 9:00-14:00").',
|
|
)
|
|
|
|
@api.depends('horario_ids')
|
|
def _compute_tiene_horario(self):
|
|
for emp in self:
|
|
emp.tiene_horario = bool(emp.horario_ids)
|
|
|
|
@api.depends('horario_ids.day_of_week', 'horario_ids.hour_from', 'horario_ids.hour_to')
|
|
def _compute_horario_resumen(self):
|
|
for emp in self:
|
|
emp.horario_resumen = emp._horario_resumen()
|
|
|
|
# ------------------------------------------------------------------
|
|
# Consultas de horario
|
|
# ------------------------------------------------------------------
|
|
def _horario_del_dia(self, fecha):
|
|
"""Línea de horario del día de la semana de `fecha` (vacío si no aplica)."""
|
|
self.ensure_one()
|
|
if not fecha:
|
|
return self.env['hr.employee.horario']
|
|
return self.horario_ids.filtered(lambda h: int(h.day_of_week) == fecha.weekday())[:1]
|
|
|
|
def _horario_resumen(self):
|
|
"""Resumen legible agrupando días consecutivos con el mismo horario.
|
|
|
|
Ej.: "Lun-Vie 9:00-14:00" o "Lun, Mié 9:00-14:00; Sáb 9:00-13:00".
|
|
"""
|
|
self.ensure_one()
|
|
if not self.horario_ids:
|
|
return ''
|
|
por_hora = {}
|
|
for h in self.horario_ids:
|
|
clave = (_hora_str(h.hour_from), _hora_str(h.hour_to))
|
|
por_hora.setdefault(clave, []).append(int(h.day_of_week))
|
|
partes = []
|
|
for (desde, hasta), dias in por_hora.items():
|
|
dias = sorted(dias)
|
|
tramos = []
|
|
inicio = previo = dias[0]
|
|
for d in dias[1:]:
|
|
if d == previo + 1:
|
|
previo = d
|
|
continue
|
|
tramos.append((inicio, previo))
|
|
inicio = previo = d
|
|
tramos.append((inicio, previo))
|
|
etiquetas = [
|
|
DIAS_ABREV[a] if a == b else '%s-%s' % (DIAS_ABREV[a], DIAS_ABREV[b])
|
|
for a, b in tramos
|
|
]
|
|
partes.append('%s %s-%s' % (', '.join(etiquetas), desde, hasta))
|
|
return '; '.join(partes)
|
|
|
|
def _valida_cita_en_horario(self, fecha, hora_ini, duracion_min):
|
|
"""(ok, mensaje) para una cita en `fecha` a las `hora_ini` (float de horas).
|
|
|
|
Si el médico no tiene horario configurado devuelve (True, '').
|
|
"""
|
|
self.ensure_one()
|
|
if not self.horario_ids or not fecha:
|
|
return True, ''
|
|
linea = self._horario_del_dia(fecha)
|
|
resumen = self._horario_resumen() or 'sin definir'
|
|
if not linea:
|
|
return False, 'El doctor no atiende el %s (su horario: %s)' % (
|
|
DIAS[fecha.weekday()].lower(), resumen)
|
|
dur = (duracion_min or 30) / 60.0
|
|
if hora_ini < linea.hour_from - 1e-6 or hora_ini + dur > linea.hour_to + 1e-6:
|
|
return False, 'El doctor no atiende a esa hora (su horario: %s)' % resumen
|
|
return True, ''
|