diff --git a/base_time_window/README.rst b/base_time_window/README.rst new file mode 100644 index 000000000..59c1f1de1 --- /dev/null +++ b/base_time_window/README.rst @@ -0,0 +1,136 @@ +================ +Base Time Window +================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/15.0/base_time_window + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-15-0/server-tools-15-0-base_time_window + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/149/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provides base classes and models to manage time windows through +`time.window.mixin`. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Example implementation for the mixin can be found in module `test_base_time_window`. + +As a time window will always be linked to a related model thourgh a M2o relation, +when defining the new model inheriting the mixin, one should pay attention to the +following points in order to have the overlapping check work properly: + +- Define class property `_overlap_check_field`: This must state the M2o field to + use for the to check of overlapping time window records linked to a specific + record of the related model. + +- Add the M2o field to the related model in the `api.constrains`: + + +For example: + +.. code-block:: python + + class PartnerTimeWindow(models.Model): + _name = 'partner.time.window' + _inherit = 'time.window.mixin' + + partner_id = fields.Many2one( + res.partner', required=True, index=True, ondelete='cascade' + ) + + _overlap_check_field = 'partner_id' + + @api.constrains('partner_id') + def check_window_no_overlaps(self): + return super().check_window_no_overlaps() + +Known issues / Roadmap +====================== + +* Storing times using `float_time` widget requires extra processing to ensure + computations are done in the right timezone, because the value is not stored + as UTC in the database, and must therefore be related to a `tz` field. + + `float_time` in this sense should only be used for durations and not for a + "point in time" as this is always needs a Date for a timezone conversion to + be done properly. (Because a conversion from UTC to e.g. Europe/Brussels won't + give the same result in winter or summer because of Daylight Saving Time). + + Therefore the right move would be to use a `resource.calendar` to define time + windows using Datetime with recurrences. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Laurent Mignon +* Akim Juillerat + +Trobz + +* Dung Tran + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* Camptocamp + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_time_window/__init__.py b/base_time_window/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/base_time_window/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_time_window/__manifest__.py b/base_time_window/__manifest__.py new file mode 100644 index 000000000..7e8deb02a --- /dev/null +++ b/base_time_window/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "Base Time Window", + "summary": "Base model to handle time windows", + "version": "16.0.1.0.0", + "category": "Technical Settings", + "author": "ACSONE SA/NV, Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/server-tools", + "depends": ["base"], + "data": ["data/time_weekday.xml", "security/ir.model.access.xml"], + "installable": True, +} diff --git a/base_time_window/data/time_weekday.xml b/base_time_window/data/time_weekday.xml new file mode 100644 index 000000000..8c2837e5d --- /dev/null +++ b/base_time_window/data/time_weekday.xml @@ -0,0 +1,26 @@ + + + + + 0 + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + diff --git a/base_time_window/i18n/base_time_window.pot b/base_time_window/i18n/base_time_window.pot new file mode 100644 index 000000000..228cf71b1 --- /dev/null +++ b/base_time_window/i18n/base_time_window.pot @@ -0,0 +1,151 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_time_window +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "%(end_time)s must be > %(start_time)s" +msgstr "" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "%(record_name)s overlaps %(other_name)s" +msgstr "" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "At least one time.weekday is required" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_uid +msgid "Created by" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_date +msgid "Created on" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__display_name +msgid "Display Name" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__4 +msgid "Friday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_start +msgid "From" +msgstr "" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "Hour should be between 00 and 23" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__id +msgid "ID" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__0 +msgid "Monday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__name +msgid "Name" +msgstr "" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_weekday.py:0 +#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq +#, python-format +msgid "Name must be unique" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__5 +msgid "Saturday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__6 +msgid "Sunday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__3 +msgid "Thursday" +msgstr "" + +#. module: base_time_window +#: model:ir.model,name:base_time_window.model_time_weekday +msgid "Time Week Day" +msgstr "" + +#. module: base_time_window +#: model:ir.model,name:base_time_window.model_time_window_mixin +msgid "Time Window" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_weekday_ids +msgid "Time Window Weekday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_end +msgid "To" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__1 +msgid "Tuesday" +msgstr "" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__2 +msgid "Wednesday" +msgstr "" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "{days}: From {start} to {end}" +msgstr "" diff --git a/base_time_window/i18n/es_AR.po b/base_time_window/i18n/es_AR.po new file mode 100644 index 000000000..23e4a15d3 --- /dev/null +++ b/base_time_window/i18n/es_AR.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_time_window +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-09-04 06:07+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "%(end_time)s must be > %(start_time)s" +msgstr "%(end_time)s debe ser > %(start_time)s" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "%(record_name)s overlaps %(other_name)s" +msgstr "%(record_name)s se superpone %(other_name)s" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "At least one time.weekday is required" +msgstr "Se requiere al menos una vez al día de la semana" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__4 +msgid "Friday" +msgstr "Viernes" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_start +msgid "From" +msgstr "Desde" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "Hour should be between 00 and 23" +msgstr "Las horas deben ser entre 00 y 23" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__id +msgid "ID" +msgstr "ID" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_uid +msgid "Last Updated by" +msgstr "Última actualización realizada por" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__0 +msgid "Monday" +msgstr "Lunes" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__name +msgid "Name" +msgstr "Nombre" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_weekday.py:0 +#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq +#, python-format +msgid "Name must be unique" +msgstr "El nombre debe ser único" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__5 +msgid "Saturday" +msgstr "Sábado" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__6 +msgid "Sunday" +msgstr "Domingo" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__3 +msgid "Thursday" +msgstr "Jueves" + +#. module: base_time_window +#: model:ir.model,name:base_time_window.model_time_weekday +msgid "Time Week Day" +msgstr "Hora Semana Día" + +#. module: base_time_window +#: model:ir.model,name:base_time_window.model_time_window_mixin +msgid "Time Window" +msgstr "Ventana de Tiempo" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_weekday_ids +msgid "Time Window Weekday" +msgstr "Ventana de Tiempo Día de la semana" + +#. module: base_time_window +#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_end +msgid "To" +msgstr "Hasta" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__1 +msgid "Tuesday" +msgstr "Martes" + +#. module: base_time_window +#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__2 +msgid "Wednesday" +msgstr "Miércoles" + +#. module: base_time_window +#: code:addons/base_time_window/models/time_window_mixin.py:0 +#, python-format +msgid "{days}: From {start} to {end}" +msgstr "{days}: Desde {start} hasta {end}" diff --git a/base_time_window/models/__init__.py b/base_time_window/models/__init__.py new file mode 100644 index 000000000..122ebbf3e --- /dev/null +++ b/base_time_window/models/__init__.py @@ -0,0 +1,2 @@ +from . import time_weekday +from . import time_window_mixin diff --git a/base_time_window/models/time_weekday.py b/base_time_window/models/time_weekday.py new file mode 100644 index 000000000..0307342f4 --- /dev/null +++ b/base_time_window/models/time_weekday.py @@ -0,0 +1,62 @@ +# Copyright 2020 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models, tools + + +class TimeWeekday(models.Model): + + _name = "time.weekday" + _description = "Time Week Day" + + name = fields.Selection( + selection=[ + ("0", "Monday"), + ("1", "Tuesday"), + ("2", "Wednesday"), + ("3", "Thursday"), + ("4", "Friday"), + ("5", "Saturday"), + ("6", "Sunday"), + ], + required=True, + ) + _sql_constraints = [("name_uniq", "UNIQUE(name)", _("Name must be unique"))] + + @api.depends("name") + def _compute_display_name(self): + """ + WORKAROUND since Odoo doesn't handle properly records where name is + a selection + """ + translated_values = dict(self._fields["name"]._description_selection(self.env)) + for record in self: + record.display_name = translated_values[record.name] + + def name_get(self): + """ + WORKAROUND since Odoo doesn't handle properly records where name is + a selection + """ + return [(r.id, r.display_name) for r in self] + + @api.model + @tools.ormcache("name") + def _get_id_by_name(self, name): + return self.search([("name", "=", name)], limit=1).id + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + self._get_id_by_name.clear_cache(self) + return records + + def write(self, vals): + result = super().write(vals) + self._get_id_by_name.clear_cache(self) + return result + + def unlink(self): + result = super().unlink() + self._get_id_by_name.clear_cache(self) + return result diff --git a/base_time_window/models/time_window_mixin.py b/base_time_window/models/time_window_mixin.py new file mode 100644 index 000000000..b8079043a --- /dev/null +++ b/base_time_window/models/time_window_mixin.py @@ -0,0 +1,137 @@ +# Copyright 2020 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import math +from datetime import time + +from psycopg2.extensions import AsIs + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools.misc import format_time + + +class TimeWindowMixin(models.AbstractModel): + _name = "time.window.mixin" + _description = "Time Window" + _order = "time_window_start" + + # TODO patch api.constrains with field here? + _time_window_overlap_check_field = False + + time_window_start = fields.Float("From", required=True) + time_window_end = fields.Float("To", required=True) + time_window_weekday_ids = fields.Many2many( + comodel_name="time.weekday", required=True + ) + + @api.constrains("time_window_start", "time_window_end", "time_window_weekday_ids") + def check_window_no_overlaps(self): + weekdays_field = self._fields["time_window_weekday_ids"] + for record in self: + if record.time_window_start > record.time_window_end: + raise ValidationError( + _("%(end_time)s must be > %(start_time)s") + % ( + { + "end_time": self.float_to_time_repr(record.time_window_end), + "start_time": self.float_to_time_repr( + record.time_window_start + ), + } + ) + ) + if not record.time_window_weekday_ids: + raise ValidationError(_("At least one time.weekday is required")) + # here we use a plain SQL query to benefit of the numrange + # function available in PostgresSQL + # (http://www.postgresql.org/docs/current/static/rangetypes.html) + SQL = """ + SELECT + id + FROM + %(table)s w + join %(relation)s as d + on d.%(relation_window_fkey)s = w.id + WHERE + NUMRANGE(w.time_window_start::numeric, + w.time_window_end::numeric) && + NUMRANGE(%(start)s::numeric, %(end)s::numeric) + AND w.id != %(window_id)s + AND d.%(relation_week_day_fkey)s in %(weekday_ids)s + AND w.%(check_field)s = %(check_field_id)s;""" + self.env.cr.execute( + SQL, + dict( + table=AsIs(self._table), + relation=AsIs(weekdays_field.relation), + relation_window_fkey=AsIs(weekdays_field.column1), + relation_week_day_fkey=AsIs(weekdays_field.column2), + start=record.time_window_start, + end=record.time_window_end, + window_id=record.id, + weekday_ids=tuple(record.time_window_weekday_ids.ids), + check_field=AsIs(self._time_window_overlap_check_field), + check_field_id=record[self._time_window_overlap_check_field].id, + ), + ) + res = self.env.cr.fetchall() + if res: + other = self.browse(res[0][0]) + raise ValidationError( + _("%(record_name)s overlaps %(other_name)s") + % ( + { + "record_name": record.display_name, + "other_name": other.display_name, + } + ) + ) + + @api.depends("time_window_start", "time_window_end", "time_window_weekday_ids") + def _compute_display_name(self): + for record in self: + record.display_name = _("{days}: From {start} to {end}").format( + days=", ".join(record.time_window_weekday_ids.mapped("display_name")), + start=format_time(self.env, record.get_time_window_start_time()), + end=format_time(self.env, record.get_time_window_end_time()), + ) + + @api.constrains("time_window_start", "time_window_end") + def _check_window_under_twenty_four_hours(self): + error_msg = _("Hour should be between 00 and 23") + for record in self: + if record.time_window_start: + hour, minute = self._get_hour_min_from_value(record.time_window_start) + if hour > 23: + raise ValidationError(error_msg) + if record.time_window_end: + hour, minute = self._get_hour_min_from_value(record.time_window_end) + if hour > 23: + raise ValidationError(error_msg) + + @api.model + def _get_hour_min_from_value(self, value): + hour = math.floor(value) + minute = round((value % 1) * 60) + if minute == 60: + minute = 0 + hour += 1 + return hour, minute + + @api.model + def float_to_time_repr(self, value): + pattern = "%02d:%02d" + hour, minute = self._get_hour_min_from_value(value) + return pattern % (hour, minute) + + @api.model + def float_to_time(self, value): + hour, minute = self._get_hour_min_from_value(value) + return time(hour=hour, minute=minute) + + def get_time_window_start_time(self): + return self.float_to_time(self.time_window_start) + + def get_time_window_end_time(self): + return self.float_to_time(self.time_window_end) diff --git a/base_time_window/readme/CONTRIBUTORS.rst b/base_time_window/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..aa25acd2a --- /dev/null +++ b/base_time_window/readme/CONTRIBUTORS.rst @@ -0,0 +1,6 @@ +* Laurent Mignon +* Akim Juillerat + +Trobz + +* Dung Tran diff --git a/base_time_window/readme/CREDITS.rst b/base_time_window/readme/CREDITS.rst new file mode 100644 index 000000000..f5cc070c7 --- /dev/null +++ b/base_time_window/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +* Camptocamp diff --git a/base_time_window/readme/DESCRIPTION.rst b/base_time_window/readme/DESCRIPTION.rst new file mode 100644 index 000000000..ad077245b --- /dev/null +++ b/base_time_window/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module provides base classes and models to manage time windows through +`time.window.mixin`. diff --git a/base_time_window/readme/ROADMAP.rst b/base_time_window/readme/ROADMAP.rst new file mode 100644 index 000000000..50b5d9b78 --- /dev/null +++ b/base_time_window/readme/ROADMAP.rst @@ -0,0 +1,11 @@ +* Storing times using `float_time` widget requires extra processing to ensure + computations are done in the right timezone, because the value is not stored + as UTC in the database, and must therefore be related to a `tz` field. + + `float_time` in this sense should only be used for durations and not for a + "point in time" as this is always needs a Date for a timezone conversion to + be done properly. (Because a conversion from UTC to e.g. Europe/Brussels won't + give the same result in winter or summer because of Daylight Saving Time). + + Therefore the right move would be to use a `resource.calendar` to define time + windows using Datetime with recurrences. diff --git a/base_time_window/readme/USAGE.rst b/base_time_window/readme/USAGE.rst new file mode 100644 index 000000000..f27eb7352 --- /dev/null +++ b/base_time_window/readme/USAGE.rst @@ -0,0 +1,30 @@ +Example implementation for the mixin can be found in module `test_base_time_window`. + +As a time window will always be linked to a related model thourgh a M2o relation, +when defining the new model inheriting the mixin, one should pay attention to the +following points in order to have the overlapping check work properly: + +- Define class property `_overlap_check_field`: This must state the M2o field to + use for the to check of overlapping time window records linked to a specific + record of the related model. + +- Add the M2o field to the related model in the `api.constrains`: + + +For example: + +.. code-block:: python + + class PartnerTimeWindow(models.Model): + _name = 'partner.time.window' + _inherit = 'time.window.mixin' + + partner_id = fields.Many2one( + res.partner', required=True, index=True, ondelete='cascade' + ) + + _overlap_check_field = 'partner_id' + + @api.constrains('partner_id') + def check_window_no_overlaps(self): + return super().check_window_no_overlaps() diff --git a/base_time_window/security/ir.model.access.xml b/base_time_window/security/ir.model.access.xml new file mode 100644 index 000000000..886ae01b9 --- /dev/null +++ b/base_time_window/security/ir.model.access.xml @@ -0,0 +1,14 @@ + + + + + time.weekday access read + + + + + + + + diff --git a/base_time_window/static/description/icon.png b/base_time_window/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/base_time_window/static/description/icon.png differ diff --git a/base_time_window/static/description/index.html b/base_time_window/static/description/index.html new file mode 100644 index 000000000..edc04dae1 --- /dev/null +++ b/base_time_window/static/description/index.html @@ -0,0 +1,480 @@ + + + + + + +Base Time Window + + + +
+

Base Time Window

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

This module provides base classes and models to manage time windows through +time.window.mixin.

+

Table of contents

+ +
+

Usage

+

Example implementation for the mixin can be found in module test_base_time_window.

+

As a time window will always be linked to a related model thourgh a M2o relation, +when defining the new model inheriting the mixin, one should pay attention to the +following points in order to have the overlapping check work properly:

+
    +
  • Define class property _overlap_check_field: This must state the M2o field to +use for the to check of overlapping time window records linked to a specific +record of the related model.
  • +
  • Add the M2o field to the related model in the api.constrains:
  • +
+

For example:

+
+class PartnerTimeWindow(models.Model):
+    _name = 'partner.time.window'
+    _inherit = 'time.window.mixin'
+
+    partner_id = fields.Many2one(
+        res.partner', required=True, index=True, ondelete='cascade'
+    )
+
+    _overlap_check_field = 'partner_id'
+
+    @api.constrains('partner_id')
+    def check_window_no_overlaps(self):
+        return super().check_window_no_overlaps()
+
+
+
+

Known issues / Roadmap

+
    +
  • Storing times using float_time widget requires extra processing to ensure +computations are done in the right timezone, because the value is not stored +as UTC in the database, and must therefore be related to a tz field.

    +

    float_time in this sense should only be used for durations and not for a +“point in time” as this is always needs a Date for a timezone conversion to +be done properly. (Because a conversion from UTC to e.g. Europe/Brussels won’t +give the same result in winter or summer because of Daylight Saving Time).

    +

    Therefore the right move would be to use a resource.calendar to define time +windows using Datetime with recurrences.

    +
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
  • Camptocamp
  • +
+
+
+

Contributors

+ +

Trobz

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Camptocamp
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/setup/base_time_window/odoo/addons/base_time_window b/setup/base_time_window/odoo/addons/base_time_window new file mode 120000 index 000000000..d4e4791f4 --- /dev/null +++ b/setup/base_time_window/odoo/addons/base_time_window @@ -0,0 +1 @@ +../../../../base_time_window \ No newline at end of file diff --git a/setup/base_time_window/setup.py b/setup/base_time_window/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/base_time_window/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)