Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions resource_booking/models/resource_booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@ def _get_available_slots(self, start_dt, end_dt):
start_dt = max(
start_dt, now + timedelta(hours=self.type_id.modifications_deadline)
)
# Cap the search window when the booking type restricts how far in
# advance a slot may start.
max_advance_days = self.type_id.max_advance_booking_days
max_start_dt = False
if max_advance_days:
max_start_dt = now + timedelta(days=max_advance_days)
end_dt = min(end_dt, max_start_dt + booking_duration)
# available_intervals should start with the beginning of the work day,
# to compute each slot based on the beginning of the work day.
workday_min = start_dt.replace(hour=0, minute=0, second=0, microsecond=0)
Expand All @@ -575,6 +582,7 @@ def _get_available_slots(self, start_dt, end_dt):
test_stop = test_start + booking_duration
if (
test_start >= start_dt
and (not max_start_dt or test_start <= max_start_dt)
and test_start >= available_start
and test_stop <= available_stop
):
Expand Down
13 changes: 13 additions & 0 deletions resource_booking/models/resource_booking_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class ResourceBookingType(models.Model):
_description = "Resource Booking Type"
_sql_constraints = [
("duration_positive", "CHECK(duration > 0)", "Duration must be positive."),
(
"max_advance_booking_days_nonnegative",
"CHECK(max_advance_booking_days >= 0)",
"Maximum advance booking days must be zero or positive.",
),
]

active = fields.Boolean(default=True)
Expand Down Expand Up @@ -64,6 +69,14 @@ class ResourceBookingType(models.Model):
default=0.5, # 30 minutes
help=("Interval offered to start each resource booking."),
)
max_advance_booking_days = fields.Integer(
string="Maximum Advance Booking Days",
default=0,
help=(
"Limit suggested booking starts to within this many days from now. "
"Set to 0 to allow the full availability calendar range."
),
)
location = fields.Char()
videocall_location = fields.Char(string="Meeting URL")
modifications_deadline = fields.Float(
Expand Down
40 changes: 40 additions & 0 deletions resource_booking/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,46 @@ def test_free_slots_with_different_type_and_booking_durations(self):
},
)

def test_max_advance_booking_days_caps_slots(self):
"""max_advance_booking_days caps suggested starts to N days from now."""
# Now is 2021-02-26 (frozen). Cap at 4 days -> last suggestable start
# is 2021-03-02.
self.rbt.max_advance_booking_days = 4
rb = self.env["resource.booking"].create(
{"partner_ids": [(4, self.partner.id)], "type_id": self.rbt.id}
)
slots = rb._get_available_slots(
utc.localize(datetime(2021, 2, 28)),
utc.localize(datetime(2021, 3, 31)),
)
all_starts = [s for day in slots.values() for s in day]
self.assertTrue(all_starts, "max_advance cap should not strand all slots")
# 2021-03-08 (Monday) is past the 4-day cap, so it must be excluded
self.assertNotIn(date(2021, 3, 8), slots)
# 2021-03-02 (Tuesday) is within the cap and the calendar (Mon/Tue)
self.assertIn(date(2021, 3, 2), slots)

def test_max_advance_booking_days_zero_keeps_full_range(self):
"""A zero value (default) keeps the existing unbounded behavior."""
self.assertEqual(self.rbt.max_advance_booking_days, 0)
rb = self.env["resource.booking"].create(
{"partner_ids": [(4, self.partner.id)], "type_id": self.rbt.id}
)
slots = rb._get_available_slots(
utc.localize(datetime(2021, 2, 28)),
utc.localize(datetime(2021, 3, 31)),
)
self.assertIn(date(2021, 3, 8), slots)

def test_max_advance_booking_days_constraint(self):
"""Negative max_advance_booking_days is rejected by SQL constraint."""
from psycopg2 import IntegrityError

with self.assertRaises(IntegrityError), mute_logger("odoo.sql_db"):
with self.env.cr.savepoint():
self.rbt.write({"max_advance_booking_days": -1})
self.env.flush_all()

@mute_logger("odoo.models.unlink")
def test_location(self):
"""Location across records works as expected."""
Expand Down
1 change: 1 addition & 0 deletions resource_booking/views/resource_booking_type_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
/>
<field name="duration" widget="float_time" />
<field name="slot_duration" widget="float_time" />
<field name="max_advance_booking_days" />
<field name="modifications_deadline" widget="float_time" />
<field name="resource_calendar_id" />
</group>
Expand Down
Loading