manager: allow separate arrival/departure check for partial bookings (#80047)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Valentin Deniaud 2023-10-11 12:41:01 +02:00
parent 3e478042f6
commit e278707c98
6 changed files with 81 additions and 13 deletions

View File

@ -18,8 +18,8 @@ class Migration(migrations.Migration):
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('presence', models.BooleanField()),
('start_time', models.TimeField(null=True, verbose_name='Arrival')),
('end_time', models.TimeField(null=True, verbose_name='Departure')),
('start_time', models.TimeField(null=True, blank=True, verbose_name='Arrival')),
('end_time', models.TimeField(null=True, blank=True, verbose_name='Departure')),
('computed_end_time', models.TimeField(null=True)),
('computed_start_time', models.TimeField(null=True)),
('type_slug', models.CharField(blank=True, max_length=160, null=True)),

View File

@ -3158,8 +3158,8 @@ class BookingCheck(models.Model):
presence = models.BooleanField()
start_time = models.TimeField(_('Arrival'), null=True)
end_time = models.TimeField(_('Departure'), null=True)
start_time = models.TimeField(_('Arrival'), null=True, blank=True)
end_time = models.TimeField(_('Departure'), null=True, blank=True)
computed_start_time = models.TimeField(null=True)
computed_end_time = models.TimeField(null=True)
@ -3262,7 +3262,12 @@ class BookingCheck(models.Model):
if len(booking_checks) > 1:
raise ValueError('too many booking checks') # should not happen
return bool(start_time < booking_checks[0].end_time and end_time > booking_checks[0].start_time)
booking_check = booking_checks[0]
if not start_time or not end_time:
return bool(booking_check.start_time < (start_time or end_time) < booking_check.end_time)
return bool(start_time < booking_check.end_time and end_time > booking_check.start_time)
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])

View File

@ -640,12 +640,16 @@ class PartialBookingCheckForm(forms.ModelForm):
self.fields['presence'].widget.choices = presence_choices
def clean(self):
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']:
start_time = self.cleaned_data.get('start_time')
end_time = self.cleaned_data.get('end_time')
if not start_time and not end_time:
raise ValidationError(_('Both arrival and departure cannot not be empty.'))
if start_time and end_time and end_time <= start_time:
raise ValidationError(_('Arrival must be before departure.'))
if self.instance.overlaps_existing_check(
self.cleaned_data['start_time'], self.cleaned_data['end_time']
):
if self.instance.overlaps_existing_check(start_time, end_time):
raise ValidationError(_('Booking check hours overlap existing check.'))
if self.cleaned_data['presence'] is not None:

View File

@ -126,8 +126,12 @@
{% endif %}
>
<strong class="sr-only">{% trans "Checked period:" %}</strong>
<time class="start-time" datetime="{{ check.start_time|time:"H:i" }}">{{ check.start_time|time:"H:i" }}</time>
<time class="end-time" datetime="{{ check.end_time|time:"H:i" }}">{{ check.end_time|time:"H:i" }}</time>
{% if check.start_time %}
<time class="start-time" datetime="{{ check.start_time|time:"H:i" }}">{{ check.start_time|time:"H:i" }}</time>
{% endif %}
{% if check.end_time %}
<time class="end-time" datetime="{{ check.end_time|time:"H:i" }}">{{ check.end_time|time:"H:i" }}</time>
{% endif %}
{% if check.type_label %}<span>{{ check.type_label }}</span>{% endif %}
</a>
{% endfor %}

View File

@ -1667,8 +1667,19 @@ class AgendaDayView(EventChecksMixin, AgendaDateView, DayArchiveView):
booking.user_check_list = list(booking.user_checks.all()) # queryset is prefetched
for check in booking.user_check_list:
check.css_class = 'present' if check.presence else 'absent'
check.css_left = get_time_ratio(check.start_time, start_time)
check.css_width = get_time_ratio(check.end_time, check.start_time)
if not check.start_time:
check.css_class += ' end-only'
check.css_left = booking.css_left
check.css_width = booking.css_width
elif not check.end_time:
check.css_class += ' start-only'
check.css_left = get_time_ratio(check.start_time, start_time)
check.css_width = 4
else:
check.css_left = get_time_ratio(check.start_time, start_time)
check.css_width = get_time_ratio(check.end_time, check.start_time)
if check.computed_start_time and check.computed_end_time:
check.computed_css_left = get_time_ratio(check.computed_start_time, start_time)
check.computed_css_width = get_time_ratio(

View File

@ -536,6 +536,11 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user):
assert 'Add second booking check' not in resp.text
resp.form['start_time'] = '11:30'
resp.form['end_time'] = ''
resp = resp.form.submit()
assert 'Booking check hours overlap existing check.' in resp.text
resp.form['end_time'] = '17:30'
resp = resp.form.submit()
assert 'Booking check hours overlap existing check.' in resp.text
@ -550,6 +555,45 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user):
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
def test_manager_partial_bookings_incomplete_check(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 = 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('Booked period')
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.refresh_from_db()
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')) == 2
assert len(resp.pyquery('.registrant--bar.booking')) == 1
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert resp.pyquery('.registrant--bar.check time').text() == '11:01'
@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 = []