Skip to content

[13.0][ADD] base_time_window #1798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 15, 2020
1 change: 1 addition & 0 deletions base_time_window/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
17 changes: 17 additions & 0 deletions base_time_window/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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": "13.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,
}
34 changes: 34 additions & 0 deletions base_time_window/data/time_weekday.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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>
2 changes: 2 additions & 0 deletions base_time_window/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import base_time_weekday
from . import base_time_window
67 changes: 67 additions & 0 deletions base_time_window/models/base_time_weekday.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# 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
def create(self, vals):
result = super().create(vals)
self._get_id_by_name.clear_cache(self)
return result

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
113 changes: 113 additions & 0 deletions base_time_window/models/base_time_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# 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


class TimeWindow(models.AbstractModel):

_name = "time.window.mixin"
_description = "Time Window"
_order = "start"

# TODO patch api.constrains with field here?
_overlap_check_field = False

start = fields.Float("From", required=True)
end = fields.Float("To", required=True)
weekday_ids = fields.Many2many(
comodel_name="time.weekday", required=True
)

@api.constrains("start", "end", "weekday_ids")
def check_window_no_overlaps(self):
weekdays_field = self._fields["weekday_ids"]
for record in self:
if record.start > record.end:
raise ValidationError(
_("%s must be > %s")
% (
self.float_to_time_repr(record.end),
self.float_to_time_repr(record.start),
)
)
# 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.start::numeric, w.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.start,
end=record.end,
window_id=record.id,
weekday_ids=tuple(record.weekday_ids.ids),
check_field=AsIs(self._overlap_check_field),
check_field_id=record[self._overlap_check_field].id,
),
)
res = self.env.cr.fetchall()
if res:
other = self.browse(res[0][0])
raise ValidationError(
_("%s overlaps %s")
% (record.display_name, other.display_name)
)

@api.depends("start", "end", "weekday_ids")
def _compute_display_name(self):
for record in self:
record.display_name = _("{days}: From {start} to {end}").format(
days=", ".join(record.weekday_ids.mapped("display_name")),
start=self.float_to_time_repr(record.start),
end=self.float_to_time_repr(record.end),
)

@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_start_time(self):
self.float_to_time(self.start)

def get_end_time(self):
self.float_to_time(self.end)
2 changes: 2 additions & 0 deletions base_time_window/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Laurent Mignon <[email protected]>
* Akim Juillerat <[email protected]>
1 change: 1 addition & 0 deletions base_time_window/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module provides base classes to manage time windows.
15 changes: 15 additions & 0 deletions base_time_window/security/ir.model.access.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?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>