agendas: partial bookings, compute double booking check (#82848)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
9d1c33970c
commit
e4864ea95b
|
@ -2947,10 +2947,43 @@ class Booking(models.Model):
|
|||
|
||||
def refresh_computed_times(self, commit=False):
|
||||
to_update = []
|
||||
for user_check in self.user_checks.all():
|
||||
user_checks = sorted(self.user_checks.all(), key=lambda a: a.start_time or datetime.time(0))
|
||||
many_user_checks = len(user_checks) > 1
|
||||
assert len(user_checks) < 3 # 2 user_checks max !
|
||||
|
||||
if user_checks and not many_user_checks:
|
||||
# only one user_check
|
||||
user_check = user_checks[0]
|
||||
changed = user_check._refresh_computed_times()
|
||||
if changed:
|
||||
to_update.append(user_check)
|
||||
|
||||
elif many_user_checks:
|
||||
# two user_checks
|
||||
user_check1 = user_checks[0]
|
||||
user_check2 = user_checks[1]
|
||||
compute_one_first = True
|
||||
if user_check1.presence != user_check2.presence:
|
||||
# one user check is presence, the other is absence
|
||||
if user_check1.presence is False:
|
||||
# the second check is presence, it should be computed first
|
||||
compute_one_first = False
|
||||
|
||||
if compute_one_first:
|
||||
changed = user_check1._refresh_computed_times(adjust_end_to_booking=False)
|
||||
if changed:
|
||||
to_update.append(user_check1)
|
||||
changed = user_check2._refresh_computed_times(other_user_check=user_check1)
|
||||
if changed:
|
||||
to_update.append(user_check2)
|
||||
else:
|
||||
changed = user_check2._refresh_computed_times(adjust_start_to_booking=False)
|
||||
if changed:
|
||||
to_update.append(user_check2)
|
||||
changed = user_check1._refresh_computed_times(other_user_check=user_check2)
|
||||
if changed:
|
||||
to_update.append(user_check1)
|
||||
|
||||
if commit and to_update:
|
||||
BookingCheck.objects.bulk_update(to_update, ['computed_start_time', 'computed_end_time'])
|
||||
return to_update
|
||||
|
@ -3073,13 +3106,19 @@ class BookingCheck(models.Model):
|
|||
next_slot = datetime.time(*divmod(previous_slot_minutes + minutes, 60))
|
||||
return previous_slot, next_slot
|
||||
|
||||
def get_computed_start_time(self):
|
||||
def get_computed_start_time(self, other_user_check=None, adjust_to_booking=True):
|
||||
if self.start_time is None:
|
||||
return None
|
||||
|
||||
start_time = self.start_time
|
||||
if self.booking.start_time:
|
||||
if self.booking.start_time and adjust_to_booking:
|
||||
# adjust start_time to the start of the booking if requested
|
||||
start_time = min(self.start_time, self.booking.start_time)
|
||||
if other_user_check and other_user_check.computed_start_time and other_user_check.computed_end_time:
|
||||
# if other user_check exists and is completely computed
|
||||
if other_user_check.start_time < self.start_time:
|
||||
# if other user_check is the first of the day, start_time is the end of other user_check
|
||||
start_time = other_user_check.computed_end_time
|
||||
if self.booking.event.agenda.invoicing_unit == 'minute':
|
||||
return start_time
|
||||
|
||||
|
@ -3095,13 +3134,19 @@ class BookingCheck(models.Model):
|
|||
# else take previous_slot
|
||||
return previous_slot
|
||||
|
||||
def get_computed_end_time(self):
|
||||
def get_computed_end_time(self, other_user_check=None, adjust_to_booking=True):
|
||||
if self.end_time is None:
|
||||
return None
|
||||
|
||||
end_time = self.end_time
|
||||
if self.booking.end_time:
|
||||
if self.booking.end_time and adjust_to_booking:
|
||||
# adjust end_time to the end of the booking if requested
|
||||
end_time = max(self.end_time, self.booking.end_time)
|
||||
if other_user_check and other_user_check.computed_start_time and other_user_check.computed_end_time:
|
||||
# if other user_check exists and is completely computed
|
||||
if other_user_check.start_time > self.start_time:
|
||||
# if other user_check is the second of the day, end_time is the start of other user_check
|
||||
end_time = other_user_check.computed_start_time
|
||||
if self.booking.event.agenda.invoicing_unit == 'minute':
|
||||
return end_time
|
||||
|
||||
|
@ -3117,11 +3162,17 @@ class BookingCheck(models.Model):
|
|||
# else take next_slot
|
||||
return next_slot
|
||||
|
||||
def _refresh_computed_times(self):
|
||||
def _refresh_computed_times(
|
||||
self, other_user_check=None, adjust_start_to_booking=True, adjust_end_to_booking=True
|
||||
):
|
||||
old_computed_start_time = self.computed_start_time
|
||||
old_computed_end_time = self.computed_end_time
|
||||
self.computed_start_time = self.get_computed_start_time()
|
||||
self.computed_end_time = self.get_computed_end_time()
|
||||
self.computed_start_time = self.get_computed_start_time(
|
||||
other_user_check=other_user_check, adjust_to_booking=adjust_start_to_booking
|
||||
)
|
||||
self.computed_end_time = self.get_computed_end_time(
|
||||
other_user_check=other_user_check, adjust_to_booking=adjust_end_to_booking
|
||||
)
|
||||
# return True if changed, else False
|
||||
if (
|
||||
old_computed_start_time == self.computed_start_time
|
||||
|
|
|
@ -4035,6 +4035,197 @@ def test_booking_get_computed_end_time(end_time, user_check_end_time, tolerance,
|
|||
assert booking.user_check.get_computed_end_time() == expected
|
||||
|
||||
|
||||
def test_booking_refresh_computed_times_only_one_user_check():
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', kind='events', invoicing_unit='half_hour', invoicing_tolerance=10
|
||||
)
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
|
||||
places=10,
|
||||
)
|
||||
booking = Booking.objects.create(
|
||||
event=event, start_time=datetime.time(8, 0), end_time=datetime.time(17, 0)
|
||||
)
|
||||
|
||||
# only one user_check, presence or absence, same behavior
|
||||
booking_check = BookingCheck.objects.create(
|
||||
booking=booking, presence=True, start_time=datetime.time(7, 45), end_time=datetime.time(17, 15)
|
||||
)
|
||||
booking.refresh_computed_times(commit=True)
|
||||
booking_check.refresh_from_db()
|
||||
assert booking_check.computed_start_time == datetime.time(7, 30)
|
||||
assert booking_check.computed_end_time == datetime.time(17, 30)
|
||||
|
||||
booking_check.presence = False
|
||||
booking.refresh_computed_times(commit=True)
|
||||
booking_check.refresh_from_db()
|
||||
assert booking_check.computed_start_time == datetime.time(7, 30)
|
||||
assert booking_check.computed_end_time == datetime.time(17, 30)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'presence1, start_time1, end_time1, presence2, start_time2, end_time2, expected_start1, expected_end1, expected_start2, expected_end2',
|
||||
[
|
||||
# first presence, then absence
|
||||
(
|
||||
True,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
False,
|
||||
datetime.time(13, 0), # no overlapping
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
(
|
||||
True,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
False,
|
||||
datetime.time(12, 15), # computed start should be 12H00
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30), # but it is adjusted to 12H30
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
# first absence, then presence
|
||||
(
|
||||
False,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 0), # no overlapping
|
||||
True,
|
||||
datetime.time(13, 15),
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(13, 0),
|
||||
datetime.time(13, 0),
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
(
|
||||
False,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 45), # computed end should be 13H00
|
||||
True,
|
||||
datetime.time(12, 45),
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30), # but it is adjusted to 12H30
|
||||
datetime.time(12, 30),
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
# both absence
|
||||
(
|
||||
True,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
True,
|
||||
datetime.time(13, 0), # no overlapping
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
(
|
||||
True,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
True,
|
||||
datetime.time(12, 15), # computed start should be 12H00
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30), # but it is adjusted to 12H30
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
# both presence
|
||||
(
|
||||
False,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
False,
|
||||
datetime.time(13, 0), # no overlapping
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
(
|
||||
False,
|
||||
datetime.time(7, 45),
|
||||
datetime.time(12, 15),
|
||||
False,
|
||||
datetime.time(12, 15), # computed start should be 12H00
|
||||
datetime.time(15, 0),
|
||||
datetime.time(7, 30),
|
||||
datetime.time(12, 30),
|
||||
datetime.time(12, 30), # but it is adjusted to 12H30
|
||||
datetime.time(17, 0),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_booking_refresh_computed_times_two_user_check(
|
||||
presence1,
|
||||
start_time1,
|
||||
end_time1,
|
||||
presence2,
|
||||
start_time2,
|
||||
end_time2,
|
||||
expected_start1,
|
||||
expected_end1,
|
||||
expected_start2,
|
||||
expected_end2,
|
||||
):
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', kind='events', invoicing_unit='half_hour', invoicing_tolerance=10
|
||||
)
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
|
||||
places=10,
|
||||
)
|
||||
booking = Booking.objects.create(
|
||||
event=event, start_time=datetime.time(8, 0), end_time=datetime.time(17, 0)
|
||||
)
|
||||
user_check1 = BookingCheck.objects.create(
|
||||
booking=booking, presence=presence1, start_time=start_time1, end_time=end_time1
|
||||
)
|
||||
user_check2 = BookingCheck.objects.create(
|
||||
booking=booking, presence=presence2, start_time=start_time2, end_time=end_time2
|
||||
)
|
||||
booking.refresh_computed_times(commit=True)
|
||||
user_check1.refresh_from_db()
|
||||
user_check2.refresh_from_db()
|
||||
assert user_check1.computed_start_time == expected_start1
|
||||
assert user_check1.computed_end_time == expected_end1
|
||||
assert user_check2.computed_start_time == expected_start2
|
||||
assert user_check2.computed_end_time == expected_end2
|
||||
|
||||
|
||||
def test_booking_refresh_computed_times_more_than_two_user_check():
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', kind='events', invoicing_unit='half_hour', invoicing_tolerance=10
|
||||
)
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
|
||||
places=10,
|
||||
)
|
||||
booking = Booking.objects.create(
|
||||
event=event, start_time=datetime.time(8, 0), end_time=datetime.time(17, 0)
|
||||
)
|
||||
BookingCheck.objects.create(booking=booking, presence=True)
|
||||
BookingCheck.objects.create(booking=booking, presence=True)
|
||||
BookingCheck.objects.create(booking=booking, presence=True)
|
||||
with pytest.raises(AssertionError):
|
||||
booking.refresh_computed_times(commit=True)
|
||||
|
||||
|
||||
def test_agenda_refresh_booking_computed_times():
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda',
|
||||
|
|
Loading…
Reference in New Issue