Skip to content

Commit ccc0455

Browse files
committed
Merge PR #219 into 18.0
Signed-off-by pedrobaeza
2 parents 2f320dd + 3b1a404 commit ccc0455

2 files changed

Lines changed: 69 additions & 9 deletions

File tree

resource_booking/models/resource_calendar.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
# Copyright 2022 Tecnativa - Pedro M. Baeza
33
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
44

5+
from datetime import datetime, time, timedelta
6+
57
from pytz import UTC
68

79
from odoo import api, fields, models
10+
from odoo.osv import expression
811

912
from odoo.addons.resource.models.utils import Intervals
1013

@@ -41,6 +44,9 @@ def _calendar_event_busy_intervals(
4144
"""Get busy meeting intervals."""
4245
assert start_dt.tzinfo
4346
assert end_dt.tzinfo
47+
interval_tz = start_dt.tzinfo
48+
start_local_date = start_dt.date()
49+
end_local_date = end_dt.date()
4450
start_dt, end_dt = (
4551
fields.Datetime.to_string(dt.astimezone(UTC)) for dt in (start_dt, end_dt)
4652
)
@@ -56,8 +62,19 @@ def _calendar_event_busy_intervals(
5662
return Intervals(intervals)
5763
# Simple domain to get all possibly conflicting events in a single
5864
# query; this reduces DB calls and helps the underlying recurring
59-
# system (in calendar.event) to work smoothly
60-
domain = [("start", "<=", end_dt), ("stop", ">=", start_dt)]
65+
# system (in calendar.event) to work smoothly. All-day events are
66+
# stored without start/stop timestamps in some flows, so OR in a
67+
# date-based predicate to catch them too.
68+
domain = expression.OR(
69+
[
70+
[("start", "<=", end_dt), ("stop", ">=", start_dt)],
71+
[
72+
("allday", "=", True),
73+
("start_date", "<=", end_local_date),
74+
("stop_date", ">=", start_local_date),
75+
],
76+
]
77+
)
6178
# Anyway up to this version, is more performant to restrict as much as possible
6279
# the events to avoid recurrent events.
6380
# TODO: in v14 we should test which approach remains the most performant
@@ -89,15 +106,27 @@ def _calendar_event_busy_intervals(
89106
):
90107
raise Busy
91108
except Busy:
92-
# Add the matched event as a busy interval
109+
# Add the matched event as a busy interval. All-day events
110+
# have no start/stop timestamps in some flows, so derive the
111+
# interval from start_date/stop_date in the analyzer's tz.
112+
if event.allday and event.start_date and event.stop_date:
113+
event_start = interval_tz.localize(
114+
datetime.combine(event.start_date, time.min)
115+
)
116+
event_stop = interval_tz.localize(
117+
datetime.combine(event.stop_date + timedelta(days=1), time.min)
118+
)
119+
else:
120+
event_start = fields.Datetime.context_timestamp(
121+
event, fields.Datetime.to_datetime(event.start)
122+
)
123+
event_stop = fields.Datetime.context_timestamp(
124+
event, fields.Datetime.to_datetime(event.stop)
125+
)
93126
intervals.append(
94127
(
95-
fields.Datetime.context_timestamp(
96-
event, fields.Datetime.to_datetime(event.start)
97-
),
98-
fields.Datetime.context_timestamp(
99-
event, fields.Datetime.to_datetime(event.stop)
100-
),
128+
event_start,
129+
event_stop,
101130
self.env["resource.calendar.leaves"],
102131
)
103132
)

resource_booking/tests/test_backend.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,37 @@ def test_recurring_event(self):
507507
rb_f.start = datetime(2021, 3, 1, 9)
508508
self.assertTrue(rb_f.combination_id)
509509

510+
def test_allday_event_blocks_booking_slot(self):
511+
"""All-day calendar events block booking slots that overlap their day.
512+
513+
Without all-day handling, the date-only event is invisible to the
514+
scheduling search (which queries on start/stop datetimes), so the
515+
booking is incorrectly accepted.
516+
"""
517+
user = self.users[0]
518+
self.env["calendar.event"].create(
519+
{
520+
"name": "PTO",
521+
"allday": True,
522+
"start_date": "2021-03-01",
523+
"stop_date": "2021-03-01",
524+
"user_id": user.id,
525+
"partner_ids": [Command.set([user.partner_id.id])],
526+
}
527+
)
528+
rb_f = Form(self.env["resource.booking"])
529+
rb_f.partner_ids.add(self.partner)
530+
rb_f.type_id = self.rbt
531+
# Force the user-resource combination so the all-day event has to block it
532+
rb_f.combination_auto_assign = False
533+
rb_f.combination_id = self.rbcs[0]
534+
rb_f.start = datetime(2021, 3, 1, 9)
535+
with self.assertRaises(ValidationError):
536+
rb_f.save()
537+
# Following Monday is fine
538+
rb_f.start = datetime(2021, 3, 8, 9)
539+
rb_f.save()
540+
510541
@mute_logger("odoo.models.unlink")
511542
def test_change_calendar_after_bookings_exist(self):
512543
"""Calendar changes can be done only if they introduce no conflicts."""

0 commit comments

Comments
 (0)