mirror of
https://github.com/bringout/oca-mrp.git
synced 2026-04-20 18:32:00 +02:00
273 lines
8.7 KiB
Python
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
|