oca-mrp/odoo-bringout-oca-event-event_session/event_session/wizards/wizard_event_session.py
2025-08-29 15:43:05 +02:00

273 lines
8.7 KiB
Python

# Copyright 2017 David Vidal<david.vidal@tecnativa.com>
# Copyright 2017 Tecnativa - Pedro M. Baeza
# Copyright 2021 Moka Tourisme (https://www.mokatourisme.fr).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import datetime, time, timedelta
import pytz
from dateutil import rrule
from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
SELECT_FREQ_TO_RRULE = {
"daily": rrule.DAILY,
"weekly": rrule.WEEKLY,
"monthly": rrule.MONTHLY,
"yearly": rrule.YEARLY,
}
RRULE_WEEKDAYS = {
"SUN": "SU",
"MON": "MO",
"TUE": "TU",
"WED": "WE",
"THU": "TH",
"FRI": "FR",
"SAT": "SA",
}
def freq_to_rrule(freq):
return SELECT_FREQ_TO_RRULE[freq]
def float_time_to_hours_and_minutes(float_time):
# Round to 2 decimals to avoid hours like 1:60
# It'd be rounded to 2:00
float_time = round(float_time, 2)
hours = int(float_time)
minutes = round((float_time - hours) * 60)
return (hours, minutes)
def float_time_as_timedelta(float_time):
hours, minutes = float_time_to_hours_and_minutes(float_time)
return timedelta(hours=hours, minutes=minutes)
def float_time_as_time(float_time):
hours, minutes = float_time_to_hours_and_minutes(float_time)
return time(hour=hours, minute=minutes)
class WizardEventSession(models.TransientModel):
_name = "wizard.event.session"
_description = "Wizard for ease sessions creation"
event_id = fields.Many2one(
comodel_name="event.event",
default=lambda self: self.env.context["active_id"],
ondelete="cascade",
required=True,
readonly=True,
)
date_tz = fields.Selection(
related="event_id.date_tz",
help="Set it up in the event configuration"
"Sessions will be generated up to this date",
)
duration = fields.Float(
compute="_compute_duration",
readonly=False,
store=True,
required=True,
help="Duration of the sessions in hours",
)
timeslot_ids = fields.Many2many(
comodel_name="event.session.timeslot",
string="Time slots",
required=True,
)
# rrule fields
interval = fields.Integer(default=1, required=True)
rrule_type = fields.Selection(
[("weekly", "Weeks"), ("monthly", "Months")],
string="Recurrence",
default="weekly",
required=True,
)
mon = fields.Boolean()
tue = fields.Boolean()
wed = fields.Boolean()
thu = fields.Boolean()
fri = fields.Boolean()
sat = fields.Boolean()
sun = fields.Boolean()
month_by = fields.Selection(
[("date", "Date of month"), ("day", "Day of month")],
default="date",
)
day = fields.Integer(default=1)
weekday = fields.Selection(
[
("MON", "Monday"),
("TUE", "Tuesday"),
("WED", "Wednesday"),
("THU", "Thursday"),
("FRI", "Friday"),
("SAT", "Saturday"),
("SUN", "Sunday"),
],
)
byday = fields.Selection(
[
("1", "First"),
("2", "Second"),
("3", "Third"),
("4", "Fourth"),
("-1", "Last"),
],
string="By day",
)
start = fields.Date(
compute="_compute_start",
readonly=False,
required=True,
store=True,
)
until = fields.Date(required=True)
@api.depends("event_id")
def _compute_start(self):
# Suggest to create sessions from the date of the last session
# Usually the user wants to add new ones.
for rec in self:
rec.start = rec.event_id.date_end.date() + timedelta(days=1)
@api.depends("event_id")
def _compute_duration(self):
# Suggest to create sessions with the same duration than the
# last existing session
for rec in self:
if rec.event_id.session_ids:
session = rec.event_id.session_ids[-1]
delta = session.date_end - session.date_begin
rec.duration = round(delta.total_seconds() / 3600, 2)
@api.constrains("duration")
def _check_duration(self):
if any(rec.duration <= 0 for rec in self):
raise ValidationError(_("Duration is required."))
@api.constrains("interval")
def _check_interval(self):
if any(rec.interval <= 0 for rec in self):
raise ValidationError(_("The interval cannot be negative."))
def _get_lang_week_start(self):
lang = self.env["res.lang"]._lang_get(self.env.user.lang)
week_start = int(lang.week_start)
# lang.week_start ranges from '1' to '7'
# rrule expects an int from 0 to 6
return rrule.weekday(week_start - 1)
def _get_week_days(self):
return tuple(
rrule.weekday(weekday_index)
for weekday_index, weekday in {
rrule.MO.weekday: self.mon,
rrule.TU.weekday: self.tue,
rrule.WE.weekday: self.wed,
rrule.TH.weekday: self.thu,
rrule.FR.weekday: self.fri,
rrule.SA.weekday: self.sat,
rrule.SU.weekday: self.sun,
}.items()
if weekday
)
def _get_rrule(self, dtstart=None):
"""Builds the rrule from fields"""
self.ensure_one()
freq = self.rrule_type
rrule_params = dict(
dtstart=dtstart,
until=datetime.combine(self.until, datetime.max.time()),
interval=self.interval,
)
if freq == "monthly" and self.month_by == "date":
rrule_params["bymonthday"] = self.day
elif freq == "monthly" and self.month_by == "day":
rrule_params["byweekday"] = getattr(rrule, RRULE_WEEKDAYS[self.weekday])(
int(self.byday)
)
elif freq == "weekly":
weekdays = self._get_week_days()
if not weekdays: # pragma: no cover
raise ValidationError(
_("You have to choose at least one day in the week")
)
rrule_params["byweekday"] = weekdays
rrule_params["wkst"] = self._get_lang_week_start()
return rrule.rrule(freq_to_rrule(freq), **rrule_params)
def _get_start_of_period(self):
self.ensure_one()
dtstart = datetime.combine(self.start, datetime.min.time())
if self.rrule_type == "monthly":
return dtstart - relativedelta(day=1)
return dtstart
def _get_occurrences(self):
self.ensure_one()
dtstart = self._get_start_of_period()
occurences = self._get_rrule(dtstart=dtstart)
return list(occurences)
def _get_ranges(self):
"""Generate ranges from the rrule
:return: list of tuples (start_dt, end_dt, extra_vals)
"""
self.ensure_one()
res = []
ocurrences = self._get_occurrences()
duration = float_time_as_timedelta(self.duration)
timezone = pytz.timezone(self.date_tz)
timeslot_times = [float_time_as_time(t.time) for t in self.timeslot_ids]
for dtstart in ocurrences:
for tslot, ttime in zip(self.timeslot_ids, timeslot_times):
start = datetime.combine(dtstart, ttime)
start_utc = (
timezone.localize(start, is_dst=False)
.astimezone(pytz.utc)
.replace(tzinfo=None)
)
extra_vals = tslot._prepare_session_extra_vals()
res.append((start_utc, start_utc + duration, extra_vals))
return res
def _prepare_session_vals(self, date_begin, date_end):
self.ensure_one()
return {
"event_id": self.event_id.id,
"date_begin": date_begin,
"date_end": date_end,
}
def _create_sessions(self):
"""Create sessions"""
self.ensure_one()
session_vals = []
for date_begin, date_end, extra_vals in self._get_ranges():
vals = self._prepare_session_vals(date_begin, date_end)
vals.update(extra_vals)
session_vals.append(vals)
return self.env["event.session"].create(session_vals)
def action_create_sessions(self):
self.ensure_one()
sessions = self._create_sessions()
action = self.env["ir.actions.act_window"]._for_xml_id(
"event_session.act_event_session_event_form"
)
action["domain"] = [("id", "in", sessions.ids)]
action["context"] = {
"default_event_id": self.event_id.id,
"search_default_event_id": self.event_id.id,
}
return action