diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 51ce9376e534d149ffa315c50524867e8ff4f1dc..681ce0705b25842c1f24f9789569a2d88115b72c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,8 +3,8 @@ include:
     file: /ci/general.yml
   - project: "AlekSIS/official/AlekSIS"
     file: /ci/prepare/lock.yml
-  #   - project: "AlekSIS/official/AlekSIS"
-  #     file: /ci/test.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/test.yml
   - project: "AlekSIS/official/AlekSIS"
     file: /ci/test/lint.yml
   - project: "AlekSIS/official/AlekSIS"
diff --git a/aleksis/apps/lesrooster/models.py b/aleksis/apps/lesrooster/models.py
index 23c44e615ab088cb811be2a1a8c884a207d28b50..1592ae3c9fc05b82cb6a2a7910fc43c12f328efa 100644
--- a/aleksis/apps/lesrooster/models.py
+++ b/aleksis/apps/lesrooster/models.py
@@ -211,6 +211,24 @@ class Slot(ExtensiblePolymorphicModel):
     def get_last_datetime(self) -> datetime:
         return self.get_datetime_end(self.time_grid.validity_range.date_end)
 
+    def build_recurrence(self, *args, **kwargs) -> recurrence.Recurrence:
+        """Build a recurrence for this slot respecting the validity range borders."""
+        pattern = recurrence.Recurrence(
+            dtstart=timezone.make_aware(
+                datetime.combine(self.time_grid.validity_range.date_start, self.time_start)
+            ),
+            rrules=[
+                recurrence.Rule(
+                    *args,
+                    **kwargs,
+                    until=timezone.make_aware(
+                        datetime.combine(self.time_grid.validity_range.date_end, self.time_end)
+                    ),
+                )
+            ],
+        )
+        return pattern
+
     class Meta:
         constraints = [
             models.UniqueConstraint(
@@ -305,6 +323,10 @@ class Lesson(TeacherPropertiesMixin, RoomPropertiesMixin, ExtensibleModel):
         if self.slot_start.time_grid != self.slot_end.time_grid:
             raise ValidationError(_("The slots must be in the same time grid."))
 
+    def build_recurrence(self, *args, **kwargs) -> "recurrence.Recurrence":
+        """Build a recurrence for this lesson respecting the validity range borders."""
+        return self.slot_start.build_recurrence(*args, **kwargs)
+
     @property
     def real_recurrence(self) -> "recurrence.Recurrence":
         """Get the real recurrence adjusted to the validity range and including holidays."""
@@ -431,6 +453,10 @@ class Supervision(TeacherPropertiesMixin, RoomPropertiesMixin, ExtensibleModel):
         verbose_name = _("Supervision")
         verbose_name_plural = _("Supervisions")
 
+    def build_recurrence(self, *args, **kwargs) -> "recurrence.Recurrence":
+        """Build a recurrence for this supervision respecting the validity range borders."""
+        return self.break_slot.build_recurrence(*args, **kwargs)
+
     @property
     def real_recurrence(self) -> "recurrence.Recurrence":
         """Get the real recurrence adjusted to the validity range and including holidays."""
diff --git a/aleksis/apps/lesrooster/tests/test_recurrence.py b/aleksis/apps/lesrooster/tests/test_recurrence.py
new file mode 100644
index 0000000000000000000000000000000000000000..78c204b385338c7d2f8c542a3d6e6189132e6876
--- /dev/null
+++ b/aleksis/apps/lesrooster/tests/test_recurrence.py
@@ -0,0 +1,79 @@
+from datetime import date, datetime, time
+from pprint import pprint
+
+from django.utils.timezone import get_current_timezone
+
+import pytest
+import recurrence
+
+from aleksis.apps.lesrooster.models import (
+    BreakSlot,
+    Lesson,
+    Slot,
+    Supervision,
+    TimeGrid,
+    ValidityRange,
+)
+from aleksis.core.models import SchoolTerm
+
+pytestmark = pytest.mark.django_db
+
+
+@pytest.fixture
+def school_term():
+    date_start = date(2024, 1, 1)
+    date_end = date(2024, 6, 1)
+    school_term = SchoolTerm.objects.create(name="Test", date_start=date_start, date_end=date_end)
+    return school_term
+
+
+@pytest.fixture
+def validity_range(school_term):
+    validity_range = ValidityRange.objects.create(
+        school_term=school_term, date_start=school_term.date_start, date_end=school_term.date_end
+    )
+    return validity_range
+
+
+@pytest.fixture
+def time_grid(validity_range):
+    return TimeGrid.objects.get(validity_range=validity_range, group=None)
+
+
+def test_slot_build_recurrence(time_grid):
+    slot = Slot.objects.create(
+        time_grid=time_grid, weekday=0, period=1, time_start=time(8, 0), time_end=time(9, 0)
+    )
+    rec = slot.build_recurrence(recurrence.WEEKLY)
+
+    pprint(rec.rrules[0].__dict__)
+
+    assert rec.dtstart == datetime(2024, 1, 1, 8, 0, tzinfo=get_current_timezone())
+    assert len(rec.rrules) == 1
+
+    rrule = rec.rrules[0]
+    assert rrule.until == datetime(2024, 6, 1, 9, 0, tzinfo=get_current_timezone())
+    assert rrule.freq == 2
+    assert rrule.interval == 1
+
+
+def test_lesson_recurrence(time_grid):
+    slot = Slot.objects.create(
+        time_grid=time_grid, weekday=0, period=1, time_start=time(8, 0), time_end=time(9, 0)
+    )
+    break_slot = BreakSlot.objects.create(
+        time_grid=time_grid, weekday=0, time_start=time(9, 0), time_end=time(9, 15)
+    )
+
+    lesson = Lesson.objects.create(
+        slot_start=slot,
+        slot_end=slot,
+    )
+
+    assert lesson.build_recurrence(recurrence.WEEKLY) == slot.build_recurrence(recurrence.WEEKLY)
+
+    supervision = Supervision.objects.create(break_slot=break_slot)
+
+    assert supervision.build_recurrence(recurrence.WEEKLY) == break_slot.build_recurrence(
+        recurrence.WEEKLY
+    )