From 0afb82c723c777ca14d07f291a217dfaede18210 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Wed, 8 Jan 2025 00:33:49 +0100 Subject: [PATCH] Generalize overlap handling mechanism in separate class method --- aleksis/apps/kolego/models/absence.py | 168 ++++++++++++++------------ 1 file changed, 91 insertions(+), 77 deletions(-) diff --git a/aleksis/apps/kolego/models/absence.py b/aleksis/apps/kolego/models/absence.py index da1326d..af62ef7 100644 --- a/aleksis/apps/kolego/models/absence.py +++ b/aleksis/apps/kolego/models/absence.py @@ -144,6 +144,94 @@ class Absence(FreeBusy): """Return the title of the calendar event.""" return "" + @classmethod + def clear_or_extend_absences_in_timespan( + cls, + person: Person, + datetime_start: datetime, + datetime_end: datetime, + reason: AbsenceReason | None = None, + ) -> list: + extended_absence = None + modified_absences = [] + + events_within = cls.get_objects( + None, + {"person": person.pk}, + start=datetime_start, + end=datetime_end, + ) + + for event_within in events_within: + event_within_datetime_start = ( + event_within.datetime_start + if event_within.datetime_start + else datetime.combine(event_within.date_start, time.min) + ) + event_within_datetime_end = ( + event_within.datetime_end + if event_within.datetime_end + else datetime.combine(event_within.date_end, time.max) + ) + + # If overlapping absence has the same reason, just extend it + if reason is not None and event_within.reason == reason: + event_within.datetime_start = min(datetime_start, event_within_datetime_start) + event_within.datetime_end = max(datetime_end, event_within_datetime_end) + event_within.save(skip_overlap_handling=True) + extended_absence = event_within + modified_absences.append(event_within) + else: + if ( + datetime_start > event_within_datetime_start + and datetime_end < event_within_datetime_end + ): + # Cut existing event in two parts + # First, cut end date of existing one + event_within.datetime_end = datetime_start + event_within.save(skip_overlap_handling=True) + modified_absences.append(event_within) + # Then, create new event based on existing one filling up the remaining time + end_filler_event = event_within + end_filler_event.pk = None + end_filler_event.id = None + end_filler_event.calendarevent_ptr_id = None + end_filler_event.freebusy_ptr_id = None + end_filler_event._state.adding = True + end_filler_event.datetime_start = datetime_end + end_filler_event.datetime_end = event_within_datetime_end + + end_filler_event.save(skip_overlap_handling=True) + modified_absences.append(end_filler_event) + elif ( + datetime_start <= event_within_datetime_start + and datetime_end >= event_within_datetime_end + ): + # Delete existing event + if event_within in modified_absences: + modified_absences.remove(event_within) + event_within.delete() + elif ( + datetime_start > event_within_datetime_start + and datetime_start < event_within_datetime_end + and datetime_end >= event_within_datetime_end + ): + # Cut end of existing event + event_within.datetime_end = datetime_start + event_within.save(skip_overlap_handling=True) + modified_absences.append(event_within) + elif ( + datetime_start <= event_within_datetime_start + and datetime_end < event_within_datetime_end + and datetime_end > event_within_datetime_start + ): + # Cut start of existing event + event_within.datetime_start = datetime_end + event_within.save(skip_overlap_handling=True) + modified_absences.append(event_within) + + return modified_absences, extended_absence + def __str__(self): return f"{self.person} ({self.datetime_start} - {self.datetime_end})" @@ -164,84 +252,10 @@ class Absence(FreeBusy): else datetime.combine(self.date_end, time.max) ).astimezone(self.timezone) - events_within = Absence.get_objects( - None, - {"person": self.person.pk}, - start=new_datetime_start, - end=new_datetime_end, + modified_absences, extended_absence = self.clear_or_extend_absences_in_timespan( + self.person, new_datetime_start, new_datetime_end, self.reason ) - - for event_within in events_within: - event_within_datetime_start = ( - event_within.datetime_start - if event_within.datetime_start - else datetime.combine(event_within.date_start, time.min) - ) - event_within_datetime_end = ( - event_within.datetime_end - if event_within.datetime_end - else datetime.combine(event_within.date_end, time.max) - ) - - # If overlapping absence has the same reason, just extend it - if event_within.reason == self.reason: - event_within.datetime_start = min( - new_datetime_start, event_within_datetime_start - ) - event_within.datetime_end = max(new_datetime_end, event_within_datetime_end) - event_within.save(skip_overlap_handling=True) - extended_absence = event_within - modified_absences.append(event_within) - else: - if ( - new_datetime_start > event_within_datetime_start - and new_datetime_end < event_within_datetime_end - ): - # Cut existing event in two parts - # First, cut end date of existing one - event_within.datetime_end = new_datetime_start - event_within.save(skip_overlap_handling=True) - modified_absences.append(event_within) - # Then, create new event based on existing one filling up the remaining time - end_filler_event = event_within - end_filler_event.pk = None - end_filler_event.id = None - end_filler_event.calendarevent_ptr_id = None - end_filler_event.freebusy_ptr_id = None - end_filler_event._state.adding = True - end_filler_event.datetime_start = new_datetime_end - end_filler_event.datetime_end = event_within_datetime_end - - end_filler_event.save(skip_overlap_handling=True) - modified_absences.append(end_filler_event) - elif ( - new_datetime_start <= event_within_datetime_start - and new_datetime_end >= event_within_datetime_end - ): - # Delete existing event - if event_within in modified_absences: - modified_absences.remove(event_within) - event_within.delete() - elif ( - new_datetime_start > event_within_datetime_start - and new_datetime_start < event_within_datetime_end - and new_datetime_end >= event_within_datetime_end - ): - # Cut end of existing event - event_within.datetime_end = new_datetime_start - event_within.save(skip_overlap_handling=True) - modified_absences.append(event_within) - elif ( - new_datetime_start <= event_within_datetime_start - and new_datetime_end < event_within_datetime_end - and new_datetime_end > event_within_datetime_start - ): - # Cut start of existing event - event_within.datetime_start = new_datetime_end - event_within.save(skip_overlap_handling=True) - modified_absences.append(event_within) - - modified_absences.append(self) + modified_absences.append(self) if extended_absence is not None: self._extended_absence = extended_absence -- GitLab