agendas: store computed start/end times on booking check (#80371)
This commit is contained in:
parent
ec497c66d9
commit
3cb80d478a
|
@ -20,6 +20,8 @@ class Migration(migrations.Migration):
|
|||
('presence', models.BooleanField()),
|
||||
('start_time', models.TimeField(null=True, verbose_name='Arrival')),
|
||||
('end_time', models.TimeField(null=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)),
|
||||
('type_label', models.CharField(blank=True, max_length=150, null=True)),
|
||||
(
|
||||
|
|
|
@ -15,6 +15,8 @@ def migrate_booking_check_data(apps, schema_editor):
|
|||
presence=booking.user_was_present,
|
||||
start_time=booking.user_check_start_time,
|
||||
end_time=booking.user_check_end_time,
|
||||
computed_start_time=booking.computed_start_time,
|
||||
computed_end_time=booking.computed_end_time,
|
||||
type_slug=booking.user_check_type_slug,
|
||||
type_label=booking.user_check_type_label,
|
||||
)
|
||||
|
@ -31,6 +33,8 @@ def reverse_migrate_booking_check_data(apps, schema_editor):
|
|||
booking.user_was_present = booking.user_check.presence
|
||||
booking.user_check_start_time = booking.user_check.start_time
|
||||
booking.user_check_end_time = booking.user_check.end_time
|
||||
booking.computed_start_time = booking.computed_start_time
|
||||
booking.computed_end_time = booking.computed_end_time
|
||||
booking.user_check_type_slug = booking.user_check.type_slug
|
||||
booking.user_check_type_label = booking.user_check.type_label
|
||||
|
||||
|
@ -40,6 +44,8 @@ def reverse_migrate_booking_check_data(apps, schema_editor):
|
|||
'user_was_present',
|
||||
'user_check_start_time',
|
||||
'user_check_end_time',
|
||||
'computed_start_time',
|
||||
'computed_end_time',
|
||||
'user_check_type_slug',
|
||||
'user_check_type_label',
|
||||
],
|
||||
|
|
|
@ -29,4 +29,12 @@ class Migration(migrations.Migration):
|
|||
model_name='booking',
|
||||
name='user_was_present',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='booking',
|
||||
name='computed_end_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='booking',
|
||||
name='computed_start_time',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1612,14 +1612,17 @@ class Agenda(models.Model):
|
|||
event__invoiced=False,
|
||||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
).select_related('event__agenda')
|
||||
)
|
||||
booking_checks = BookingCheck.objects.filter(booking__in=bookings_queryset).select_related(
|
||||
'booking', 'booking__event__agenda'
|
||||
)
|
||||
to_update = []
|
||||
for booking in bookings_queryset:
|
||||
changed = booking.refresh_computed_times()
|
||||
for booking_check in booking_checks:
|
||||
changed = booking_check.refresh_computed_times()
|
||||
if changed:
|
||||
to_update.append(booking)
|
||||
to_update.append(booking_check)
|
||||
if to_update:
|
||||
Booking.objects.bulk_update(to_update, ['computed_start_time', 'computed_end_time'])
|
||||
BookingCheck.objects.bulk_update(to_update, ['computed_start_time', 'computed_end_time'])
|
||||
|
||||
|
||||
class VirtualMember(models.Model):
|
||||
|
@ -2214,14 +2217,15 @@ class Event(models.Model):
|
|||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
)
|
||||
booking_checks = BookingCheck.objects.filter(booking__in=bookings_queryset).select_related('booking')
|
||||
to_update = []
|
||||
for booking in bookings_queryset:
|
||||
booking.event = self # to avoid lots of querysets
|
||||
changed = booking.refresh_computed_times()
|
||||
for booking_check in booking_checks:
|
||||
booking_check.booking.event = self # to avoid lots of querysets
|
||||
changed = booking_check.refresh_computed_times()
|
||||
if changed:
|
||||
to_update.append(booking)
|
||||
to_update.append(booking_check)
|
||||
if to_update:
|
||||
Booking.objects.bulk_update(to_update, ['computed_start_time', 'computed_end_time'])
|
||||
BookingCheck.objects.bulk_update(to_update, ['computed_start_time', 'computed_end_time'])
|
||||
|
||||
def in_bookable_period(self, bypass_delays=False):
|
||||
if self.publication_datetime and now() < self.publication_datetime:
|
||||
|
@ -2810,8 +2814,6 @@ class Booking(models.Model):
|
|||
|
||||
start_time = models.TimeField(null=True)
|
||||
end_time = models.TimeField(null=True)
|
||||
computed_start_time = models.TimeField(null=True)
|
||||
computed_end_time = models.TimeField(null=True)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
|
@ -2984,12 +2986,26 @@ class Booking(models.Model):
|
|||
def get_backoffice_url(self):
|
||||
return translate_from_publik_url(self.backoffice_url)
|
||||
|
||||
|
||||
class BookingCheck(models.Model):
|
||||
booking = models.OneToOneField(Booking, on_delete=models.CASCADE, related_name='user_check')
|
||||
|
||||
presence = models.BooleanField()
|
||||
|
||||
start_time = models.TimeField(_('Arrival'), null=True)
|
||||
end_time = models.TimeField(_('Departure'), null=True)
|
||||
computed_start_time = models.TimeField(null=True)
|
||||
computed_end_time = models.TimeField(null=True)
|
||||
|
||||
type_slug = models.CharField(max_length=160, blank=True, null=True)
|
||||
type_label = models.CharField(max_length=150, blank=True, null=True)
|
||||
|
||||
def _get_previous_and_next_slots(self, _time):
|
||||
minutes = {
|
||||
'hour': 60,
|
||||
'half_hour': 30,
|
||||
'quarter': 15,
|
||||
}[self.event.agenda.invoicing_unit]
|
||||
}[self.booking.event.agenda.invoicing_unit]
|
||||
|
||||
time_minutes = _time.hour * 60 + _time.minute
|
||||
previous_slot_minutes = math.trunc(time_minutes / minutes) * minutes
|
||||
|
@ -2998,16 +3014,16 @@ class Booking(models.Model):
|
|||
return previous_slot, next_slot
|
||||
|
||||
def get_computed_start_time(self):
|
||||
if not hasattr(self, 'user_check') or self.user_check.start_time is None:
|
||||
if self.start_time is None:
|
||||
return None
|
||||
|
||||
start_time = self.user_check.start_time
|
||||
if self.start_time:
|
||||
start_time = min(start_time, self.start_time)
|
||||
if self.event.agenda.invoicing_unit == 'minute':
|
||||
start_time = self.start_time
|
||||
if self.booking.start_time:
|
||||
start_time = min(self.start_time, self.booking.start_time)
|
||||
if self.booking.event.agenda.invoicing_unit == 'minute':
|
||||
return start_time
|
||||
|
||||
tolerance = self.event.agenda.invoicing_tolerance
|
||||
tolerance = self.booking.event.agenda.invoicing_tolerance
|
||||
|
||||
# compute previous and next slot
|
||||
previous_slot, next_slot = self._get_previous_and_next_slots(start_time)
|
||||
|
@ -3020,16 +3036,16 @@ class Booking(models.Model):
|
|||
return previous_slot
|
||||
|
||||
def get_computed_end_time(self):
|
||||
if not hasattr(self, 'user_check') or self.user_check.end_time is None:
|
||||
if self.end_time is None:
|
||||
return None
|
||||
|
||||
end_time = self.user_check.end_time
|
||||
if self.end_time:
|
||||
end_time = max(end_time, self.end_time)
|
||||
if self.event.agenda.invoicing_unit == 'minute':
|
||||
end_time = self.end_time
|
||||
if self.booking.end_time:
|
||||
end_time = max(self.end_time, self.booking.end_time)
|
||||
if self.booking.event.agenda.invoicing_unit == 'minute':
|
||||
return end_time
|
||||
|
||||
tolerance = self.event.agenda.invoicing_tolerance
|
||||
tolerance = self.booking.event.agenda.invoicing_tolerance
|
||||
|
||||
# compute previous and next slot
|
||||
previous_slot, next_slot = self._get_previous_and_next_slots(end_time)
|
||||
|
@ -3055,18 +3071,6 @@ class Booking(models.Model):
|
|||
return True
|
||||
|
||||
|
||||
class BookingCheck(models.Model):
|
||||
booking = models.OneToOneField(Booking, on_delete=models.CASCADE, related_name='user_check')
|
||||
|
||||
presence = models.BooleanField()
|
||||
|
||||
start_time = models.TimeField(_('Arrival'), null=True)
|
||||
end_time = models.TimeField(_('Departure'), null=True)
|
||||
|
||||
type_slug = models.CharField(max_length=160, blank=True, null=True)
|
||||
type_label = models.CharField(max_length=150, blank=True, null=True)
|
||||
|
||||
|
||||
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
|
||||
|
||||
|
||||
|
|
|
@ -344,6 +344,8 @@ class BookingSerializer(serializers.ModelSerializer):
|
|||
if self.instance.event.agenda.kind == 'events' and self.instance.event.agenda.partial_bookings:
|
||||
self.instance.user_check_start_time = self.instance.user_check.start_time
|
||||
self.instance.user_check_end_time = self.instance.user_check.end_time
|
||||
self.instance.computed_start_time = self.instance.user_check.computed_start_time
|
||||
self.instance.computed_end_time = self.instance.user_check.computed_end_time
|
||||
for key in ['', 'user_check_', 'computed_']:
|
||||
start_key, end_key, minutes_key = (
|
||||
'%sstart_time' % key,
|
||||
|
|
|
@ -652,13 +652,9 @@ class PartialBookingCheckForm(forms.ModelForm):
|
|||
self.instance.delete()
|
||||
return self.instance
|
||||
|
||||
super().save()
|
||||
self.instance.refresh_computed_times()
|
||||
|
||||
self.instance.booking.refresh_from_db()
|
||||
if self.instance.booking.refresh_computed_times():
|
||||
self.instance.booking.save()
|
||||
|
||||
return self.instance
|
||||
return super().save()
|
||||
|
||||
|
||||
class EventsTimesheetForm(forms.Form):
|
||||
|
|
|
@ -103,15 +103,15 @@
|
|||
</div>
|
||||
<div class="registrant--bar-container">
|
||||
{% for booking in user.bookings %}
|
||||
{% if booking.user_check and booking.computed_start_time != None and booking.computed_end_time != None %}
|
||||
{% if booking.user_check.computed_start_time and booking.user_check.computed_end_time %}
|
||||
<p
|
||||
class="registrant--bar clearfix computed {{ booking.check_css_class }}"
|
||||
title="{% trans "Computed period" %}"
|
||||
style="left: {{ booking.computed_css_left }}%; width: {{ booking.computed_css_width }}%;"
|
||||
style="left: {{ booking.user_check.computed_css_left }}%; width: {{ booking.user_check.computed_css_width }}%;"
|
||||
>
|
||||
<strong class="sr-only">{% trans "Computed period:" %}</strong>
|
||||
<time class="start-time" datetime="{{ booking.computed_start_time|time:"H:i" }}">{{ booking.computed_start_time|time:"H:i" }}</time>
|
||||
<time class="end-time" datetime="{{ booking.computed_end_time|time:"H:i" }}">{{ booking.computed_end_time|time:"H:i" }}</time>
|
||||
<time class="start-time" datetime="{{ booking.user_check.computed_start_time|time:"H:i" }}">{{ booking.user_check.computed_start_time|time:"H:i" }}</time>
|
||||
<time class="end-time" datetime="{{ booking.user_check.computed_end_time|time:"H:i" }}">{{ booking.user_check.computed_end_time|time:"H:i" }}</time>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -1662,10 +1662,12 @@ class AgendaDayView(EventChecksMixin, AgendaDateView, DayArchiveView):
|
|||
booking.check_css_width = get_time_ratio(
|
||||
booking.user_check.end_time, booking.user_check.start_time
|
||||
)
|
||||
if booking.computed_start_time is not None and booking.computed_end_time is not None:
|
||||
booking.computed_css_left = get_time_ratio(booking.computed_start_time, start_time)
|
||||
booking.computed_css_width = get_time_ratio(
|
||||
booking.computed_end_time, booking.computed_start_time
|
||||
if booking.user_check.computed_start_time and booking.user_check.computed_end_time:
|
||||
booking.user_check.computed_css_left = get_time_ratio(
|
||||
booking.user_check.computed_start_time, start_time
|
||||
)
|
||||
booking.user_check.computed_css_width = get_time_ratio(
|
||||
booking.user_check.computed_end_time, booking.user_check.computed_start_time
|
||||
)
|
||||
|
||||
users_info = {}
|
||||
|
|
|
@ -1398,10 +1398,10 @@ def test_events_check_status_events(app, user, partial_bookings):
|
|||
booking1.mark_user_presence(
|
||||
check_type_slug='foo-reason', start_time=datetime.time(7, 55), end_time=datetime.time(17, 15)
|
||||
)
|
||||
booking1.refresh_computed_times()
|
||||
booking1.save()
|
||||
assert booking1.computed_start_time == datetime.time(8, 0)
|
||||
assert booking1.computed_end_time == datetime.time(17, 30)
|
||||
booking1.user_check.refresh_computed_times()
|
||||
booking1.user_check.save()
|
||||
assert booking1.user_check.computed_start_time == datetime.time(8, 0)
|
||||
assert booking1.user_check.computed_end_time == datetime.time(17, 30)
|
||||
booking2 = Booking.objects.create(event=event, user_external_id='child:42')
|
||||
booking2.mark_user_presence(check_type_slug='foo-reason')
|
||||
|
||||
|
@ -1863,8 +1863,8 @@ def test_events_check_lock(app, user, partial_bookings):
|
|||
end_time=datetime.time(17, 15),
|
||||
)
|
||||
# computed times are None, because refresh_computed_times was not called in this test
|
||||
assert booking.computed_start_time is None
|
||||
assert booking.computed_end_time is None
|
||||
assert booking.user_check.computed_start_time is None
|
||||
assert booking.user_check.computed_end_time is None
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
url = '/api/agendas/events/check-lock/'
|
||||
|
@ -1880,8 +1880,8 @@ def test_events_check_lock(app, user, partial_bookings):
|
|||
assert event.check_locked is True
|
||||
booking.refresh_from_db()
|
||||
# computed times are still None, refresh_computed_times is not called on lock
|
||||
assert booking.computed_start_time is None
|
||||
assert booking.computed_end_time is None
|
||||
assert booking.user_check.computed_start_time is None
|
||||
assert booking.user_check.computed_end_time is None
|
||||
|
||||
params['check_locked'] = False
|
||||
resp = app.post_json(url, params=params)
|
||||
|
@ -1890,12 +1890,12 @@ def test_events_check_lock(app, user, partial_bookings):
|
|||
assert event.check_locked is False
|
||||
booking.refresh_from_db()
|
||||
if partial_bookings:
|
||||
assert booking.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.computed_end_time == datetime.time(18, 0)
|
||||
assert booking.user_check.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.user_check.computed_end_time == datetime.time(18, 0)
|
||||
else:
|
||||
# not refreshed, not a partial_bookings agenda
|
||||
assert booking.computed_start_time is None
|
||||
assert booking.computed_end_time is None
|
||||
assert booking.user_check.computed_start_time is None
|
||||
assert booking.user_check.computed_end_time is None
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-05-30 14:00')
|
||||
|
|
|
@ -56,8 +56,8 @@ def test_options_partial_bookings_invoicing_settings(app, admin_user):
|
|||
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.computed_start_time == datetime.time(10, 0)
|
||||
assert booking.computed_end_time == datetime.time(15, 0)
|
||||
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)
|
||||
|
@ -76,8 +76,8 @@ def test_options_partial_bookings_invoicing_settings(app, admin_user):
|
|||
assert agenda.invoicing_unit == 'half_hour'
|
||||
assert agenda.invoicing_tolerance == 10
|
||||
booking.refresh_from_db()
|
||||
assert booking.computed_start_time == datetime.time(11, 0)
|
||||
assert booking.computed_end_time == datetime.time(14, 0)
|
||||
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
|
||||
|
@ -357,8 +357,8 @@ def test_manager_partial_bookings_check(check_types, app, admin_user):
|
|||
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.computed_start_time == datetime.time(11, 0)
|
||||
assert booking.computed_end_time == datetime.time(14, 0)
|
||||
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
|
||||
|
@ -379,8 +379,8 @@ def test_manager_partial_bookings_check(check_types, app, admin_user):
|
|||
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.computed_start_time == datetime.time(11, 0)
|
||||
assert booking.computed_end_time == datetime.time(13, 30)
|
||||
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'),
|
||||
|
|
|
@ -3973,7 +3973,7 @@ def test_booking_get_computed_start_time(start_time, user_check_start_time, tole
|
|||
|
||||
booking = Booking.objects.create(event=event, start_time=start_time)
|
||||
booking.mark_user_presence(start_time=user_check_start_time)
|
||||
assert booking.get_computed_start_time() == expected
|
||||
assert booking.user_check.get_computed_start_time() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -4030,7 +4030,7 @@ def test_booking_get_computed_end_time(end_time, user_check_end_time, tolerance,
|
|||
|
||||
booking = Booking.objects.create(event=event, end_time=end_time)
|
||||
booking.mark_user_presence(end_time=user_check_end_time)
|
||||
assert booking.get_computed_end_time() == expected
|
||||
assert booking.user_check.get_computed_end_time() == expected
|
||||
|
||||
|
||||
def test_agenda_refresh_booking_computed_times():
|
||||
|
@ -4055,19 +4055,19 @@ def test_agenda_refresh_booking_computed_times():
|
|||
booking.mark_user_presence(start_time=datetime.time(7, 55), end_time=datetime.time(17, 15))
|
||||
|
||||
def reset_booking():
|
||||
booking.computed_start_time = None
|
||||
booking.computed_end_time = None
|
||||
booking.save()
|
||||
booking.user_check.computed_start_time = None
|
||||
booking.user_check.computed_end_time = None
|
||||
booking.user_check.save()
|
||||
|
||||
def test_booking(success):
|
||||
agenda.refresh_booking_computed_times()
|
||||
booking.refresh_from_db()
|
||||
if success is True:
|
||||
assert booking.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.computed_end_time == datetime.time(18, 0)
|
||||
assert booking.user_check.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.user_check.computed_end_time == datetime.time(18, 0)
|
||||
reset_booking()
|
||||
else:
|
||||
assert booking.computed_start_time is booking.computed_end_time is None
|
||||
assert booking.user_check.computed_start_time is booking.user_check.computed_end_time is None
|
||||
|
||||
test_booking(True)
|
||||
|
||||
|
@ -4152,19 +4152,19 @@ def test_event_refresh_booking_computed_times():
|
|||
booking.mark_user_presence(start_time=datetime.time(7, 55), end_time=datetime.time(17, 15))
|
||||
|
||||
def reset_booking():
|
||||
booking.computed_start_time = None
|
||||
booking.computed_end_time = None
|
||||
booking.save()
|
||||
booking.user_check.computed_start_time = None
|
||||
booking.user_check.computed_end_time = None
|
||||
booking.user_check.save()
|
||||
|
||||
def test_booking(success):
|
||||
event.refresh_booking_computed_times()
|
||||
booking.refresh_from_db()
|
||||
if success is True:
|
||||
assert booking.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.computed_end_time == datetime.time(18, 0)
|
||||
assert booking.user_check.computed_start_time == datetime.time(7, 0)
|
||||
assert booking.user_check.computed_end_time == datetime.time(18, 0)
|
||||
reset_booking()
|
||||
else:
|
||||
assert booking.computed_start_time is booking.computed_end_time is None
|
||||
assert booking.user_check.computed_start_time is booking.user_check.computed_end_time is None
|
||||
|
||||
test_booking(True)
|
||||
|
||||
|
|
|
@ -418,6 +418,8 @@ def test_migration_booking_check_data(transactional_db):
|
|||
user_check_type_label='XXX',
|
||||
user_check_start_time=datetime.time(12, 0),
|
||||
user_check_end_time=datetime.time(14, 0),
|
||||
computed_start_time=datetime.time(12, 30),
|
||||
computed_end_time=datetime.time(14, 30),
|
||||
)
|
||||
|
||||
executor = MigrationExecutor(connection)
|
||||
|
@ -437,3 +439,5 @@ def test_migration_booking_check_data(transactional_db):
|
|||
assert with_check_type.user_check.type_label == 'XXX'
|
||||
assert with_check_type.user_check.start_time == datetime.time(12, 0)
|
||||
assert with_check_type.user_check.end_time == datetime.time(14, 0)
|
||||
assert with_check_type.computed_start_time == datetime.time(12, 30)
|
||||
assert with_check_type.computed_end_time == datetime.time(14, 30)
|
||||
|
|
Loading…
Reference in New Issue