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