diff --git a/aleksis/apps/chronos/frontend/components/SubstitutionOverview.vue b/aleksis/apps/chronos/frontend/components/SubstitutionOverview.vue
index 59b5a12b9d3fed15a9766af09e4040b3ef8b2e25..35831f8c20b90c9afcc969af4efe7eae69deced1 100644
--- a/aleksis/apps/chronos/frontend/components/SubstitutionOverview.vue
+++ b/aleksis/apps/chronos/frontend/components/SubstitutionOverview.vue
@@ -5,7 +5,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
 
 import {
   amendedLessonsFromAbsences,
-  patchAmendLessonsWithAmends,
+  createOrUpdateSubstitutions,
   groupsByOwner,
 } from "./amendLesson.graphql";
 
@@ -118,7 +118,7 @@ export default {
   data() {
     return {
       gqlQuery: amendedLessonsFromAbsences,
-      gqlPatchMutation: patchAmendLessonsWithAmends,
+      gqlPatchMutation: createOrUpdateSubstitutions,
       groups: [],
     };
   },
diff --git a/aleksis/apps/chronos/frontend/components/amendLesson.graphql b/aleksis/apps/chronos/frontend/components/amendLesson.graphql
index 3f69d827ecb35ecb5cdcd28c340cd05255f85b9d..12b8db72de8e9de8004ecd368e760e257c141e10 100644
--- a/aleksis/apps/chronos/frontend/components/amendLesson.graphql
+++ b/aleksis/apps/chronos/frontend/components/amendLesson.graphql
@@ -83,9 +83,9 @@ mutation patchAmendLessons($input: [BatchPatchLessonEventInput]!) {
   }
 }
 
-mutation patchAmendLessonsWithAmends($input: [BatchPatchLessonEventInput]!) {
-  patchAmendLessonsWithAmends(input: $input) {
-    items: lessonEvents {
+mutation createOrUpdateSubstitutions($input: [SubstitutionInputType]!) {
+  createOrUpdateSubstitutions(input: $input) {
+    items: substitutions {
       id
       subject {
         id
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 4334f3ef4e83cd94c586cde3650d13bd5c2ed44c..051e6ccecd275aa649535256794e6e06d1359899 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -1605,29 +1605,44 @@ class LessonEvent(CalendarEvent):
         )
 
         # 2. For each lessonEvent → check if there are any teachers with absences that overlap the lesson & if yes, check if there is already an amendment for that lesson
-        # if so, add it to a list, if not, create a new one (no dummy creation here possible since teachers is a m2m field)
+        # if so, add it to a list, if not, create a dummy one
 
-        amended_lessons = []
+        substitutions = []
 
         for event in events:
             reference_obj = event["REFERENCE_OBJECT"]
 
+            datetime_start = event["DTSTART"].dt
+            datetime_end = event["DTEND"].dt
+
             affected_teachers = reference_obj.teachers.filter(
-                Q(kolego_absences__datetime_start__lte=event["DTEND"].dt)
-                & Q(kolego_absences__datetime_end__gte=event["DTSTART"].dt)
+                Q(kolego_absences__datetime_start__lte=datetime_end)
+                & Q(kolego_absences__datetime_end__gte=datetime_start)
             )
 
             if affected_teachers.exists():
-                obj, created = LessonEvent.objects.update_or_create(
-                    amends=reference_obj,
+                existing_substitutions = reference_obj.amended_by.filter(
                     datetime_start=event["DTSTART"].dt,
                     datetime_end=event["DTEND"].dt,
                 )
-                if created:
-                    obj.teachers.set(reference_obj.teachers.exclude(pk__in=affected_teachers))
-                amended_lessons.append(obj)
 
-        return amended_lessons
+                if existing_substitutions.exists():
+                    substitution = existing_substitutions.first()
+                    # if incomplete and doc.topic:
+                    #     continue
+                    substitutions.append(substitution)
+
+                else:
+                    substitutions.append(
+                        cls(
+                            pk=f"DUMMY;{reference_obj.id};{datetime_start.isoformat()};{datetime_end.isoformat()}",
+                            amends=reference_obj,
+                            datetime_start=datetime_start,
+                            datetime_end=datetime_end,
+                        )
+                    )
+
+        return substitutions
 
     class Meta:
         verbose_name = _("Lesson Event")
diff --git a/aleksis/apps/chronos/schema/__init__.py b/aleksis/apps/chronos/schema/__init__.py
index fdda397a3ffbdd762dceb46d1cd3d93cb8a22803..6898fb5ae6314f853d4de7b7b2eb4a2dfa141659 100644
--- a/aleksis/apps/chronos/schema/__init__.py
+++ b/aleksis/apps/chronos/schema/__init__.py
@@ -12,6 +12,7 @@ from graphene_django_cud.mutations import (
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
 )
+from reversion import create_revision, set_comment, set_user
 
 from aleksis.core.models import CalendarEvent, Group, Person, Room
 from aleksis.core.schema.base import DeleteMutation, FilterOrderList
@@ -63,6 +64,35 @@ class LessonEventType(DjangoObjectType):
 
     amends = graphene.Field(lambda: LessonEventType, required=False)
 
+    @staticmethod
+    def resolve_teachers(root: LessonEvent, info, **kwargs):
+        if not str(root.pk).startswith("DUMMY") and hasattr(root, "teachers"):
+            return root.teachers
+        elif root.amends:
+            affected_teachers = root.amends.teachers.filter(
+                Q(kolego_absences__datetime_start__lte=root.datetime_end)
+                & Q(kolego_absences__datetime_end__gte=root.datetime_start)
+            )
+            return root.amends.teachers.exclude(pk__in=affected_teachers)
+        return []
+
+    @staticmethod
+    def resolve_groups(root: LessonEvent, info, **kwargs):
+        if not str(root.pk).startswith("DUMMY") and hasattr(root, "groups"):
+            return root.groups
+        elif root.amends:
+            root.amends.groups
+        return []
+
+    @staticmethod
+    def resolve_rooms(root: LessonEvent, info, **kwargs):
+        if not str(root.pk).startswith("DUMMY") and hasattr(root, "rooms"):
+            return root.rooms
+        elif root.amends:
+            root.amends.rooms
+        return []
+
+
 
 class DatetimeTimezoneMixin:
     """Handle datetimes for mutations with CalendarEvent objects.
@@ -126,6 +156,89 @@ class AmendLessonBatchDeleteMutation(DjangoBatchDeleteMutation):
         permissions = ("chronos.delete_substitution_rule",)
 
 
+class SubstitutionInputType(graphene.InputObjectType):
+    id = graphene.ID(required=True)
+    subject = graphene.ID(required=False)
+    teachers = graphene.List(graphene.ID, required=False)
+    rooms = graphene.List(graphene.ID, required=False)
+
+    comment = graphene.String(required=False)
+    cancelled = graphene.Boolean(required=False)
+
+
+class SubstitutionBatchCreateOrUpdateMutation(graphene.Mutation):
+    class Arguments:
+        input = graphene.List(SubstitutionInputType)
+
+    substitutions = graphene.List(LessonEventType)
+
+    @classmethod
+    def create_or_update(cls, info, substitution):
+        _id = substitution.id
+
+        # Sadly, we can't use the update_or_create method since create_defaults
+        # is only introduced in Django 5.0
+        if _id.startswith("DUMMY"):
+            dummy, amended_lesson_event_id, datetime_start_iso, datetime_end_iso = _id.split(";")
+            amended_lesson_event = LessonEvent.objects.get(id=amended_lesson_event_id)
+
+            datetime_start = datetime.fromisoformat(datetime_start_iso).astimezone(
+                amended_lesson_event.timezone
+            )
+            datetime_end = datetime.fromisoformat(datetime_end_iso).astimezone(
+                amended_lesson_event.timezone
+            )
+
+            if info.context.user.has_perm(
+                "chronos.change_lessonsubstitution"
+            ):
+                obj = LessonEvent.objects.create(
+                    datetime_start=datetime_start,
+                    datetime_end=datetime_end,
+                    amends=amended_lesson_event,
+                    subject=substitution.subject,
+                    comment=substitution.comment or "",
+                    cancelled=substitution.cancelled,
+                )
+                if substitution.teachers is not None:
+                    obj.teachers.set(Person.objects.filter(pk__in=substitution.teachers))
+                if substitution.rooms is not None:
+                    obj.rooms.set(Room.objects.filter(pk__in=substitution.rooms))
+                obj.save()
+                return obj
+            raise PermissionDenied()
+        else:
+            obj = LessonEvent.objects.get(id=_id)
+
+            if not info.context.user.has_perm("chronos.edit_substitution_rule", obj):
+                raise PermissionDenied()
+
+            if substitution.subject is not None:
+                obj.subject = Subject.objects.get(pk=substitution.subject)
+            if substitution.teachers is not None:
+                obj.teachers.set(Person.objects.filter(pk__in=substitution.teachers))
+            if substitution.rooms is not None:
+                obj.rooms.set(Room.objects.filter(pk__in=substitution.rooms))
+
+            if substitution.cancelled is not None:
+                obj.cancelled = substitution.cancelled
+            if substitution.comment is not None:
+                obj.comment = substitution.comment
+
+            obj.save()
+            return obj
+
+    @classmethod
+    def mutate(cls, root, info, input):  # noqa
+        with create_revision():
+            set_user(info.context.user)
+            set_comment("Updated in substitution overview")
+            objs = [cls.create_or_update(info, substitution) for substitution in input]
+
+        return SubstitutionBatchCreateOrUpdateMutation(substitutions=objs)
+
+
+
 class TimetableType(graphene.Enum):
     TEACHER = "teacher"
     GROUP = "group"
@@ -226,3 +339,5 @@ class Mutation(graphene.ObjectType):
     patch_amend_lessons = AmendLessonBatchPatchMutation.Field()
     patch_amend_lessons_with_amends = AmendLessonBatchPatchMutation.Field()
     delete_amend_lessons = AmendLessonBatchDeleteMutation.Field()
+
+    create_or_update_substitutions = SubstitutionBatchCreateOrUpdateMutation.Field()