manager: detect partial booking checks overlap using only form data (#82231)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
b7c5d4f675
commit
3161f47cd1
|
@ -3254,21 +3254,6 @@ class BookingCheck(models.Model):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def overlaps_existing_check(self, start_time, end_time):
|
|
||||||
booking_checks = BookingCheck.objects.filter(booking=self.booking).exclude(pk=self.pk)
|
|
||||||
if not booking_checks:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(booking_checks) > 1:
|
|
||||||
raise ValueError('too many booking checks') # should not happen
|
|
||||||
|
|
||||||
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'])
|
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
|
||||||
|
|
||||||
|
|
|
@ -648,9 +648,6 @@ class PartialBookingCheckForm(forms.ModelForm):
|
||||||
if start_time and end_time and end_time <= start_time:
|
if start_time and end_time and end_time <= start_time:
|
||||||
raise ValidationError(_('Arrival must be before departure.'))
|
raise ValidationError(_('Arrival must be before departure.'))
|
||||||
|
|
||||||
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:
|
if self.cleaned_data['presence'] is not None:
|
||||||
kind = 'presence' if self.cleaned_data['presence'] else 'absence'
|
kind = 'presence' if self.cleaned_data['presence'] else 'absence'
|
||||||
if f'{kind}_check_type' in self.cleaned_data:
|
if f'{kind}_check_type' in self.cleaned_data:
|
||||||
|
|
|
@ -4637,13 +4637,34 @@ class PartialBookingCheckView(ViewableAgendaMixin, TemplateView):
|
||||||
forms = self.get_forms()
|
forms = self.get_forms()
|
||||||
|
|
||||||
all_valid = all(form.is_valid() for form in forms)
|
all_valid = all(form.is_valid() for form in forms)
|
||||||
if all_valid:
|
if all_valid and not self.checks_overlap(forms):
|
||||||
for form in forms:
|
for form in forms:
|
||||||
form.save()
|
form.save()
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
return self.render_to_response(self.get_context_data(forms=forms))
|
return self.render_to_response(self.get_context_data(forms=forms))
|
||||||
|
|
||||||
|
def checks_overlap(self, forms):
|
||||||
|
Interval = collections.namedtuple('Interval', ['start', 'end', 'form'])
|
||||||
|
intervals = [
|
||||||
|
Interval(
|
||||||
|
# if check has no start/end time, take (time, time) as an interval
|
||||||
|
form.cleaned_data['start_time'] or form.cleaned_data['end_time'],
|
||||||
|
form.cleaned_data['end_time'] or form.cleaned_data['start_time'],
|
||||||
|
form,
|
||||||
|
)
|
||||||
|
for form in forms
|
||||||
|
if form.cleaned_data['presence'] is not None
|
||||||
|
]
|
||||||
|
intervals.sort(key=lambda x: x.start)
|
||||||
|
|
||||||
|
for i in range(1, len(intervals)):
|
||||||
|
if intervals[i - 1].end > intervals[i].start:
|
||||||
|
intervals[i].form.add_error(None, _('Booking check hours are overlapping.'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
date = self.event.start_datetime
|
date = self.event.start_datetime
|
||||||
return reverse(
|
return reverse(
|
||||||
|
|
|
@ -552,11 +552,11 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user):
|
||||||
resp.form['check-2-start_time'] = '11:30'
|
resp.form['check-2-start_time'] = '11:30'
|
||||||
resp.form['check-2-end_time'] = ''
|
resp.form['check-2-end_time'] = ''
|
||||||
resp = resp.form.submit()
|
resp = resp.form.submit()
|
||||||
assert 'Booking check hours overlap existing check.' in resp.text
|
assert 'Booking check hours are overlapping.' in resp.text
|
||||||
|
|
||||||
resp.form['check-2-end_time'] = '17:30'
|
resp.form['check-2-end_time'] = '17:30'
|
||||||
resp = resp.form.submit()
|
resp = resp.form.submit()
|
||||||
assert 'Booking check hours overlap existing check.' in resp.text
|
assert 'Booking check hours are overlapping.' in resp.text
|
||||||
|
|
||||||
resp.form['check-2-start_time'] = '12:30'
|
resp.form['check-2-start_time'] = '12:30'
|
||||||
resp.form['check-2-presence'] = ''
|
resp.form['check-2-presence'] = ''
|
||||||
|
@ -578,6 +578,19 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user):
|
||||||
assert 'Arrival must be before departure.' in resp.text
|
assert 'Arrival must be before departure.' in resp.text
|
||||||
assert 'gadjo-folded' not 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
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('subscription_only', (True, False))
|
@pytest.mark.parametrize('subscription_only', (True, False))
|
||||||
def test_manager_partial_bookings_incomplete_check(subscription_only, app, admin_user):
|
def test_manager_partial_bookings_incomplete_check(subscription_only, app, admin_user):
|
||||||
|
|
Loading…
Reference in New Issue