Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

152
base_time_window/README.rst Executable file
View File

@@ -0,0 +1,152 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
================
Base Time Window
================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d2feedfba0c1f2d3ff259174ed5416d5511b53ad3c6c6c2539226e6f74823251
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/license-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/18.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-18-0/server-tools-18-0-base_time_window
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=18.0
:alt: Try me on Runboat
|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:: 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 <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20base_time_window%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* ACSONE SA/NV
* Camptocamp
Contributors
------------
- Laurent Mignon <laurent.mignon@acsone.eu>
- Akim Juillerat <akim.juillerat@camptocamp.com>
- SodexisTeam <dev@sodexis.com>
Trobz
- Dung Tran <dungtd@trobz.com>
- Khoi (Kien Kim) <khoikk@trobz.com>
Other credits
-------------
The development of this module has been financially supported by:
- Camptocamp
The migration of this module from 17.0 to 18.0 was 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 <https://github.com/OCA/server-tools/tree/18.0/base_time_window>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

1
base_time_window/__init__.py Executable file
View File

@@ -0,0 +1 @@
from . import models

View File

@@ -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": "18.0.1.1.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,
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo noupdate="1">
<record model="time.weekday" id="time_weekday_monday">
<field name="name">0</field>
</record>
<record model="time.weekday" id="time_weekday_tuesday">
<field name="name">1</field>
</record>
<record model="time.weekday" id="time_weekday_wednesday">
<field name="name">2</field>
</record>
<record model="time.weekday" id="time_weekday_thursday">
<field name="name">3</field>
</record>
<record model="time.weekday" id="time_weekday_friday">
<field name="name">4</field>
</record>
<record model="time.weekday" id="time_weekday_saturday">
<field name="name">5</field>
</record>
<record model="time.weekday" id="time_weekday_sunday">
<field name="name">6</field>
</record>
</odoo>

View File

@@ -0,0 +1,150 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_time_window
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(end_time)s must be > %(start_time)s"
msgstr ""
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(record_name)s overlaps %(other_name)s"
msgstr ""
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
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__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
#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq
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,field_description:base_time_window.field_time_weekday__smart_search
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__smart_search
msgid "Smart Search"
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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "{days}: From {start} to {end}"
msgstr ""

156
base_time_window/i18n/es.po Executable file
View File

@@ -0,0 +1,156 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_time_window
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-11-11 15:39+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\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.17\n"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(end_time)s must be > %(start_time)s"
msgstr "%(end_time)s debe ser > %(start_time)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(record_name)s overlaps %(other_name)s"
msgstr "%(record_name)s se solapa con %(other_name)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "At least one time.weekday is required"
msgstr "Al menos se requiere una vez por 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 el"
#. 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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "Hour should be between 00 and 23"
msgstr "La hora debe estar comprendida 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 (identificación)"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_uid
msgid "Last Updated by"
msgstr "Actualizado por Última vez 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
#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq
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,field_description:base_time_window.field_time_weekday__smart_search
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__smart_search
msgid "Smart Search"
msgstr "Búsqueda Inteligente"
#. 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 "Tiempo 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 horaria 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 "A"
#. 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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "{days}: From {start} to {end}"
msgstr "{days}: De [start] a [end]"
#~ msgid "Last Modified on"
#~ msgstr "Última Modifiación el"

156
base_time_window/i18n/es_AR.po Executable file
View File

@@ -0,0 +1,156 @@
# 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: 2023-06-09 16:09+0000\n"
"Last-Translator: Ignacio Buioli <ibuioli@gmail.com>\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.17\n"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(end_time)s must be > %(start_time)s"
msgstr "%(end_time)s debe ser > %(start_time)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(record_name)s overlaps %(other_name)s"
msgstr "%(record_name)s se superpone %(other_name)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
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__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
#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq
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,field_description:base_time_window.field_time_weekday__smart_search
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__smart_search
msgid "Smart Search"
msgstr "Búsqueda Inteligente"
#. 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
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "{days}: From {start} to {end}"
msgstr "{days}: Desde {start} hasta {end}"
#~ msgid "Last Modified on"
#~ msgstr "Última modificación en"

156
base_time_window/i18n/it.po Executable file
View File

@@ -0,0 +1,156 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_time_window
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-08 17:35+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\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.17\n"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(end_time)s must be > %(start_time)s"
msgstr "%(end_time)s deve essere > %(start_time)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "%(record_name)s overlaps %(other_name)s"
msgstr "%(record_name)s si sovrappone a %(other_name)s"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "At least one time.weekday is required"
msgstr "È richiesto almeno un time.weekday"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__create_date
msgid "Created on"
msgstr "Creato il"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__4
msgid "Friday"
msgstr "Venerdì"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_start
msgid "From"
msgstr "Dal"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "Hour should be between 00 and 23"
msgstr "L'ora deve essere tra 00 e 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__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__0
msgid "Monday"
msgstr "Lunedì"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__name
msgid "Name"
msgstr "Nome"
#. module: base_time_window
#: model:ir.model.constraint,message:base_time_window.constraint_time_weekday_name_uniq
msgid "Name must be unique"
msgstr "Il nome deve essere univoco"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__5
msgid "Saturday"
msgstr "Sabato"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_weekday__smart_search
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__smart_search
msgid "Smart Search"
msgstr "Ricerca intelligente"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__6
msgid "Sunday"
msgstr "Domenica"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__3
msgid "Thursday"
msgstr "Giovedì"
#. module: base_time_window
#: model:ir.model,name:base_time_window.model_time_weekday
msgid "Time Week Day"
msgstr "Ora giorno della settimana"
#. module: base_time_window
#: model:ir.model,name:base_time_window.model_time_window_mixin
msgid "Time Window"
msgstr "Intervallo orario"
#. 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 "Intervallo orario giorno della settimana"
#. module: base_time_window
#: model:ir.model.fields,field_description:base_time_window.field_time_window_mixin__time_window_end
msgid "To"
msgstr "Al"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__1
msgid "Tuesday"
msgstr "Martedì"
#. module: base_time_window
#: model:ir.model.fields.selection,name:base_time_window.selection__time_weekday__name__2
msgid "Wednesday"
msgstr "Mercoledì"
#. module: base_time_window
#. odoo-python
#: code:addons/base_time_window/models/time_window_mixin.py:0
msgid "{days}: From {start} to {end}"
msgstr "{days}: dal {start} al {end}"
#~ msgid "Last Modified on"
#~ msgstr "Ultima modifica il"

View File

@@ -0,0 +1,2 @@
from . import time_weekday
from . import time_window_mixin

View File

@@ -0,0 +1,81 @@
# 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]
@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.env.registry.clear_cache()
return records
def write(self, vals):
result = super().write(vals)
self.env.registry.clear_cache()
return result
def unlink(self):
result = super().unlink()
self.env.registry.clear_cache()
return result
def _get_next_weekday_date(self, date_from=False, include_date_from=True):
"""Returns the next Date matching weekday
:param date_from: Date object from which we start searching for next weekday
:param include_date_from: Allows to return date from if it's same weekday
:return Date object matching the weekday
"""
self.ensure_one()
if not date_from:
date_from = fields.Date.today()
next_weekday_delta = int(self.name) - date_from.weekday()
if next_weekday_delta < 0:
next_weekday_delta += 7
elif next_weekday_delta == 0 and not include_date_from:
next_weekday_delta += 7
return fields.Date.add(date_from, days=next_weekday_delta)
def _get_next_weekdays_date(self, date_from=False, include_date_from=True):
return min(
[
weekday._get_next_weekday_date(
date_from=date_from, include_date_from=include_date_from
)
for weekday in self
]
)

View File

@@ -0,0 +1,133 @@
# 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(
self.env._(
"%(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(
self.env._("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(
self.env._(
"%(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 = self.env._("{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 = self.env._("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)

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,8 @@
- Laurent Mignon \<<laurent.mignon@acsone.eu>\>
- Akim Juillerat \<<akim.juillerat@camptocamp.com>\>
- SodexisTeam \<<dev@sodexis.com>\>
Trobz
- Dung Tran \<<dungtd@trobz.com>\>
- Khoi (Kien Kim) \<<khoikk@trobz.com>\>

View File

@@ -0,0 +1,7 @@
The development of this module has been financially supported by:
- Camptocamp
The migration of this module from 17.0 to 18.0 was financially supported by:
- Camptocamp

View File

@@ -0,0 +1,2 @@
This module provides base classes and models to manage time windows
through time.window.mixin.

View File

@@ -0,0 +1,13 @@
- 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.

View File

@@ -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:
``` 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()
```

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.model.access" id="time_weekday_access_read">
<field name="name">time.weekday access read</field>
<field name="model_id" ref="model_time_weekday" />
<field name="group_id" ref="base.group_user" />
<field name="perm_read" eval="1" />
<field name="perm_create" eval="0" />
<field name="perm_write" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,501 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="base-time-window">
<h1>Base Time Window</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d2feedfba0c1f2d3ff259174ed5416d5511b53ad3c6c6c2539226e6f74823251
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-tools/tree/18.0/base_time_window"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-base_time_window"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-tools&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module provides base classes and models to manage time windows
through time.window.mixin.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-7">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<p>Example implementation for the mixin can be found in module
test_base_time_window.</p>
<p>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:</p>
<ul class="simple">
<li>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.</li>
<li>Add the M2o field to the related model in the `api.constrains`:</li>
</ul>
<p>For example:</p>
<pre class="code python literal-block">
<span class="k">class</span><span class="w"> </span><span class="nc">PartnerTimeWindow</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span><span class="w">
</span> <span class="n">_name</span> <span class="o">=</span> <span class="s1">'partner.time.window'</span><span class="w">
</span> <span class="n">_inherit</span> <span class="o">=</span> <span class="s1">'time.window.mixin'</span><span class="w">
</span> <span class="n">partner_id</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Many2one</span><span class="p">(</span><span class="w">
</span> <span class="n">res</span><span class="o">.</span><span class="n">partner</span><span class="s1">', required=True, index=True, ondelete='</span><span class="n">cascade</span><span class="s1">'</span><span class="w">
</span> <span class="p">)</span><span class="w">
</span> <span class="n">_overlap_check_field</span> <span class="o">=</span> <span class="s1">'partner_id'</span><span class="w">
</span> <span class="nd">&#64;api</span><span class="o">.</span><span class="n">constrains</span><span class="p">(</span><span class="s1">'partner_id'</span><span class="p">)</span><span class="w">
</span> <span class="k">def</span><span class="w"> </span><span class="nf">check_window_no_overlaps</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w">
</span> <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">check_window_no_overlaps</span><span class="p">()</span>
</pre>
</div>
<div class="section" id="known-issues-roadmap">
<h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
<ul>
<li><p class="first">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.</p>
<p>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 wont give the same result in winter or summer because
of Daylight Saving Time).</p>
<p>Therefore the right move would be to use a resource.calendar to define
time windows using Datetime with recurrences.</p>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-tools/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20base_time_window%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
<ul class="simple">
<li>ACSONE SA/NV</li>
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<ul class="simple">
<li>Laurent Mignon &lt;<a class="reference external" href="mailto:laurent.mignon&#64;acsone.eu">laurent.mignon&#64;acsone.eu</a>&gt;</li>
<li>Akim Juillerat &lt;<a class="reference external" href="mailto:akim.juillerat&#64;camptocamp.com">akim.juillerat&#64;camptocamp.com</a>&gt;</li>
<li>SodexisTeam &lt;<a class="reference external" href="mailto:dev&#64;sodexis.com">dev&#64;sodexis.com</a>&gt;</li>
</ul>
<p>Trobz</p>
<ul class="simple">
<li>Dung Tran &lt;<a class="reference external" href="mailto:dungtd&#64;trobz.com">dungtd&#64;trobz.com</a>&gt;</li>
<li>Khoi (Kien Kim) &lt;<a class="reference external" href="mailto:khoikk&#64;trobz.com">khoikk&#64;trobz.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-7">Other credits</a></h3>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Camptocamp</li>
</ul>
<p>The migration of this module from 17.0 to 18.0 was financially supported
by:</p>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/18.0/base_time_window">OCA/server-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,3 @@
from . import test_time_weekday
from . import test_time_window_mixin
from . import test_weekday

View File

@@ -0,0 +1,12 @@
from odoo import fields, models
class TestTimeWindowModel(models.Model):
_name = "test.time.window.model"
_description = "Test Time Window Model"
_inherit = "time.window.mixin"
_time_window_overlap_check_field = "partner_id"
partner_id = fields.Many2one(
"res.partner", required=True, index=True, ondelete="cascade"
)

View File

@@ -0,0 +1,16 @@
# Copyright 2024 sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
class TestTimeWeekday(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.time_weekday = cls.env["time.weekday"]
cls.time_weekday_saturday = cls.time_weekday.search([("name", "=", "5")])
def test_weekday_delete(cls):
cls.time_weekday._get_id_by_name(cls.time_weekday_saturday.name)
cls.time_weekday_saturday.unlink()

View File

@@ -0,0 +1,88 @@
from odoo_test_helper import FakeModelLoader
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestTimeWindowMixin(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.customer1 = cls.env["res.partner"].create({"name": "Test1"})
cls.customer2 = cls.env["res.partner"].create({"name": "Test2"})
cls.customer3 = cls.env["res.partner"].create({"name": "Test3"})
cls.weekday1 = cls.env["time.weekday"].search([("name", "=", "1")])
cls.weekday2 = cls.env["time.weekday"].search([("name", "=", "2")])
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.loader.backup_registry()
from .test_models import TestTimeWindowModel
cls.loader.update_registry((TestTimeWindowModel,))
@classmethod
def tearDownClass(cls):
cls.loader.restore_registry()
super().tearDownClass()
def test_time_window_no_overlap(self):
with self.assertRaises(ValidationError):
self.record1 = self.env["test.time.window.model"].create(
{
"partner_id": self.customer1.id,
"time_window_start": 9.0,
"time_window_end": 12.0,
}
)
with self.assertRaises(ValidationError):
self.env["test.time.window.model"].create(
{
"partner_id": self.customer2.id,
"time_window_start": 15.0,
"time_window_end": 13.0,
"time_window_weekday_ids": self.weekday1.ids,
}
)
self.record2 = self.env["test.time.window.model"].create(
{
"partner_id": self.customer3.id,
"time_window_start": 13.0,
"time_window_end": 15.0,
"time_window_weekday_ids": self.weekday1.ids,
}
)
self.assertTrue(self.record2)
with self.assertRaises(ValidationError):
self.record3 = self.env["test.time.window.model"].create(
{
"partner_id": self.customer3.id,
"time_window_start": 15.0,
"time_window_end": 25.0,
"time_window_weekday_ids": self.weekday1.ids,
}
)
with self.assertRaises(ValidationError):
self.record4 = self.env["test.time.window.model"].create(
{
"partner_id": self.customer3.id,
"time_window_start": 0.998,
"time_window_end": 22.0,
"time_window_weekday_ids": self.weekday1.ids,
}
)
with self.assertRaises(ValidationError):
self.record5 = self.env["test.time.window.model"].create(
{
"partner_id": self.customer3.id,
"time_window_start": 25,
"time_window_end": 22.0,
"time_window_weekday_ids": self.weekday1.ids,
}
)

View File

@@ -0,0 +1,61 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo.fields import Date
from odoo.tests.common import TransactionCase
class TestWeekday(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
time_weekday_mapping = dict(cls.env["time.weekday"]._fields["name"].selection)
for val, name in time_weekday_mapping.items():
setattr(
cls,
name.lower(),
cls.env["time.weekday"].search([("name", "=", val)], limit=1),
)
def test_next_weekday_date(self):
# 2024-01-01 is Monday, next Monday (including date_from) is 2024-01-01
self.assertEqual(
self.monday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-01"),
)
# 2024-01-01 is Monday, next Monday (excluding date_from) is 2024-01-08
self.assertEqual(
self.monday._get_next_weekday_date(
Date.to_date("2024-01-01"), include_date_from=False
),
Date.to_date("2024-01-08"),
)
# 2024-01-01 is Monday, next Tuesday is 2024-01-02
self.assertEqual(
self.tuesday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-02"),
)
# 2024-01-01 is Monday, next Wednesday is 2024-01-03
self.assertEqual(
self.wednesday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-03"),
)
# 2024-01-01 is Monday, next Thursday is 2024-01-04
self.assertEqual(
self.thursday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-04"),
)
# 2024-01-01 is Monday, next Friday is 2024-01-05
self.assertEqual(
self.friday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-05"),
)
# 2024-01-01 is Monday, next Saturday is 2024-01-06
self.assertEqual(
self.saturday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-06"),
)
# 2024-01-01 is Monday, next Sunday is 2024-01-07
self.assertEqual(
self.sunday._get_next_weekday_date(Date.to_date("2024-01-01")),
Date.to_date("2024-01-07"),
)