avoir une heure de départ des réservations flottante ou fixe (#56284) #47

Merged
bdauvergne merged 3 commits from wip/56284-Idee-avoir-un-delai-de-reservati into main 2023-03-28 11:09:28 +02:00
9 changed files with 255 additions and 40 deletions

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.18 on 2023-03-08 17:20
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agendas', '0147_check_future_events'),
]
operations = [
migrations.AddField(
model_name='agenda',
name='minimal_booking_time',
field=models.TimeField(
blank=True, default=datetime.time(0, 0), null=True, verbose_name='Minimal booking time'
),
),
]

View File

@ -262,6 +262,15 @@ class Agenda(models.Model):
null=True, null=True,
blank=True, blank=True,
) )
minimal_booking_time = models.TimeField(
verbose_name=_('Minimal booking time'),
default=datetime.time(0, 0, 0), # booking is possible starting and finishin at 00:00
help_text=_(
'Ex.: 08:00:00. If left empty, available events will be those that are later than the current time.'
),
null=True,
blank=True,
)
class Meta: class Meta:
ordering = ['label'] ordering = ['label']
@ -634,31 +643,27 @@ class Agenda(models.Model):
if self.maximal_booking_delay is None: if self.maximal_booking_delay is None:
return None return None
# compute middle of today with localtime # reference is now, in local timezone
# 28 Mar 2021 12:00 +01:00 t = localtime(now())
t = localtime(now()).replace(hour=12, minute=0)
# advance of self.maximal_booking_delay - 1 days # add delay
# 28 Mar 2021 12:00 +01:00 == 28 Mars 2021 13:00 +02:00 as DST happend on 28 Mar 2021.
t += datetime.timedelta(days=self.maximal_booking_delay) t += datetime.timedelta(days=self.maximal_booking_delay)
# move to midnight of the day before, DST happen between 2h/3h so it # replace time if needed
# always exists because +/- timedelta does not move the timezone, only if self.minimal_booking_time:
# localtime() does it. t = datetime.datetime.combine(t.date(), self.minimal_booking_time, tzinfo=t.tzinfo)
# 27 Mar 2021 12:00 +01:00 == 28 Mars 2021 01:00 +02:00 # t could not exist, recompute it as an existing datetime by converting to UTC then to localtime
return localtime(t).replace(hour=0, minute=0, second=0, microsecond=0) return localtime(t.astimezone(utc))
@functional.cached_property @functional.cached_property
def min_booking_datetime(self): def min_booking_datetime(self):
if self.minimal_booking_delay is None: if self.minimal_booking_delay is None:
return None return None
# compute middle of today with localtime # reference is now, in local timezone
# 28 Mar 2021 12:00 +01:00 t = localtime(now())
t = localtime(now()).replace(hour=12, minute=0)
# advance of self.minimal_booking_delay - 1 days # add delay
# 28 Mar 2021 12:00 +01:00 == 28 Mars 2021 13:00 +02:00 as DST happend on 28 Mar 2021.
if settings.WORKING_DAY_CALENDAR is not None and self.minimal_booking_delay_in_working_days: if settings.WORKING_DAY_CALENDAR is not None and self.minimal_booking_delay_in_working_days:
source_class = import_string(settings.WORKING_DAY_CALENDAR) source_class = import_string(settings.WORKING_DAY_CALENDAR)
calendar = source_class() calendar = source_class()
@ -666,11 +671,11 @@ class Agenda(models.Model):
else: else:
t += datetime.timedelta(days=self.minimal_booking_delay) t += datetime.timedelta(days=self.minimal_booking_delay)
# move to midnight of the day before, DST happen between 2h/3h so it # replace time if needed
# always exists because +/- timedelta does not move the timezone, only if self.minimal_booking_time:
# localtime() does it. t = datetime.datetime.combine(t.date(), self.minimal_booking_time, tzinfo=t.tzinfo)
# 27 Mar 2021 12:00 +01:00 == 28 Mars 2021 01:00 +02:00 # t could not exist, recompute it as an existing datetime by converting to UTC then to localtime
return localtime(t).replace(hour=0, minute=0, second=0, microsecond=0) return localtime(t.astimezone(utc))
def get_open_events( def get_open_events(
self, self,

View File

@ -521,6 +521,7 @@ class AgendaSerializer(serializers.ModelSerializer):
'minimal_booking_delay', 'minimal_booking_delay',
'minimal_booking_delay_in_working_days', 'minimal_booking_delay_in_working_days',
'maximal_booking_delay', 'maximal_booking_delay',
'minimal_booking_time',
'anonymize_delay', 'anonymize_delay',
'edit_role', 'edit_role',
'view_role', 'view_role',

View File

@ -361,6 +361,7 @@ def get_agenda_detail(request, agenda, check_events=False):
'kind': agenda.kind, 'kind': agenda.kind,
'minimal_booking_delay': agenda.minimal_booking_delay, 'minimal_booking_delay': agenda.minimal_booking_delay,
'maximal_booking_delay': agenda.maximal_booking_delay, 'maximal_booking_delay': agenda.maximal_booking_delay,
'minimal_booking_time': agenda.minimal_booking_time,
'edit_role': agenda.edit_role.name if agenda.edit_role else None, 'edit_role': agenda.edit_role.name if agenda.edit_role else None,
'view_role': agenda.view_role.name if agenda.view_role else None, 'view_role': agenda.view_role.name if agenda.view_role else None,
'category': agenda.category.slug if agenda.category else None, 'category': agenda.category.slug if agenda.category else None,

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: chrono 0\n" "Project-Id-Version: chrono 0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-16 17:02+0100\n" "POT-Creation-Date: 2023-03-23 10:33+0100\n"
"PO-Revision-Date: 2023-03-02 18:58+0100\n" "PO-Revision-Date: 2023-03-23 10:35+0100\n"
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n" "Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
"Language: French\n" "Language: French\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -207,8 +207,7 @@ msgstr ""
#: agendas/models.py #: agendas/models.py
msgid "Enable the check of bookings when event has not passed" msgid "Enable the check of bookings when event has not passed"
msgstr "" msgstr "Activer le pointage des réservations sur les événements à venir"
"Activer le pointage des réservations sur les événements à venir"
#: agendas/models.py #: agendas/models.py
msgid "Filters" msgid "Filters"
@ -248,6 +247,17 @@ msgstr ""
msgid "Events type" msgid "Events type"
msgstr "Type dévénements" msgstr "Type dévénements"
#: agendas/models.py
msgid "Minimal booking time"
msgstr "Heure de réservation minimale"
#: agendas/models.py
msgid ""
"Ex.: 08:00:00. If left empty, available events will be those that are later "
"than the current time."
msgstr ""
"Ex.: 08:00:00. Si ce champ est laissé vide alors les évènements ou rendez-vous disponibles à la réservation sont ceux dont le début est plus tard que l'heure courante, en prenant en compte les délais minimaux et maximaux de réservation en jours"
#: agendas/models.py #: agendas/models.py
#, python-format #, python-format
msgid "Missing \"%s\" resource" msgid "Missing \"%s\" resource"
@ -2182,6 +2192,14 @@ msgstr[1] "%(count)s jours"
msgid "Maximal booking delay:" msgid "Maximal booking delay:"
msgstr "Délai de réservation maximal :" msgstr "Délai de réservation maximal :"
#: manager/templates/chrono/manager_agenda_settings.html
msgid "Minimal booking time:"
msgstr "Heure de réservation minimale :"
#: manager/templates/chrono/manager_agenda_settings.html
msgid "current time"
msgstr "heure actuelle"
#: manager/templates/chrono/manager_agenda_settings.html #: manager/templates/chrono/manager_agenda_settings.html
msgid "Edit Role:" msgid "Edit Role:"
msgstr "Rôle dédition :" msgstr "Rôle dédition :"
@ -2633,8 +2651,7 @@ msgstr ""
#: manager/templates/chrono/manager_events_agenda_settings.html #: manager/templates/chrono/manager_events_agenda_settings.html
msgid "Enable the check of bookings when event has not passed:" msgid "Enable the check of bookings when event has not passed:"
msgstr "" msgstr "Activer le pointage des réservations sur les événements à venir :"
"Activer le pointage des réservations sur les événements à venir :"
#: manager/templates/chrono/manager_events_agenda_settings.html #: manager/templates/chrono/manager_events_agenda_settings.html
#, python-format #, python-format

View File

@ -129,6 +129,7 @@ class AgendaBookingDelaysForm(forms.ModelForm):
'minimal_booking_delay', 'minimal_booking_delay',
'minimal_booking_delay_in_working_days', 'minimal_booking_delay_in_working_days',
'maximal_booking_delay', 'maximal_booking_delay',
'minimal_booking_time',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -89,6 +89,10 @@
{% if agenda.maximal_booking_delay is not None %} {% if agenda.maximal_booking_delay is not None %}
{% blocktrans count count=agenda.maximal_booking_delay %}{{ count }} day{% plural %}{{ count }} days{% endblocktrans %} {% blocktrans count count=agenda.maximal_booking_delay %}{{ count }} day{% plural %}{{ count }} days{% endblocktrans %}
{% else %}<i>{% trans "undefined" %}</i>{% endif %}</li> {% else %}<i>{% trans "undefined" %}</i>{% endif %}</li>
{% if agenda.minimal_booking_delay is not None or agenda.maximal_booking_delay is not None %}
<li>{% trans "Minimal booking time:" %}
{% if agenda.minimal_booking_time is not None %}{{ agenda.minimal_booking_time }}{% else %}{% trans "current time" %}{% endif %}</li>
{% endif %}
</ul> </ul>
<div class="panel--buttons"> <div class="panel--buttons">
<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-delays' pk=object.id %}">{% trans 'Configure' %}</a> <a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-delays' pk=object.id %}">{% trans 'Configure' %}</a>

View File

@ -66,6 +66,7 @@ def test_agendas_api(app):
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False, 'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': 'Edit', 'edit_role': 'Edit',
'view_role': None, 'view_role': None,
'category': 'category-a', 'category': 'category-a',
@ -85,6 +86,7 @@ def test_agendas_api(app):
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False, 'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': None, 'edit_role': None,
'view_role': None, 'view_role': None,
'category': 'category-a', 'category': 'category-a',
@ -104,6 +106,7 @@ def test_agendas_api(app):
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False, 'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': None, 'edit_role': None,
'view_role': None, 'view_role': None,
'category': None, 'category': None,
@ -121,6 +124,7 @@ def test_agendas_api(app):
'slug': 'foo-bar-meeting', 'slug': 'foo-bar-meeting',
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': None, 'edit_role': None,
'view_role': 'View', 'view_role': 'View',
'category': 'category-b', 'category': 'category-b',
@ -144,6 +148,7 @@ def test_agendas_api(app):
'slug': 'foo-bar-meeting-2', 'slug': 'foo-bar-meeting-2',
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': None, 'edit_role': None,
'view_role': None, 'view_role': None,
'category': None, 'category': None,
@ -164,6 +169,7 @@ def test_agendas_api(app):
'slug': 'virtual-agenda', 'slug': 'virtual-agenda',
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'maximal_booking_delay': 56, 'maximal_booking_delay': 56,
'minimal_booking_time': '00:00:00',
'edit_role': 'Edit', 'edit_role': 'Edit',
'view_role': 'View', 'view_role': 'View',
'category': None, 'category': None,
@ -411,6 +417,7 @@ def test_virtual_agenda_detail(app, virtual_meetings_agenda):
'slug': 'virtual-agenda', 'slug': 'virtual-agenda',
'minimal_booking_delay': None, 'minimal_booking_delay': None,
'maximal_booking_delay': None, 'maximal_booking_delay': None,
'minimal_booking_time': '00:00:00',
'edit_role': None, 'edit_role': None,
'view_role': None, 'view_role': None,
'category': None, 'category': None,
@ -457,25 +464,25 @@ def test_agenda_api_delete_busy(app, user):
assert not Agenda.objects.exists() assert not Agenda.objects.exists()
@pytest.mark.freeze_time('2021-07-09') @pytest.mark.freeze_time('2021-07-09T08:00:00.0+02:00')
def test_add_agenda(app, user, settings): def test_add_agenda(app, user, settings):
events_type = EventsType.objects.create(label='Type A') events_type = EventsType.objects.create(label='Type A')
category_a = Category.objects.create(label='Category A') category_a = Category.objects.create(label='Category A')
api_url = '/api/agenda/' api_url = '/api/agenda/'
# no authentication # no authentication
resp = app.post(api_url, status=401) resp = app.post_json(api_url, status=401)
assert resp.json['detail'] == 'Authentication credentials were not provided.' assert resp.json['detail'] == 'Authentication credentials were not provided.'
# wrong password # wrong password
app.authorization = ('Basic', ('john.doe', 'wrong')) app.authorization = ('Basic', ('john.doe', 'wrong'))
resp = app.post(api_url, status=401) resp = app.post_json(api_url, status=401)
assert resp.json['detail'] == 'Invalid username/password.' assert resp.json['detail'] == 'Invalid username/password.'
app.authorization = ('Basic', ('john.doe', 'password')) app.authorization = ('Basic', ('john.doe', 'password'))
# missing fields # missing fields
resp = app.post(api_url, status=400) resp = app.post_json(api_url, status=400)
assert resp.json['err'] assert resp.json['err']
assert resp.json['errors'] == {'label': ['This field is required.'], 'slug': ['This field is required.']} assert resp.json['errors'] == {'label': ['This field is required.'], 'slug': ['This field is required.']}
@ -492,7 +499,7 @@ def test_add_agenda(app, user, settings):
'category': 'oups', 'category': 'oups',
'events_type': 'oups', 'events_type': 'oups',
} }
resp = app.post(api_url, params=params, status=400) resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err'] assert resp.json['err']
assert resp.json['errors'] == { assert resp.json['errors'] == {
'kind': ['"oups" is not a valid choice.'], 'kind': ['"oups" is not a valid choice.'],
@ -512,7 +519,7 @@ def test_add_agenda(app, user, settings):
'label': 'foo', 'label': 'foo',
'slug': meeting_agenda.slug, 'slug': meeting_agenda.slug,
} }
resp = app.post(api_url, params=params, status=400) resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err'] assert resp.json['err']
assert resp.json['errors'] == {'slug': ['agenda with this Identifier already exists.']} assert resp.json['errors'] == {'slug': ['agenda with this Identifier already exists.']}
@ -523,7 +530,7 @@ def test_add_agenda(app, user, settings):
'kind': 'meetings', 'kind': 'meetings',
'minimal_booking_delay_in_working_days': True, 'minimal_booking_delay_in_working_days': True,
} }
resp = app.post(api_url, params=params, status=400) resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err'] assert resp.json['err']
assert resp.json['errors'] == { assert resp.json['errors'] == {
'minimal_booking_delay_in_working_days': ['Option not available on meetings agenda'] 'minimal_booking_delay_in_working_days': ['Option not available on meetings agenda']
@ -534,7 +541,7 @@ def test_add_agenda(app, user, settings):
'kind': 'meetings', 'kind': 'meetings',
'events_type': 'type-a', 'events_type': 'type-a',
} }
resp = app.post(api_url, params=params, status=400) resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err'] assert resp.json['err']
assert resp.json['errors'] == {'events_type': ['Option not available on meetings agenda']} assert resp.json['errors'] == {'events_type': ['Option not available on meetings agenda']}
@ -543,7 +550,7 @@ def test_add_agenda(app, user, settings):
'label': 'My Agenda', 'label': 'My Agenda',
'slug': 'my-agenda', 'slug': 'my-agenda',
} }
resp = app.post(api_url, params=params) resp = app.post_json(api_url, params=params)
assert not resp.json['err'] assert not resp.json['err']
assert len(resp.json['data']) == 1 assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='my-agenda') agenda = Agenda.objects.get(slug='my-agenda')
@ -567,11 +574,14 @@ def test_add_agenda(app, user, settings):
'mark_event_checked_auto': True, 'mark_event_checked_auto': True,
'disable_check_update': False, 'disable_check_update': False,
} }
resp = app.post(api_url, params=params) resp = app.post_json(api_url, params=params)
assert not resp.json['err'] assert not resp.json['err']
assert len(resp.json['data']) == 1 assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='foo-meetings') agenda = Agenda.objects.get(slug='foo-meetings')
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10) assert agenda.min_booking_datetime == localtime(now()).replace(
day=10, hour=0, minute=0, second=0, microsecond=0
)
assert agenda.minimal_booking_time == datetime.time(0)
assert agenda.edit_role == edit_group assert agenda.edit_role == edit_group
assert agenda.view_role == view_group assert agenda.view_role == view_group
assert agenda.category == category_a assert agenda.category == category_a
@ -587,6 +597,7 @@ def test_add_agenda(app, user, settings):
'minimal_booking_delay': 1, 'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': True, 'minimal_booking_delay_in_working_days': True,
'maximal_booking_delay': 3, 'maximal_booking_delay': 3,
'minimal_booking_time': None,
'anonymize_delay': 30, 'anonymize_delay': 30,
'edit_role': 'Edit', 'edit_role': 'Edit',
'view_role': 'View', 'view_role': 'View',
@ -596,13 +607,14 @@ def test_add_agenda(app, user, settings):
'disable_check_update': True, 'disable_check_update': True,
'booking_check_filters': 'foo,bar,baz', 'booking_check_filters': 'foo,bar,baz',
} }
resp = app.post(api_url, params=params) resp = app.post_json(api_url, params=params)
assert not resp.json['err'] assert not resp.json['err']
assert len(resp.json['data']) == 1 assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='foo-events') agenda = Agenda.objects.get(slug='foo-events')
assert agenda.edit_role == edit_group assert agenda.edit_role == edit_group
assert agenda.view_role == view_group assert agenda.view_role == view_group
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 12) assert agenda.min_booking_datetime == localtime(now()).replace(day=12)
assert agenda.minimal_booking_time is None
assert agenda.category == category_a assert agenda.category == category_a
assert agenda.events_type == events_type assert agenda.events_type == events_type
assert agenda.mark_event_checked_auto is False assert agenda.mark_event_checked_auto is False

View File

@ -36,7 +36,7 @@ from chrono.agendas.models import (
UnavailabilityCalendar, UnavailabilityCalendar,
VirtualMember, VirtualMember,
) )
from chrono.utils.timezone import localtime, make_aware, now from chrono.utils.timezone import localtime, make_aware, make_naive, now
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -268,6 +268,158 @@ def test_agenda_maximal_booking_delay(freezer):
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2021, 11, 2, 0, 0, 0)) assert agenda.max_booking_datetime == make_aware(datetime.datetime(2021, 11, 2, 0, 0, 0))
def delay_parameter_to_label(argvalue):
if isinstance(argvalue, str):
return argvalue
return repr(argvalue)
@pytest.mark.parametrize(
'current_time,min_booking_datetime',
[
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 7)),
# summer DST change on sunday 28th
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 29, 1, 30)),
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 2, 30)),
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 3, 30)),
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
# winter DST change on sunday 31th
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 1, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 1, 30)),
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 2, 30)),
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 2, 30)),
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 3, 30)),
],
ids=delay_parameter_to_label,
)
def test_agenda_minimal_booking_delay_no_minimal_booking_time(freezer, current_time, min_booking_datetime):
freezer.move_to(current_time)
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=4, minimal_booking_time=None)
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
@pytest.mark.parametrize(
'current_time,min_booking_datetime',
[
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 16, 8)),
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 24, 7)),
# summer DST change on sunday 28th
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 31, 1, 30)),
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 31, 2, 30)),
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 31, 3, 30)),
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
# winter DST change on sunday 31th
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 5, 1, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 5, 1, 30)),
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 5, 2, 30)),
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 5, 3, 30)),
],
ids=delay_parameter_to_label,
)
def test_agenda_minimal_booking_delay_in_working_days_no_minimal_booking_time(
settings, freezer, current_time, min_booking_datetime
):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
freezer.move_to(current_time)
agenda = Agenda.objects.create(
label='Agenda',
minimal_booking_delay=4,
minimal_booking_time=None,
minimal_booking_delay_in_working_days=True,
)
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
@pytest.mark.parametrize(
'current_time,min_booking_datetime',
[
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 8)),
# summer DST change on sunday
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
# winter DST change on sunday
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
],
ids=delay_parameter_to_label,
)
def test_agenda_minimal_booking_delay_minimal_booking_time_at_8(freezer, current_time, min_booking_datetime):
freezer.move_to(current_time)
agenda = Agenda.objects.create(
label='Agenda', minimal_booking_delay=4, minimal_booking_time=datetime.time(8)
)
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
@pytest.mark.parametrize(
'current_time,max_booking_datetime',
[
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 7)),
# summer DST change on sunday 28th
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 29, 1, 30)),
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 2, 30)),
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 3, 30)),
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
# winter DST change on sunday 31th
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 1, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 1, 30)),
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 2, 30)),
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 2, 30)),
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 3, 30)),
],
ids=delay_parameter_to_label,
)
def test_agenda_maximal_booking_delay_no_minimal_booking_time(freezer, current_time, max_booking_datetime):
freezer.move_to(current_time)
agenda = Agenda.objects.create(label='Agenda', maximal_booking_delay=4, minimal_booking_time=None)
assert make_naive(agenda.max_booking_datetime) == max_booking_datetime
@pytest.mark.parametrize(
'current_time,max_booking_datetime',
[
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 8)),
# summer DST change on sunday
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
# winter DST change on sunday
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
],
ids=delay_parameter_to_label,
)
def test_agenda_maximal_booking_delay_minimal_booking_time_at_8(freezer, current_time, max_booking_datetime):
freezer.move_to(current_time)
agenda = Agenda.objects.create(
label='Agenda', maximal_booking_delay=4, minimal_booking_time=datetime.time(8)
)
assert make_naive(agenda.max_booking_datetime) == max_booking_datetime
@pytest.mark.parametrize('with_prefetch', [True, False]) @pytest.mark.parametrize('with_prefetch', [True, False])
def test_agenda_is_available_for_simple_management(settings, with_prefetch): def test_agenda_is_available_for_simple_management(settings, with_prefetch):
settings.EXCEPTIONS_SOURCES = { settings.EXCEPTIONS_SOURCES = {