1264 lines
51 KiB
Python
1264 lines
51 KiB
Python
import datetime
|
|
import json
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from chrono.agendas.models import Agenda, Booking, BookingCheck, Event, Subscription
|
|
from chrono.utils.lingo import CheckType
|
|
from chrono.utils.timezone import make_aware, now
|
|
from tests.utils import login
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
def test_manager_partial_bookings_add_agenda(app, admin_user, settings):
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/add/')
|
|
assert 'partial-bookings' not in resp.text
|
|
|
|
settings.PARTIAL_BOOKINGS_ENABLED = True
|
|
|
|
resp = app.get('/manage/agendas/add/')
|
|
resp.form['label'] = 'Foo bar'
|
|
resp.form['kind'] = 'partial-bookings'
|
|
resp = resp.form.submit().follow()
|
|
|
|
agenda = Agenda.objects.get(label='Foo bar')
|
|
assert agenda.kind == 'events'
|
|
assert agenda.partial_bookings is True
|
|
assert agenda.default_view == 'day'
|
|
assert agenda.enable_check_for_future_events is True
|
|
|
|
resp = resp.click('Options')
|
|
assert resp.form['default_view'].options == [
|
|
('', False, '---------'),
|
|
('day', True, 'Day view'),
|
|
('month', False, 'Month view'),
|
|
]
|
|
|
|
|
|
def test_options_partial_bookings_invoicing_settings(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
assert agenda.invoicing_unit == 'hour'
|
|
assert agenda.invoicing_tolerance == 0
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
booking = Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=event,
|
|
)
|
|
booking.mark_user_presence(start_time=datetime.time(10, 55), end_time=datetime.time(14, 4))
|
|
agenda.refresh_booking_computed_times()
|
|
booking.refresh_from_db()
|
|
assert booking.user_check.computed_start_time == datetime.time(10, 0)
|
|
assert booking.user_check.computed_end_time == datetime.time(15, 0)
|
|
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
resp = resp.click(href='/manage/agendas/%s/invoicing-options' % agenda.pk)
|
|
assert resp.form['invoicing_unit'].options == [
|
|
('hour', True, 'Per hour'),
|
|
('half_hour', False, 'Per half hour'),
|
|
('quarter', False, 'Per quarter-hour'),
|
|
('minute', False, 'Per minute'),
|
|
]
|
|
resp.form['invoicing_unit'] = 'half_hour'
|
|
resp.form['invoicing_tolerance'] = 10
|
|
resp = resp.form.submit().follow()
|
|
|
|
agenda.refresh_from_db()
|
|
assert agenda.invoicing_unit == 'half_hour'
|
|
assert agenda.invoicing_tolerance == 10
|
|
booking.refresh_from_db()
|
|
assert booking.user_check.computed_start_time == datetime.time(11, 0)
|
|
assert booking.user_check.computed_end_time == datetime.time(14, 0)
|
|
|
|
# check kind
|
|
agenda.partial_bookings = False
|
|
agenda.save()
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
|
|
agenda.kind = 'meetings'
|
|
agenda.save()
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
|
|
agenda.kind = 'virtual'
|
|
agenda.save()
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
|
|
|
|
|
|
def test_options_partial_bookings_simpler_settings(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
assert 'Management notifications' not in resp.text
|
|
assert 'Booking reminders' not in resp.text
|
|
assert 'Booking display template' not in resp.text
|
|
assert 'Extra user block template' not in resp.text
|
|
assert 'Enable the check of bookings when event has not passed' not in resp.text
|
|
|
|
resp = app.get('/manage/agendas/%s/display-options' % agenda.pk)
|
|
assert 'booking_user_block_template' not in resp.form.fields
|
|
|
|
resp = app.get('/manage/agendas/%s/check-options' % agenda.pk)
|
|
assert 'enable_check_for_future_events' not in resp.form.fields
|
|
assert 'booking_extra_user_block_template' not in resp.form.fields
|
|
|
|
|
|
def test_manager_partial_bookings_add_event(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
resp = resp.click('New Event')
|
|
assert 'duration' not in resp.form.fields
|
|
assert 'recurrence_week_interval' not in resp.form.fields
|
|
|
|
resp.form['start_datetime_0'] = '2023-02-15'
|
|
resp.form['start_datetime_1'] = '08:00'
|
|
resp.form['end_time'] = '18:00'
|
|
resp.form['places'] = 10
|
|
resp = resp.form.submit().follow()
|
|
|
|
event = Event.objects.get()
|
|
assert event.end_time == datetime.time(18, 00)
|
|
assert event.duration is None
|
|
|
|
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk))
|
|
assert 'duration' not in resp.form.fields
|
|
assert resp.form['end_time'].value == '18:00'
|
|
|
|
resp.form['end_time'] = '17:00'
|
|
resp = resp.form.submit().follow()
|
|
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
|
resp = resp.click('New Event')
|
|
resp.form['start_datetime_0'] = '2023-02-15'
|
|
resp.form['start_datetime_1'] = '10:00'
|
|
resp.form['end_time'] = '12:00'
|
|
resp.form['places'] = 10
|
|
resp = resp.form.submit()
|
|
assert 'There can only be one event per day.' in resp.text
|
|
|
|
|
|
def test_manager_partial_bookings_day_view(app, admin_user, freezer):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=event,
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='yyy',
|
|
user_first_name='Jon',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(8, 00),
|
|
end_time=datetime.time(10, 00),
|
|
event=event,
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='zzz',
|
|
user_first_name='Bruce',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(12, 00),
|
|
end_time=datetime.time(14, 00),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert 'Week' not in resp.text
|
|
|
|
# time range from one hour before event start to one hour after end
|
|
assert resp.pyquery('.partial-booking--hour')[0].text == '07\u202fh'
|
|
assert resp.pyquery('.partial-booking--hour')[-1].text == '19\u202fh'
|
|
assert [int(x.text.replace('\u202fh', '')) for x in resp.pyquery('.partial-booking--hour')] == list(
|
|
range(7, 20)
|
|
)
|
|
|
|
assert len(resp.pyquery('.partial-booking--registrant')) == 3
|
|
assert resp.pyquery('.registrant--name-label')[0].text_content() == 'Bruce Doe'
|
|
assert resp.pyquery('.registrant--name-label')[1].text_content() == 'Jane Doe'
|
|
assert resp.pyquery('.registrant--name-label')[2].text_content() == 'Jon Doe'
|
|
|
|
assert resp.pyquery('.registrant--bar')[0].findall('time')[0].text == '12:00'
|
|
assert resp.pyquery('.registrant--bar')[0].findall('time')[1].text == '14:00'
|
|
assert resp.pyquery('.registrant--bar')[0].attrib['style'] == 'left: 38.46%; width: 15.38%;'
|
|
assert resp.pyquery('.registrant--bar')[1].findall('time')[0].text == '11:00'
|
|
assert resp.pyquery('.registrant--bar')[1].findall('time')[1].text == '13:30'
|
|
assert resp.pyquery('.registrant--bar')[1].attrib['style'] == 'left: 30.77%; width: 19.23%;'
|
|
assert resp.pyquery('.registrant--bar')[2].findall('time')[0].text == '08:00'
|
|
assert resp.pyquery('.registrant--bar')[2].findall('time')[1].text == '10:00'
|
|
assert resp.pyquery('.registrant--bar')[2].attrib['style'] == 'left: 7.69%; width: 15.38%;'
|
|
|
|
resp = resp.click('Next day')
|
|
assert 'No opening hours this day.' in resp.text
|
|
|
|
Event.objects.all().delete()
|
|
event = Event.objects.create(
|
|
label='Other Event',
|
|
start_datetime=start_datetime,
|
|
end_time=datetime.time(18, 00),
|
|
places=10,
|
|
agenda=agenda,
|
|
recurrence_days=[1, 2, 3, 4, 5, 6, 7],
|
|
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
|
|
)
|
|
event.create_all_recurrences()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert resp.pyquery('.partial-booking--hour')[0].text == '07\u202fh'
|
|
assert resp.pyquery('.partial-booking--hour')[-1].text == '19\u202fh'
|
|
|
|
|
|
def test_manager_partial_bookings_day_view_24_hours(app, admin_user, freezer):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 0, 0))
|
|
Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(23, 59), places=10, agenda=agenda
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
# from 00h to 23h
|
|
assert [int(x.text.replace('\u202fh', '')) for x in resp.pyquery('.partial-booking--hour')] == list(
|
|
range(0, 24)
|
|
)
|
|
|
|
|
|
@pytest.mark.freeze_time('2023-03-10 08:00')
|
|
def test_manager_partial_bookings_day_view_hour_indicator(app, admin_user, freezer):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
Event.objects.create(
|
|
label='Event', start_datetime=now(), end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
|
|
app = login(app)
|
|
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, now().year, now().month, now().day)
|
|
resp = app.get(url)
|
|
|
|
assert resp.pyquery('.partial-booking--hour-indicator')
|
|
assert resp.pyquery('.partial-booking').attr('data-start-datetime') == '2023-03-10T08:00:00'
|
|
assert resp.pyquery('.partial-booking').attr('data-end-datetime') == '2023-03-10T19:00:00'
|
|
|
|
freezer.move_to('2023-03-11 08:00')
|
|
|
|
resp = app.get(url)
|
|
assert not resp.pyquery('.partial-booking--hour-indicator')
|
|
|
|
|
|
def test_manager_partial_bookings_day_view_multiple_bookings(app, admin_user, freezer):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
first_booking = Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(9, 00),
|
|
end_time=datetime.time(12, 00),
|
|
from_recurring_fillslots=True,
|
|
event=event,
|
|
)
|
|
second_booking = Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(15, 00),
|
|
end_time=datetime.time(18, 00),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
assert len(resp.pyquery('.partial-booking--registrant')) == 1
|
|
assert len(resp.pyquery('.registrant--bar')) == 2
|
|
assert resp.pyquery('.registrant--bar')[0].findall('time')[0].text == '09:00'
|
|
assert resp.pyquery('.registrant--bar')[0].findall('time')[1].text == '12:00'
|
|
assert 'occasional' not in resp.pyquery('.registrant--bar')[0].text_content()
|
|
assert len(resp.pyquery('.registrant--bar')[0].findall('span.occasional')) == 0
|
|
assert resp.pyquery('.registrant--bar')[1].findall('time')[0].text == '15:00'
|
|
assert resp.pyquery('.registrant--bar')[1].findall('time')[1].text == '18:00'
|
|
assert 'occasional' in resp.pyquery('.registrant--bar')[1].text_content()
|
|
|
|
# check first booking
|
|
resp = resp.click('Check')
|
|
|
|
assert len(resp.pyquery('.pk-tabs')) == 1
|
|
assert resp.pyquery('#tab-%s' % first_booking.pk).text() == '09:00 - 12:00'
|
|
assert resp.pyquery('#tab-%s' % second_booking.pk).text() == '15:00 - 18:00'
|
|
|
|
first_prefix = 'booking-%s-' % first_booking.pk
|
|
second_prefix = 'booking-%s-' % second_booking.pk
|
|
|
|
# first booking inital check value is "Present", second booking is "Not checked"
|
|
assert resp.form[first_prefix + 'presence'].value == 'True'
|
|
assert resp.form[second_prefix + 'presence'].value == ''
|
|
|
|
resp.form[first_prefix + 'start_time'] = '09:30'
|
|
resp.form[first_prefix + 'end_time'] = '12:00'
|
|
resp.form[first_prefix + 'presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.partial-booking--registrant')) == 1
|
|
assert len(resp.pyquery('.registrant--bar')) == 4
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 1
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
|
|
|
|
# check second booking
|
|
resp = resp.click('Check')
|
|
resp.form[second_prefix + 'start_time'] = '15:00'
|
|
resp.form[second_prefix + 'end_time'] = '17:00'
|
|
resp.form[second_prefix + 'presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.partial-booking--registrant')) == 1
|
|
assert len(resp.pyquery('.registrant--bar')) == 6
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 2
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
|
|
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[0].text == '15:00'
|
|
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[1].text == '17:00'
|
|
|
|
# check display filtering
|
|
resp = app.get(
|
|
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
|
|
params={'display': ['booked', 'checked', 'computed']},
|
|
)
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 2
|
|
resp = app.get(
|
|
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
|
|
params={'display': ['booked']},
|
|
)
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 0
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 0
|
|
resp = app.get(
|
|
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
|
|
params={'display': ['checked']},
|
|
)
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 0
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 0
|
|
resp = app.get(
|
|
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
|
|
params={'display': ['computed']},
|
|
)
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 0
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 0
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 2
|
|
|
|
|
|
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
|
def test_manager_partial_bookings_check(check_types, app, admin_user):
|
|
check_types.return_value = []
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
booking = Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 1
|
|
assert resp.pyquery('.registrant--bar time')[0].text == '11:00'
|
|
assert resp.pyquery('.registrant--bar time')[1].text == '13:30'
|
|
assert resp.pyquery('.registrant--bar')[0].attrib['style'] == 'left: 30.77%; width: 19.23%;'
|
|
|
|
resp = resp.click('Check')
|
|
assert 'presence_check_type' not in resp.form.fields
|
|
assert 'absence_check_type' not in resp.form.fields
|
|
|
|
assert len(resp.pyquery('.pk-tabs')) == 0
|
|
assert resp.pyquery('.booking-check-forms').attr('data-fill-start_time') == '11:00'
|
|
assert resp.pyquery('.booking-check-forms').attr('data-fill-end_time') == '13:30'
|
|
assert resp.pyquery('.time-widget-button')[0].text == 'Fill with booking start time'
|
|
assert resp.pyquery('.time-widget-button')[1].text == 'Fill with booking end time'
|
|
|
|
# submitting empty form works
|
|
resp.form['presence'] = ''
|
|
resp = resp.form.submit().follow()
|
|
|
|
resp = resp.click('Check')
|
|
resp.form['start_time'] = '11:01'
|
|
resp.form['end_time'] = '11:00'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Arrival must be before departure.' in resp.text
|
|
|
|
resp.form['start_time'] = '07:59'
|
|
resp.form['end_time'] = '18:00'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Arrival must be after opening time.' in resp.text
|
|
|
|
resp.form['start_time'] = '08:00'
|
|
resp.form['end_time'] = '18:01'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Departure must be before closing time.' in resp.text
|
|
|
|
resp.form['start_time'] = '11:01'
|
|
resp.form['end_time'] = '13:15'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
booking.refresh_from_db()
|
|
assert booking.user_check.start_time == datetime.time(11, 1)
|
|
assert booking.user_check.end_time == datetime.time(13, 15)
|
|
assert booking.user_check.computed_start_time == datetime.time(11, 0)
|
|
assert booking.user_check.computed_end_time == datetime.time(14, 0)
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 3
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.check time')[0].text == '11:01'
|
|
assert resp.pyquery('.registrant--bar.check time')[1].text == '13:15'
|
|
assert resp.pyquery('.registrant--bar.check')[0].attrib['style'] == 'left: 30.9%; width: 17.18%;'
|
|
assert resp.pyquery('.registrant--bar.check span').text() == ''
|
|
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.computed time')[0].text == '11:00'
|
|
assert resp.pyquery('.registrant--bar.computed time')[1].text == '14:00'
|
|
assert resp.pyquery('.registrant--bar.computed')[0].attrib['style'] == 'left: 30.77%; width: 23.08%;'
|
|
|
|
agenda.invoicing_unit = 'half_hour'
|
|
agenda.invoicing_tolerance = 10
|
|
agenda.save()
|
|
agenda.refresh_booking_computed_times()
|
|
booking.refresh_from_db()
|
|
assert booking.user_check.start_time == datetime.time(11, 1)
|
|
assert booking.user_check.end_time == datetime.time(13, 15)
|
|
assert booking.user_check.computed_start_time == datetime.time(11, 0)
|
|
assert booking.user_check.computed_end_time == datetime.time(13, 30)
|
|
|
|
check_types.return_value = [
|
|
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
|
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
|
CheckType(slug='baz-reason', label='Baz reason', kind='presence'),
|
|
]
|
|
resp = resp.click('Check')
|
|
assert resp.form['presence_check_type'].options == [
|
|
('', True, '---------'),
|
|
('bar-reason', False, 'Bar reason'),
|
|
('baz-reason', False, 'Baz reason'),
|
|
]
|
|
assert resp.form['absence_check_type'].options == [
|
|
('', True, '---------'),
|
|
('foo-reason', False, 'Foo reason'),
|
|
]
|
|
resp.form['presence_check_type'] = 'bar-reason'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 3
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.check time')[0].text == '11:01'
|
|
assert resp.pyquery('.registrant--bar.check time')[1].text == '13:15'
|
|
assert resp.pyquery('.registrant--bar.check')[0].attrib['style'] == 'left: 30.9%; width: 17.18%;'
|
|
assert resp.pyquery('.registrant--bar.check span').text() == 'Bar reason'
|
|
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.computed time')[0].text == '11:00'
|
|
assert resp.pyquery('.registrant--bar.computed time')[1].text == '13:30'
|
|
assert resp.pyquery('.registrant--bar.computed')[0].attrib['style'] == 'left: 30.77%; width: 19.23%;'
|
|
|
|
resp = resp.click('Check')
|
|
assert resp.form['presence_check_type'].value == 'bar-reason'
|
|
|
|
resp.form['presence'] = 'False'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 3
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.check.absent')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.computed.absent')) == 1
|
|
assert resp.pyquery('.registrant--bar.check span').text() == ''
|
|
|
|
resp = resp.click('Check')
|
|
resp.form['presence'] = ''
|
|
resp = resp.form.submit().follow()
|
|
assert len(resp.pyquery('.registrant--bar')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 1
|
|
assert resp.pyquery('.registrant--bar.check span').text() == ''
|
|
|
|
# event is checked
|
|
event.checked = True
|
|
event.save()
|
|
assert agenda.disable_check_update is False
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) in resp
|
|
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=200)
|
|
|
|
# event check is locked
|
|
event.check_locked = True
|
|
event.save()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) not in resp
|
|
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=404)
|
|
|
|
# now disable check update
|
|
event.check_locked = False
|
|
event.save()
|
|
agenda.disable_check_update = True
|
|
agenda.save()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) not in resp
|
|
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=404)
|
|
|
|
|
|
def test_manager_partial_bookings_multiple_checks(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(9, 00),
|
|
end_time=datetime.time(18, 00),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
resp = resp.click('Check')
|
|
|
|
# main check inital value is "Present", second check is "Not checked"
|
|
assert resp.form['presence'].value == 'True'
|
|
assert resp.form['check-2-presence'].value == ''
|
|
|
|
resp.form['start_time'] = '09:30'
|
|
resp.form['end_time'] = '12:00'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 3
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 1
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
|
|
|
|
resp = resp.click('Check')
|
|
assert 'gadjo-folded' in resp.text
|
|
|
|
resp.form['check-2-start_time'] = '12:30'
|
|
resp.form['check-2-end_time'] = '17:30'
|
|
resp.form['check-2-presence'] = 'False'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 5
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 2
|
|
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[0].text == '12:30'
|
|
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[1].text == '17:30'
|
|
|
|
resp = resp.click('Check')
|
|
assert 'gadjo-folded' not in resp.text
|
|
|
|
resp.form['check-2-start_time'] = '11:30'
|
|
resp.form['check-2-end_time'] = ''
|
|
resp = resp.form.submit()
|
|
assert 'Booking check hours are overlapping.' in resp.text
|
|
|
|
resp.form['check-2-end_time'] = '17:30'
|
|
resp = resp.form.submit()
|
|
assert 'Booking check hours are overlapping.' in resp.text
|
|
|
|
resp.form['check-2-start_time'] = '12:30'
|
|
resp.form['check-2-presence'] = ''
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 3
|
|
assert len(resp.pyquery('.registrant--bar.check')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.computed')) == 1
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
|
|
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
|
|
|
|
resp = resp.click('Check')
|
|
assert 'gadjo-folded' in resp.text
|
|
|
|
resp.form['check-2-presence'] = 'False'
|
|
resp.form['check-2-start_time'] = '11:30'
|
|
resp.form['check-2-end_time'] = '11:29'
|
|
resp = resp.form.submit()
|
|
assert 'Arrival must be before departure.' in resp.text
|
|
assert 'gadjo-folded' not in resp.text
|
|
|
|
# overlap is detected when 2 checks are created at the same time
|
|
BookingCheck.objects.all().delete()
|
|
|
|
resp.form['presence'] = 'True'
|
|
resp.form['start_time'] = '10:00'
|
|
resp.form['end_time'] = '11:00'
|
|
resp.form['check-2-presence'] = 'False'
|
|
resp.form['check-2-start_time'] = '10:30'
|
|
resp.form['check-2-end_time'] = '11:30'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Booking check hours are overlapping.' in resp.text
|
|
|
|
# two check cannot have the same presence value
|
|
resp.form['presence'] = 'True'
|
|
resp.form['start_time'] = '10:00'
|
|
resp.form['end_time'] = '11:00'
|
|
resp.form['check-2-presence'] = 'True'
|
|
resp.form['check-2-start_time'] = '12:00'
|
|
resp.form['check-2-end_time'] = '13:00'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Both booking checks cannot have the same status.' in resp.text
|
|
|
|
resp.form['presence'] = 'False'
|
|
resp.form['check-2-presence'] = 'False'
|
|
resp = resp.form.submit()
|
|
|
|
assert 'Both booking checks cannot have the same status.' in resp.text
|
|
|
|
|
|
@pytest.mark.parametrize('subscription_only', (True, False))
|
|
def test_manager_partial_bookings_incomplete_check(subscription_only, app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
if subscription_only:
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=2),
|
|
)
|
|
else:
|
|
booking = Booking.objects.create(
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
resp = resp.click('Check')
|
|
resp = resp.form.submit()
|
|
assert 'Both arrival and departure cannot not be empty.' in resp.text
|
|
|
|
resp.form['start_time'] = '11:01'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
|
|
booking = Booking.objects.get()
|
|
assert booking.user_check.start_time == datetime.time(11, 1)
|
|
assert booking.user_check.end_time is None
|
|
assert booking.user_check.computed_start_time == datetime.time(11, 0)
|
|
assert booking.user_check.computed_end_time is None
|
|
|
|
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.check.present')[0].attrib['style'] == 'left: 30.9%;'
|
|
assert resp.pyquery('.registrant--bar.check time').text() == '11:01'
|
|
|
|
resp = resp.click('Check')
|
|
resp.form['start_time'] = ''
|
|
resp.form['end_time'] = '13:15'
|
|
resp = resp.form.submit().follow()
|
|
|
|
booking.refresh_from_db()
|
|
assert booking.user_check.start_time is None
|
|
assert booking.user_check.end_time == datetime.time(13, 15)
|
|
assert booking.user_check.computed_start_time is None
|
|
assert booking.user_check.computed_end_time == datetime.time(14, 0)
|
|
|
|
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.check.present')[0].attrib['style'] == 'left: 0%; width: 48.08%;'
|
|
assert resp.pyquery('.registrant--bar.check time').text() == '13:15'
|
|
|
|
resp = resp.click('Check')
|
|
resp.form['start_time'] = ''
|
|
resp.form['end_time'] = ''
|
|
resp = resp.form.submit()
|
|
assert 'Both arrival and departure cannot not be empty.' in resp.text
|
|
|
|
|
|
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
|
def test_manager_partial_bookings_check_subscription(check_types, app, admin_user):
|
|
check_types.return_value = []
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event',
|
|
start_datetime=start_datetime,
|
|
end_time=datetime.time(18, 00),
|
|
places=10,
|
|
agenda=agenda,
|
|
recurrence_days=list(range(1, 8)),
|
|
recurrence_end_date=start_datetime + datetime.timedelta(days=30),
|
|
)
|
|
event.create_all_recurrences()
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='xxx',
|
|
user_first_name='Jane',
|
|
user_last_name='Doe',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=2),
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 0
|
|
assert len(resp.pyquery('.registrant--name a')) == 1
|
|
|
|
resp = resp.click('Check')
|
|
assert 'Fill with booking start time' not in resp.text
|
|
assert 'absence_check_type' not in resp.form.fields
|
|
assert resp.form['presence'].options == [
|
|
('', False, None),
|
|
('True', True, None),
|
|
] # no 'False' option
|
|
assert 'check-2-presence' not in resp.form.fields
|
|
|
|
resp.form['start_time'] = '10:00'
|
|
resp.form['end_time'] = '16:00'
|
|
resp.form['presence'] = 'True'
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--name-label a')) == 0
|
|
assert len(resp.pyquery('.registrant--bar')) == 2
|
|
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
|
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
|
|
assert resp.pyquery('.registrant--bar.check time')[0].text == '10:00'
|
|
assert resp.pyquery('.registrant--bar.check time')[1].text == '16:00'
|
|
|
|
booking = Booking.objects.get()
|
|
assert booking.user_external_id == 'xxx'
|
|
assert booking.user_first_name == 'Jane'
|
|
assert booking.user_last_name == 'Doe'
|
|
|
|
resp = resp.click('Check')
|
|
assert 'Fill with booking start time' not in resp.text
|
|
assert 'absence_check_type' not in resp.form.fields
|
|
assert resp.form['presence'].options == [
|
|
('', False, None),
|
|
('True', True, None),
|
|
] # no 'False' option
|
|
|
|
resp.form['presence'] = ''
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert len(resp.pyquery('.registrant--bar')) == 0
|
|
|
|
|
|
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
|
def test_manager_partial_bookings_check_filters(check_types, app, admin_user):
|
|
check_types.return_value = [
|
|
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
|
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
|
]
|
|
agenda = Agenda.objects.create(
|
|
label='Foo bar',
|
|
kind='events',
|
|
partial_bookings=True,
|
|
booking_check_filters='menu',
|
|
)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='user:1',
|
|
user_first_name='User',
|
|
user_last_name='Not Checked',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=event,
|
|
)
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:2',
|
|
user_first_name='User',
|
|
user_last_name='Present Vegan',
|
|
start_time=datetime.time(8, 00),
|
|
end_time=datetime.time(10, 00),
|
|
event=event,
|
|
extra_data={'menu': 'vegan'},
|
|
)
|
|
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:3',
|
|
user_first_name='User',
|
|
user_last_name='Absent Meat Foo Reason',
|
|
start_time=datetime.time(12, 00),
|
|
end_time=datetime.time(14, 00),
|
|
event=event,
|
|
extra_data={'menu': 'meat'},
|
|
)
|
|
booking.mark_user_absence(
|
|
check_type_slug='foo-reason', start_time=datetime.time(12, 30), end_time=datetime.time(14, 30)
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:1',
|
|
user_first_name='Subscription',
|
|
user_last_name='Present Vegan',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=1),
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:4',
|
|
user_first_name='Subscription',
|
|
user_last_name='Not Booked',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=1),
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day)
|
|
resp = app.get(url)
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
|
|
'User Absent Meat Foo Reason',
|
|
'Subscription Not Booked',
|
|
'User Not Checked',
|
|
'User Present Vegan',
|
|
]
|
|
|
|
# one registrant has not booked, no bar is shown
|
|
assert len(resp.pyquery('.registrant--bar.booking')) == 3
|
|
|
|
resp = app.get(url, params={'booking-status': 'booked'})
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
|
|
'User Absent Meat Foo Reason',
|
|
'User Not Checked',
|
|
'User Present Vegan',
|
|
]
|
|
|
|
resp = app.get(url, params={'booking-status': 'presence'})
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == ['User Present Vegan']
|
|
|
|
resp = app.get(url, params={'booking-status': 'presence', 'extra-data-menu': 'meat'})
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == []
|
|
|
|
resp = app.get(url, params={'extra-data-menu': 'meat'})
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
|
|
'User Absent Meat Foo Reason'
|
|
]
|
|
|
|
resp = app.get(url, params={'booking-status': 'absence::foo-reason'})
|
|
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
|
|
'User Absent Meat Foo Reason'
|
|
]
|
|
|
|
|
|
def test_manager_partial_bookings_event_checked(app, admin_user):
|
|
agenda = Agenda.objects.create(
|
|
label='Foo bar',
|
|
kind='events',
|
|
partial_bookings=True,
|
|
)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
login(app)
|
|
|
|
today = start_datetime.date()
|
|
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day)
|
|
resp = app.get(url)
|
|
assert 'Mark the event as checked' not in resp
|
|
for i in range(8):
|
|
booking = Booking.objects.create(
|
|
event=event,
|
|
user_external_id='xxx',
|
|
start_time=datetime.time(8, 00),
|
|
end_time=datetime.time(10, 00),
|
|
presence_callback_url='https://example.invalid/presence/%s' % i,
|
|
absence_callback_url='https://example.invalid/absence/%s' % i,
|
|
)
|
|
if i == 0:
|
|
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(9, 00))
|
|
BookingCheck.objects.create(
|
|
booking=booking,
|
|
start_time=datetime.time(9, 00),
|
|
end_time=datetime.time(10, 00),
|
|
presence=False,
|
|
)
|
|
elif i < 3:
|
|
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
|
|
elif i < 7:
|
|
booking.mark_user_absence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
|
|
resp = app.get(url)
|
|
assert 'Mark the event as checked' in resp
|
|
assert event.checked is False
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
|
|
assert 'checked tag' not in resp
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '<span class="checked tag">Checked</span>' not in resp
|
|
assert 'check-locked' not in resp
|
|
assert 'invoiced' not in resp
|
|
|
|
token = resp.context['csrf_token']
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
mock_response = mock.Mock(status_code=200)
|
|
mock_send.return_value = mock_response
|
|
resp = app.post(
|
|
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
|
params={'csrfmiddlewaretoken': token},
|
|
)
|
|
event.refresh_from_db()
|
|
assert event.checked is True
|
|
resp = resp.follow()
|
|
assert 'Mark the event as checked' not in resp
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
|
|
assert 'checked tag' in resp
|
|
for booking in Booking.objects.filter(user_checks__isnull=True):
|
|
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '<span class="checked tag">Checked</span>' in resp
|
|
assert 'check-locked' not in resp
|
|
assert 'invoiced' not in resp
|
|
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
|
'https://example.invalid/presence/0',
|
|
'https://example.invalid/presence/1',
|
|
'https://example.invalid/presence/2',
|
|
'https://example.invalid/absence/0',
|
|
'https://example.invalid/absence/3',
|
|
'https://example.invalid/absence/4',
|
|
'https://example.invalid/absence/5',
|
|
'https://example.invalid/absence/6',
|
|
}
|
|
assert set(json.loads(mock_send.call_args_list[0][0][0].body).keys()) == {
|
|
'user_check_type_label',
|
|
'user_check_type_slug',
|
|
'user_was_present',
|
|
'start_time',
|
|
'end_time',
|
|
'computed_start_time',
|
|
'computed_end_time',
|
|
}
|
|
|
|
# event not in past
|
|
agenda.disable_check_update = False
|
|
agenda.save()
|
|
assert agenda.enable_check_for_future_events is False
|
|
today = now().date()
|
|
event.start_datetime = make_aware(
|
|
datetime.datetime(today.year, today.month, today.day, 8, 0)
|
|
) + datetime.timedelta(days=1)
|
|
event.save()
|
|
app.post(
|
|
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
|
params={'csrfmiddlewaretoken': token},
|
|
status=404,
|
|
)
|
|
|
|
# not in past, but check for future events is enabled
|
|
agenda.enable_check_for_future_events = True
|
|
agenda.save()
|
|
app.post(
|
|
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
|
params={'csrfmiddlewaretoken': token},
|
|
status=302,
|
|
)
|
|
|
|
# event check is locked
|
|
event.checked = False
|
|
event.check_locked = True
|
|
event.save()
|
|
today = event.start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert '<span class="check-locked tag">Check locked</span>' in resp
|
|
assert 'invoiced' not in resp
|
|
app.post(
|
|
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
|
params={'csrfmiddlewaretoken': token},
|
|
status=404,
|
|
)
|
|
|
|
# event check is locked and envent is invoiced
|
|
event.invoiced = True
|
|
event.save()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
assert 'check-locked' not in resp
|
|
assert '<span class="invoiced tag">Invoiced</span>' in resp
|
|
|
|
|
|
def test_manager_partial_bookings_month_view(app, admin_user, freezer):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
|
)
|
|
event2 = Event.objects.create(
|
|
label='Event 2',
|
|
start_datetime=start_datetime + datetime.timedelta(days=2),
|
|
end_time=datetime.time(18, 00),
|
|
places=10,
|
|
agenda=agenda,
|
|
)
|
|
for e in (event, event2):
|
|
Booking.objects.create(
|
|
user_external_id='user:1',
|
|
user_first_name='User',
|
|
user_last_name='Not Checked',
|
|
start_time=datetime.time(11, 00),
|
|
end_time=datetime.time(13, 30),
|
|
event=e,
|
|
)
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:2',
|
|
user_first_name='User',
|
|
user_last_name='Present',
|
|
start_time=datetime.time(8, 00),
|
|
end_time=datetime.time(10, 00),
|
|
event=event,
|
|
)
|
|
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(9, 00))
|
|
BookingCheck.objects.create(
|
|
booking=booking,
|
|
start_time=datetime.time(9, 00),
|
|
end_time=datetime.time(10, 00),
|
|
presence=False, # second 'absence' check will be ignored
|
|
)
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:3',
|
|
user_first_name='User',
|
|
user_last_name='Absent',
|
|
start_time=datetime.time(12, 00),
|
|
end_time=datetime.time(14, 00),
|
|
event=event,
|
|
)
|
|
booking.mark_user_absence(start_time=datetime.time(12, 30), end_time=datetime.time(14, 30))
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:7',
|
|
user_first_name='User',
|
|
user_last_name='Present (incomplete)',
|
|
start_time=datetime.time(12, 00),
|
|
end_time=datetime.time(14, 00),
|
|
event=event,
|
|
)
|
|
booking.mark_user_presence(start_time=datetime.time(12, 30))
|
|
booking = Booking.objects.create(
|
|
user_external_id='user:8',
|
|
user_first_name='User',
|
|
user_last_name='Present (complete) / Absent (incomplete)',
|
|
start_time=datetime.time(12, 00),
|
|
end_time=datetime.time(14, 00),
|
|
event=event,
|
|
)
|
|
booking.mark_user_presence(start_time=datetime.time(12, 30), end_time=datetime.time(14, 30))
|
|
BookingCheck.objects.create(
|
|
booking=booking,
|
|
start_time=datetime.time(9, 00),
|
|
presence=False,
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:1',
|
|
user_first_name='Subscription',
|
|
user_last_name='Present',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=1),
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:4',
|
|
user_first_name='Subscription',
|
|
user_last_name='Not Booked',
|
|
date_start=event.start_datetime,
|
|
date_end=event.start_datetime + datetime.timedelta(days=1),
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:5',
|
|
user_first_name='Subscription',
|
|
user_last_name='Next Month',
|
|
date_start=datetime.date(2023, 6, 1),
|
|
date_end=datetime.date(2023, 6, 10),
|
|
)
|
|
Subscription.objects.create(
|
|
agenda=agenda,
|
|
user_external_id='user:6',
|
|
user_first_name='Subscription',
|
|
user_last_name='Previous Month',
|
|
date_start=datetime.date(2023, 4, 20),
|
|
date_end=datetime.date(2023, 4, 30),
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 32))
|
|
|
|
assert [x.text for x in resp.pyquery('tbody tr th')] == [
|
|
'User Absent',
|
|
'Subscription Not Booked',
|
|
'User Not Checked',
|
|
'User Present',
|
|
'User Present (complete) / Absent (incomplete)',
|
|
'User Present (incomplete)',
|
|
]
|
|
|
|
user_absent_row = resp.pyquery('tbody tr')[0]
|
|
assert len(resp.pyquery(user_absent_row)('td')) == 31
|
|
assert len(resp.pyquery(user_absent_row)('td span.booking')) == 1
|
|
assert len(resp.pyquery(user_absent_row)('td span.booking.absent')) == 1
|
|
assert resp.pyquery(user_absent_row)('td span.booking.absent').text() == 'Absent'
|
|
|
|
subscription_not_booked_row = resp.pyquery('tbody tr')[1]
|
|
assert len(resp.pyquery(subscription_not_booked_row)('td')) == 31
|
|
assert len(resp.pyquery(subscription_not_booked_row)('td span.booking')) == 0
|
|
|
|
user_not_checked_row = resp.pyquery('tbody tr')[2]
|
|
assert len(resp.pyquery(user_not_checked_row)('td')) == 31
|
|
assert len(resp.pyquery(user_not_checked_row)('td span.booking')) == 2
|
|
assert resp.pyquery(user_not_checked_row)('td span.booking').text() == 'Not checked Not checked'
|
|
|
|
user_present_row = resp.pyquery('tbody tr')[3]
|
|
assert len(resp.pyquery(user_present_row)('td')) == 31
|
|
assert len(resp.pyquery(user_present_row)('td span.booking')) == 1
|
|
assert len(resp.pyquery(user_present_row)('td span.booking.present')) == 1
|
|
assert resp.pyquery(user_present_row)('td span.booking.present').text() == 'Present'
|
|
|
|
user_present_mixed_row = resp.pyquery('tbody tr')[4]
|
|
assert len(resp.pyquery(user_present_mixed_row)('td')) == 31
|
|
assert len(resp.pyquery(user_present_mixed_row)('td span.booking')) == 1
|
|
assert len(resp.pyquery(user_present_mixed_row)('td span.booking.present')) == 0
|
|
|
|
user_present_incomplete_row = resp.pyquery('tbody tr')[4]
|
|
assert len(resp.pyquery(user_present_incomplete_row)('td')) == 31
|
|
assert len(resp.pyquery(user_present_incomplete_row)('td span.booking')) == 1
|
|
assert len(resp.pyquery(user_present_incomplete_row)('td span.booking.present')) == 0
|
|
|
|
resp = resp.click('Next month')
|
|
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 31))
|
|
assert [x.text for x in resp.pyquery('tbody tr th')] == ['Subscription Next Month']
|
|
assert len(resp.pyquery('tbody tr td')) == 30
|
|
|
|
freezer.move_to('2023-05-10 14:00')
|
|
resp = app.get(resp.request.url)
|
|
assert len(resp.pyquery('th.today')) == 0
|
|
assert len(resp.pyquery('col.today')) == 0
|
|
|
|
freezer.move_to('2023-06-10 14:00')
|
|
resp = app.get(resp.request.url)
|
|
assert resp.pyquery('th.today').text() == '10'
|
|
assert len(resp.pyquery('col.today')) == 1
|
|
|
|
|
|
def test_manager_partial_bookings_occupation_rates(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
|
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 10, 0))
|
|
event = Event.objects.create(
|
|
label='Event', start_datetime=start_datetime, end_time=datetime.time(17, 00), places=3, agenda=agenda
|
|
)
|
|
|
|
Booking.objects.create(
|
|
user_external_id='1',
|
|
user_first_name='User',
|
|
user_last_name='10h - 17h',
|
|
start_time=datetime.time(10, 00),
|
|
end_time=datetime.time(17, 00),
|
|
event=event,
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='2',
|
|
user_first_name='User',
|
|
user_last_name='10h10 - 12h30',
|
|
start_time=datetime.time(10, 10),
|
|
end_time=datetime.time(12, 30),
|
|
event=event,
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='3',
|
|
user_first_name='User',
|
|
user_last_name='11h30 - 15h45',
|
|
start_time=datetime.time(11, 30),
|
|
end_time=datetime.time(15, 45),
|
|
event=event,
|
|
)
|
|
Booking.objects.create(
|
|
user_external_id='4',
|
|
user_first_name='User',
|
|
user_last_name='11h15 - 12h10',
|
|
start_time=datetime.time(11, 15),
|
|
end_time=datetime.time(12, 10),
|
|
event=event,
|
|
)
|
|
|
|
app = login(app)
|
|
today = start_datetime.date()
|
|
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
|
|
|
assert len(resp.pyquery('.partial-booking--registrant')) == 4
|
|
|
|
hours = [x.text.replace('\u202f', ' ') for x in resp.pyquery('.partial-booking--hour')]
|
|
labels = [resp.pyquery(x).text() for x in resp.pyquery('.occupation-rate--info')]
|
|
styles = [x.attrib['style'] for x in resp.pyquery('.occupation-rate')]
|
|
overbooked = [
|
|
' overbooked' if 'overbooked' in x.attrib['class'] else '' for x in resp.pyquery('.occupation-rate')
|
|
]
|
|
|
|
assert ['%s: %s / %s%s' % x for x in zip(hours, labels, styles, overbooked)] == [
|
|
'09 h: 0%\n(0/3) / --rate-percent: 0;',
|
|
'10 h: 66%\n(2/3) / --rate-percent: 66;',
|
|
'11 h: 133%\n(4/3) / --rate-percent: 100; overbooked',
|
|
'12 h: 133%\n(4/3) / --rate-percent: 100; overbooked',
|
|
'13 h: 66%\n(2/3) / --rate-percent: 66;',
|
|
'14 h: 66%\n(2/3) / --rate-percent: 66;',
|
|
'15 h: 66%\n(2/3) / --rate-percent: 66;',
|
|
'16 h: 33%\n(1/3) / --rate-percent: 33;',
|
|
'17 h: 0%\n(0/3) / --rate-percent: 0;',
|
|
'18 h: 0%\n(0/3) / --rate-percent: 0;',
|
|
]
|