Files
enterprise-addons/appointment/models/appointment_slot.py
T

137 lines
7.1 KiB
Python

from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools.misc import format_duration
class AppointmentSlot(models.Model):
_name = 'appointment.slot'
_description = "Appointment: Time Slot"
_rec_name = "weekday"
_order = "weekday, start_hour, start_datetime, end_datetime"
appointment_type_id = fields.Many2one('appointment.type', 'Appointment Type', index=True, ondelete='cascade')
schedule_based_on = fields.Selection(related="appointment_type_id.schedule_based_on")
slot_type = fields.Selection([('recurring', 'Regular'), ('unique', 'One Shot')],
string='Slot type', default='recurring', required=True, compute="_compute_slot_type", store=True,
help="""Defines the type of slot. The regular slot is the default type which is used for
appointment type that are used recurringly in type like medical appointment.
The one shot type is only used when an user create a custom appointment type for a client by
defining non-recurring time slot (e.g. 10th of April 2021 from 10 to 11 am) from its calendar.""")
allday = fields.Boolean('All day',
help="Determine if the slot englobe the whole day, mainly used for unique slot type")
restrict_to_user_ids = fields.Many2many('res.users', string='Restrict to Users',
compute="_compute_restrict_to_user_ids", readonly=False, store=True,
help="If empty, all users are considered to be available.\n"
"If set, only the selected users will be taken into account for this slot.")
restrict_to_resource_ids = fields.Many2many("appointment.resource", string="Restrict to Resources",
compute="_compute_restrict_to_resource_ids", readonly=False, store=True,
help="If empty, all resources are considered to be available.\n"
"If set, only the selected resources will be taken into account for this slot.")
# Recurring slot
weekday = fields.Selection([
('1', 'Monday'),
('2', 'Tuesday'),
('3', 'Wednesday'),
('4', 'Thursday'),
('5', 'Friday'),
('6', 'Saturday'),
('7', 'Sunday'),
], string='Week Day', required=True, default='1')
start_hour = fields.Float('Starting Hour', required=True, default=8.0)
end_hour = fields.Float('Ending Hour', required=True, default=17.0, compute='_compute_end_hour', readonly=False,
store=True)
# Real time slot
start_datetime = fields.Datetime('From', help="Start datetime for unique slot type management")
end_datetime = fields.Datetime('To', help="End datetime for unique slot type management")
duration = fields.Float('Duration', compute='_compute_duration')
_check_start_and_end_hour = models.Constraint(
'''CHECK(
((end_hour=0 AND (start_hour BETWEEN 0 AND 23.99)) OR (start_hour BETWEEN 0 AND end_hour))
AND (end_hour=0 OR (end_hour BETWEEN start_hour AND 23.99))
)''',
"The end time must be later than the start time.",
)
_check_start_and_end_datetimes = models.Constraint(
'''CHECK(
slot_type='recurring' OR (
start_datetime IS NOT NULL AND end_datetime IS NOT NULL AND (start_datetime <= end_datetime))
)''',
"The unique slot end datetime must be later than the start datetime.",
)
@api.depends('allday', 'start_datetime', 'end_datetime')
def _compute_duration(self):
for slot in self:
if slot.start_datetime and slot.end_datetime:
start_datetime = slot.start_datetime
end_datetime = slot.end_datetime
if slot.allday:
# when "allday" is True, the end date is stored as "that day at midnight"
# we add 1 full day to have a proper duration computation
end_datetime += relativedelta(days=1)
duration = (end_datetime - start_datetime).total_seconds() / 3600
slot.duration = round(duration, 2)
else:
slot.duration = 0
@api.depends('appointment_type_id', 'appointment_type_id.category')
def _compute_slot_type(self):
for slot in self:
slot.slot_type = 'unique' if slot.appointment_type_id.category == 'custom' else 'recurring'
@api.depends('start_hour')
def _compute_end_hour(self):
""" Try to adapt end_hour if the interval end_hour < start_hour """
for record in self:
duration = record.appointment_type_id.appointment_duration
if duration > 0 and record._convert_end_hour_24_format() <= record.start_hour \
and record.start_hour + duration <= 24:
record.end_hour = (record.start_hour + duration) % 24
@api.constrains('start_hour', 'end_hour')
def _check_delta_hours(self):
if any(self.filtered(lambda slot: slot.start_hour + slot.appointment_type_id.appointment_duration > slot._convert_end_hour_24_format() and slot.slot_type != 'unique')):
raise ValidationError(_(
"At least one slot duration is shorter than the meeting duration (%s hours)",
format_duration(self.appointment_type_id.appointment_duration)
))
@api.constrains('slot_type', 'start_datetime', 'end_datetime')
def _check_unique_slot_has_datetime(self):
if any(self.filtered(lambda slot: slot.slot_type == "unique" and not (slot.start_datetime and slot.end_datetime))):
raise ValidationError(_("An unique type slot should have a start and end datetime"))
def _convert_end_hour_24_format(self):
"""Convert end_hour from [0, 24[ to ]0, 24] by replacing 0 by 24 if necessary.
The end_hour can be encoded as '00:00', which means 'the next day at midnight'.
For some simple computation, we transform that 0 into 24 to make it easier to manipulate.
For example, when we want to know if the end hour is after the start hour, or when looping through
available slots 'until the end hour'.
"""
self.ensure_one()
return self.end_hour if self.end_hour else 24
@api.depends('slot_type', 'weekday', 'start_datetime', 'end_datetime', 'start_hour', 'end_hour')
def _compute_display_name(self):
weekdays = dict(self._fields['weekday']._description_selection(self.env))
for slot in self:
if slot.slot_type == 'recurring':
slot.display_name = "%s, %02d:%02d - %02d:%02d" % (weekdays.get(slot.weekday), int(slot.start_hour), int(round((slot.start_hour % 1) * 60)), int(slot.end_hour), int(round((slot.end_hour % 1) * 60)))
else:
slot.display_name = f"{slot.start_datetime} - {slot.end_datetime}"
@api.depends('appointment_type_id.resource_ids')
def _compute_restrict_to_resource_ids(self):
for slot in self:
slot.restrict_to_resource_ids &= slot.appointment_type_id.resource_ids
@api.depends('appointment_type_id.staff_user_ids')
def _compute_restrict_to_user_ids(self):
for slot in self:
slot.restrict_to_user_ids &= slot.appointment_type_id.staff_user_ids