diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f4de6f18e8f84208baf4b1abf1e79b05550c27d2
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue
@@ -0,0 +1,69 @@
+<template>
+  <mobile-fullscreen-dialog v-model="popup">
+    <template #activator="activator">
+      <!-- button +? -->
+      <!-- -> popup = true -->
+    </template>
+    <template #title>
+      <!-- Abwesenheit/Entschuldigung erfassen --> 
+      <!-- Abwesenheit/Entschuldigung Zusammenfassung --> 
+    </template>
+    <template #content>
+    <absence-form v-if="form" />
+    <absence-summary v-else />
+    </template>
+    <template #actions>
+      <!-- secondary -->
+      <!-- TODO: Return to form on cancel? form=true -->
+      <cancel-button
+        @click="popup = false"
+        disabled="loading"
+      />
+      <!-- primary -->
+      <save-button
+        v-if="form"
+        i18n-key="actions.continue"
+        @click="form = false"
+        :loading="loading"
+      />
+      <save-button
+        v-if="form"
+        i18n-key="actions.confirm"
+        @click="confirm"
+        :loading="loading"
+      />
+    </template>
+  </mobile-fullscreen-dialog>
+</template>
+
+<script>
+import MobileFullscreenDialog from "aleksis.core/components/generic/dialogs/MobileFullscreenDialog.vue";
+import AbsenceForm from "./AbsenceForm.vue";
+import AbsenceSummary from "./AbsenceSummary.vue";
+import CancelButton from "aleksis.core/components/generic/buttons/CancelButton.vue";
+import SaveButton from "aleksis.core/components/generic/buttons/SaveButton.vue";
+
+export default {
+  name: "AbsenceDialog",
+  components: {
+    MobileFullscreenDialog,
+    AbsenceForm,
+    AbsenceSummary,
+    CancelButton,
+    SaveButton,
+  },
+  data() {
+    return {
+      popup: false,
+      form: true,
+      loading: false,
+    };
+  },
+  methods: {
+    confirm() {
+      // TODO: Send mutation (shown in absence-summary)
+      popup = false,
+    },
+  },
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..11e226bfb5ed523289a87862fa18d0e7aebbfcf9
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue
@@ -0,0 +1,50 @@
+<template>
+  <v-container>
+    <v-row>
+      <!-- persons -->
+      <!-- v-autocomplete -->
+    </v-row>
+    <v-row>
+      <!-- Start -->
+      <v-col
+        cols="12"
+        :sm="6"
+        >
+        <date-field
+          :value="value"
+          @input="$emit('input', $event)"
+          :label="$t('date_select.label')"
+          :disabled="loading"
+          />
+      </v-col>
+      <!-- End -->
+      <v-col
+        cols="12"
+        :sm="6"
+        >
+        <date-field
+          :value="value"
+          @input="$emit('input', $event)"
+          :label="$t('date_select.label')"
+          :disabled="loading"
+          />
+      </v-col>
+    </v-row>
+    <v-row>
+      <!-- comment -->
+    </v-row>
+    <v-row>
+      <!-- TODO: Component from Julian -->
+    </v-row>
+  </v-container>
+</template>
+
+<script>
+import DateField from "aleksis.core/components/generic/forms/DateField.vue";
+export default {
+  name: "AbsenceForm",
+  components: {
+    DateField,
+  },
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationSummary.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationSummary.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e255df5a069f3135d6dcd5030ab58a9ce2cfefc8
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationSummary.vue
@@ -0,0 +1,25 @@
+<template>
+  <!-- TODO: Hide header -->
+  <c-r-u-d-iterator
+    :gql-query=""
+    :gql-additional-query-args="FROM FORM"
+    :enable-create="false"
+    :enable-edit="false"
+    :elevated="false"
+    >
+    <template #default="{ items }">
+      <!-- expandable card per person -->
+    </template>
+  </c-r-u-d-iterator>
+</template>
+
+<script>
+import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
+
+export default {
+  name: "AbsenceSummary",
+  components: {
+    CRUDIterator,
+  },
+};
+</script>
diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..79178bfd835e5c6dbe726d53d6e00b11c7c8f61f
--- /dev/null
+++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceCreation.graphql
@@ -0,0 +1,59 @@
+# Uses core persons query
+query persons {
+  persons: persons {
+    id
+    firstName
+    lastName
+  }
+}
+
+query lessonsForPersons(
+  $persons: [ID!]!
+  $start: Date!
+  $end: Date!
+) {
+  items: lessonsForPersons(
+    persons: $persons
+    start: $start
+    end: $end
+  ) {
+    id
+    lessons {
+      id
+      datetimeStart
+      datetimeEnd
+      course {
+        id
+        name
+      }
+      subject {
+        id
+        name
+        shortName
+        colourFg
+        colourBg
+      }
+    }
+  }
+}
+
+# Use absencesInputType?
+mutation createAbsences(
+  $persons: [ID!]!
+  $start: Date!
+  $end: Date!
+  $comment: String
+  $reason: ID!
+) {
+  createAbsences(
+    person: $persons
+    start: $start
+    end: $end
+    comment: $comment
+    reason: $reason
+  ) {
+    items: absences {
+      ok
+    }
+  }
+}
diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py
index a73a7d7b925360ce6283cdf4d3161cd78b4de4bb..ad6aa5c4f7a5a93b40fe1e37e53837be344571e1 100644
--- a/aleksis/apps/alsijil/models.py
+++ b/aleksis/apps/alsijil/models.py
@@ -2,6 +2,7 @@ from datetime import date, datetime
 from typing import Optional, Union
 from urllib.parse import urlparse
 
+from django.core.exceptions import PermissionDenied
 from django.db import models
 from django.db.models import QuerySet
 from django.db.models.constraints import CheckConstraint
@@ -9,7 +10,9 @@ from django.db.models.query_utils import Q
 from django.http import HttpRequest
 from django.urls import reverse
 from django.utils.formats import date_format
+from django.utils.timezone import localdate, localtime
 from django.utils.translation import gettext_lazy as _
+from django.contrib.auth.models import User
 
 from calendarweek import CalendarWeek
 from colorfield.fields import ColorField
@@ -515,44 +518,17 @@ class Documentation(CalendarEvent):
         # which is not possible via constraint, because amends is not local to Documentation
 
     @classmethod
-    def get_for_coursebook(
-        cls,
-        own: bool,
-        date_start: datetime,
-        date_end: datetime,
-        request: HttpRequest,
-        obj_type: Optional[str] = None,
-        obj_id: Optional[str] = None,
-        incomplete: Optional[bool] = False,
-    ) -> list:
-        """Get all the documentations for an object and a time frame.
-
-        obj_type may be one of TEACHER, GROUP, ROOM, COURSE
+    def get_documentations_for_events(
+            cls,
+            events: list,
+            incomplete: Optional[bool] = False,
+    ) -> tuple:
+        """Get all the documentations for the events.
+        Create dummy documentations if none exist.
+        Returns a tuple with a list of existing documentations and a list dummy documentations.
         """
-
-        # 1. Find all LessonEvents for all Lessons of this Course in this date range
-        event_params = {
-            "own": own,
-        }
-        if obj_type is not None and obj_id is not None:
-            event_params.update(
-                {
-                    "type": obj_type,
-                    "id": obj_id,
-                }
-            )
-
-        events = LessonEvent.get_single_events(
-            date_start,
-            date_end,
-            request,
-            event_params,
-            with_reference_object=True,
-        )
-
-        # 2. For each lessonEvent → check if there is a documentation
-        # if so, add the documentation to a list, if not, create a new one
         docs = []
+        dummies = []
         for event in events:
             if incomplete and event["STATUS"] == "CANCELLED":
                 continue
@@ -582,7 +558,7 @@ class Documentation(CalendarEvent):
                 else:
                     course, subject = event_reference_obj.course, event_reference_obj.subject
 
-                docs.append(
+                dummies.append(
                     cls(
                         pk=f"DUMMY;{event_reference_obj.id};{event['DTSTART'].dt.isoformat()};{event['DTEND'].dt.isoformat()}",
                         amends=event_reference_obj,
@@ -593,7 +569,116 @@ class Documentation(CalendarEvent):
                     )
                 )
 
-        return docs
+        return (docs, dummies)
+
+    @classmethod
+    def get_documentations_for_person(
+            cls,
+            person: int,
+            start: datetime,
+            end: datetime,
+            incomplete: Optional[bool] = False,
+    ) -> tuple:
+        """Get all the documentations for the person from start to end datetime.
+        Create dummy documentations if none exist.
+        Returns a tuple with a list of existing documentations and a list dummy documentations.
+        """
+        event_params = {
+            "type": "PARTICIPANT",
+            "obj_id": person,
+        }
+
+        events = LessonEvent.get_single_events(
+            start,
+            end,
+            None,
+            event_params,
+            with_reference_object=True,
+        )
+
+        return Documentation.get_documentations_for_events(events, incomplete)
+
+    @classmethod
+    def parse_dummy(
+            cls,
+            _id: str,
+    ) -> tuple:
+        """Parse dummy id string into lesson_event, datetime_start, datetime_end.
+        """
+        dummy, lesson_event_id, datetime_start_iso, datetime_end_iso = _id.split(";")
+        lesson_event = LessonEvent.objects.get(id=lesson_event_id)
+
+        datetime_start = datetime.fromisoformat(datetime_start_iso).astimezone(
+            lesson_event.timezone
+        )
+        datetime_end = datetime.fromisoformat(datetime_end_iso).astimezone(
+            lesson_event.timezone
+        )
+        return (lesson_event, datetime_start, datetime_end)
+
+    @classmethod
+    def create_from_lesson_event(
+            cls,
+            user: User,
+            lesson_event: LessonEvent,
+            datetime_start: datetime,
+            datetime_end: datetime,
+    ) -> "Documentation":
+        """ Create a documentation from a lesson_event with start and end datetime.
+        User is needed for permission checking.
+        """
+        if not user.has_perm(
+                "alsijil.add_documentation_for_lesson_event_rule", lesson_event
+            ) or not (
+                get_site_preferences()["alsijil__allow_edit_future_documentations"] == "all"
+                or (
+                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
+                    == "current_day"
+                    and datetime_start.date() <= localdate()
+                )
+                or (
+                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
+                    == "current_time"
+                    and datetime_start <= localtime()
+                )
+            ):
+            raise PermissionDenied()
+
+        if lesson_event.amends:
+            if lesson_event.course:
+                course = lesson_event.course
+            else:
+                course = lesson_event.amends.course
+
+            if lesson_event.subject:
+                subject = lesson_event.subject
+            else:
+                subject = lesson_event.amends.subject
+
+            if lesson_event.teachers:
+                teachers = lesson_event.teachers
+            else:
+                teachers = lesson_event.amends.teachers
+        else:
+            course, subject, teachers = (
+                lesson_event.course,
+                lesson_event.subject,
+                lesson_event.teachers,
+            )
+
+        obj = cls.objects.create(
+            datetime_start=datetime_start,
+            datetime_end=datetime_end,
+            amends=lesson_event,
+            course=course,
+            subject=subject,
+            topic="",
+            homework="",
+            group_note="",
+        )
+        obj.teachers.set(teachers.all())
+        obj.save()
+        return obj
 
 
 class ParticipationStatus(CalendarEvent):
diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py
index e4e48be10e717b4f105330a7f942861794aca12c..5871a80b09fa0cc461dda9ff5b05604ddb9475fe 100644
--- a/aleksis/apps/alsijil/schema/__init__.py
+++ b/aleksis/apps/alsijil/schema/__init__.py
@@ -11,12 +11,17 @@ from aleksis.core.models import Group, Person
 from aleksis.core.schema.base import FilterOrderList
 from aleksis.core.schema.group import GroupType
 from aleksis.core.util.core_helpers import has_person
+from aleksis.apps.chronos.models import LessonEvent
 
 from ..models import Documentation
 from .documentation import (
     DocumentationBatchCreateOrUpdateMutation,
     DocumentationType,
 )
+from .absences import (
+    LessonsForPersonType,
+    AbsencesBatchCreateMutation,
+)
 from .participation_status import ParticipationStatusBatchPatchMutation
 
 
@@ -38,6 +43,13 @@ class Query(graphene.ObjectType):
     groups_by_person = FilterOrderList(GroupType, person=graphene.ID())
     courses_of_person = FilterOrderList(CourseType, person=graphene.ID())
 
+    lessons_for_persons = graphene.List(
+        LessonsForPersonType,
+        persons=graphene.List(graphene.ID, required=True),
+        start=graphene.Date(required=True),
+        end=graphene.Date(required=True),
+    )
+
     def resolve_documentations_by_course_id(root, info, course_id, **kwargs):
         documentations = Documentation.objects.filter(
             Q(course__pk=course_id) | Q(amends__course__pk=course_id)
@@ -55,9 +67,6 @@ class Query(graphene.ObjectType):
         incomplete=False,
         **kwargs,
     ):
-        datetime_start = datetime.combine(date_start, datetime.min.time())
-        datetime_end = datetime.combine(date_end, datetime.max.time())
-
         if (
             (
                 obj_type == "COURSE"
@@ -80,10 +89,30 @@ class Query(graphene.ObjectType):
         ):
             raise PermissionDenied()
 
-        return Documentation.get_for_coursebook(
-            own, datetime_start, datetime_end, info.context, obj_type, obj_id, incomplete
+        # Find all LessonEvents for all Lessons of this Course in this date range
+        event_params = {
+            "own": own,
+        }
+        if obj_type is not None and obj_id is not None:
+            event_params.update(
+                {
+                    "type": obj_type,
+                    "id": obj_id,
+                }
+            )
+
+        events = LessonEvent.get_single_events(
+            datetime.combine(date_start, datetime.min.time()),
+            datetime.combine(date_end, datetime.max.time()),
+            info.context,
+            event_params,
+            with_reference_object=True,
         )
 
+        # Lookup or create documentations and return them all.
+        docs, dummies = Documentation.get_documentations_for_events(events, incomplete)
+        return docs + dummies
+
     @staticmethod
     def resolve_groups_by_person(root, info, person=None):
         if person:
@@ -117,7 +146,34 @@ class Query(graphene.ObjectType):
             | Q(groups__parent_groups__owners=person)
         )
 
+    @staticmethod
+    def resolve_lessons_for_persons(
+        root,
+        info,
+        persons,
+        start,
+        end,
+        **kwargs,
+    ):
+        """Resolve all lesson events for each person in timeframe start to end.
+        """
+        lessons_for_person = []
+        for person in persons:
+            docs, dummies = Documentation.get_documentations_for_person(
+                person,
+                datetime.combine(start, datetime.min.time()),
+                datetime.combine(end, datetime.max.time()),
+            )
+
+            lessons_for_person.append(
+                id=person,
+                lessons=docs + dummies
+            )
+
+        return lessons_for_person
+
 
 class Mutation(graphene.ObjectType):
     create_or_update_documentations = DocumentationBatchCreateOrUpdateMutation.Field()
+    create_absences = AbsencesBatchCreateMutation.Field()
     update_participation_statuses = ParticipationStatusBatchPatchMutation.Field()
diff --git a/aleksis/apps/alsijil/schema/absences.py b/aleksis/apps/alsijil/schema/absences.py
new file mode 100644
index 0000000000000000000000000000000000000000..663455671de7b8f2c420bb4ab8d332ffda3c61d2
--- /dev/null
+++ b/aleksis/apps/alsijil/schema/absences.py
@@ -0,0 +1,81 @@
+import graphene
+from datetime import datetime
+
+from aleksis.apps.kolego.models import Absence
+
+from .documentation import DocumentationType
+from ..models import Documentation, ParticipationStatus
+
+class LessonsForPersonType(graphene.ObjectType):
+    id = graphene.ID()  # noqa
+    lessons = graphene.List(DocumentationType)
+
+class AbsencesBatchCreateMutation(graphene.Mutation):
+    class Arguments:
+        persons = graphene.List(graphene.ID)
+        start = graphene.Date()
+        end = graphene.Date()
+        comment = graphene.String()
+        reason = graphene.ID()
+
+    ok = graphene.Boolean()
+
+    @classmethod
+    def mutate(cls, root, info, persons, start, end, comment, reason):  # noqa
+        # TODO: Check permissions for ParticipationStatus & KolegoAbsence
+        #       See MR 356
+
+        # DocumentationBatchCreateOrUpdateMutation.create_or_update
+        # at least already checks permissions.
+
+        for person in persons:
+            # Get all documentations for this person between start & end
+            docs, dummies = Documentation.get_documentations_for_person(
+                person,
+                datetime.combine(start, datetime.min.time()),
+                datetime.combine(end, datetime.max.time()),
+            )
+
+            # Create doc for dummies that are already in the past
+            future = False
+            for dummy in dummies:
+                lesson_event, dummy_start, dummy_end = Documentation.parse_dummy(dummy.id)
+
+                if dummy_start < datetime.now():
+                    # In the past -> Create a Documentation
+                    docs.append(
+                        Documentation.create_from_lesson_event(
+                            info.context.user,
+                            lesson_event,
+                            dummy_start,
+                            dummy_end,
+                        )
+                    )
+                else:
+                    future = True
+
+            # Create a ParticipationStatus for each documentation
+            for doc in docs:
+                # Set person & absence_reason directly from id
+                ParticipationStatus.objects.create(
+                    person_id=person,
+                    related_documentation=doc,
+                    absence_reason_id=reason,
+                )
+
+            # If there are still dummy documentations in the future
+            # create a Kolego Absence
+            if future:
+                # TODO: Are date_start & date_end from CalendarEvent enough
+                #       or more needed?
+                # Set reason & person directly from id
+                Absence.objects.create(
+                    date_start=datetime.now().date(),
+                    date_end=end,
+                    reason_id=reason,
+                    person_id=person,
+                    comment=comment,
+                )
+
+        # Return ok=True if everything went well.
+        return AbsencesBatchCreateMutation(ok=True)
diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py
index e9ece98b8619a134441884d0a332e533e90a872c..24029c245376a0d6a7fa8d35a6d4d5d8adebb17b 100644
--- a/aleksis/apps/alsijil/schema/documentation.py
+++ b/aleksis/apps/alsijil/schema/documentation.py
@@ -9,7 +9,6 @@ from guardian.shortcuts import get_objects_for_user
 from reversion import create_revision, set_comment, set_user
 
 from aleksis.apps.alsijil.util.predicates import can_edit_documentation, is_in_allowed_time_range
-from aleksis.apps.chronos.models import LessonEvent
 from aleksis.apps.chronos.schema import LessonEventType
 from aleksis.apps.cursus.models import Subject
 from aleksis.apps.cursus.schema import CourseType, SubjectType
@@ -18,7 +17,6 @@ from aleksis.core.schema.base import (
     DjangoFilterMixin,
     PermissionsTypeMixin,
 )
-from aleksis.core.util.core_helpers import get_site_preferences
 
 from ..models import Documentation
 from .participation_status import ParticipationStatusType
@@ -112,90 +110,30 @@ class DocumentationBatchCreateOrUpdateMutation(graphene.Mutation):
         # 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, lesson_event_id, datetime_start_iso, datetime_end_iso = _id.split(";")
-            lesson_event = LessonEvent.objects.get(id=lesson_event_id)
-
-            datetime_start = datetime.fromisoformat(datetime_start_iso).astimezone(
-                lesson_event.timezone
-            )
-            datetime_end = datetime.fromisoformat(datetime_end_iso).astimezone(
-                lesson_event.timezone
+            obj = Documentation.create_from_lesson_event(
+                info.context.user,
+                *Documentation.parse_dummy(_id),
             )
-
-            if info.context.user.has_perm(
-                "alsijil.add_documentation_for_lesson_event_rule", lesson_event
-            ) and (
-                get_site_preferences()["alsijil__allow_edit_future_documentations"] == "all"
-                or (
-                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
-                    == "current_day"
-                    and datetime_start.date() <= localdate()
-                )
-                or (
-                    get_site_preferences()["alsijil__allow_edit_future_documentations"]
-                    == "current_time"
-                    and datetime_start <= localtime()
-                )
-            ):
-                if lesson_event.amends:
-                    if lesson_event.course:
-                        course = lesson_event.course
-                    else:
-                        course = lesson_event.amends.course
-
-                    if lesson_event.subject:
-                        subject = lesson_event.subject
-                    else:
-                        subject = lesson_event.amends.subject
-
-                    if lesson_event.teachers:
-                        teachers = lesson_event.teachers
-                    else:
-                        teachers = lesson_event.amends.teachers
-                else:
-                    course, subject, teachers = (
-                        lesson_event.course,
-                        lesson_event.subject,
-                        lesson_event.teachers,
-                    )
-
-                obj = Documentation.objects.create(
-                    datetime_start=datetime_start,
-                    datetime_end=datetime_end,
-                    amends=lesson_event,
-                    course=course,
-                    subject=subject,
-                    topic=doc.topic or "",
-                    homework=doc.homework or "",
-                    group_note=doc.group_note or "",
-                )
-                if doc.teachers is not None:
-                    obj.teachers.add(*doc.teachers)
-                else:
-                    obj.teachers.set(teachers.all())
-                obj.save()
-                return obj
-            raise PermissionDenied()
         else:
             obj = Documentation.objects.get(id=_id)
 
-            if not info.context.user.has_perm("alsijil.edit_documentation_rule", obj):
-                raise PermissionDenied()
+        if not info.context.user.has_perm("alsijil.edit_documentation_rule", obj):
+            raise PermissionDenied()
 
-            if doc.topic is not None:
-                obj.topic = doc.topic
-            if doc.homework is not None:
-                obj.homework = doc.homework
-            if doc.group_note is not None:
-                obj.group_note = doc.group_note
+        if doc.topic is not None:
+            obj.topic = doc.topic
+        if doc.homework is not None:
+            obj.homework = doc.homework
+        if doc.group_note is not None:
+            obj.group_note = doc.group_note
 
-            if doc.subject is not None:
-                obj.subject = Subject.objects.get(pk=doc.subject)
-            if doc.teachers is not None:
-                obj.teachers.set(Person.objects.filter(pk__in=doc.teachers))
+        if doc.subject is not None:
+            obj.subject = Subject.objects.get(pk=doc.subject)
+        if doc.teachers is not None:
+            obj.teachers.set(Person.objects.filter(pk__in=doc.teachers))
 
-            obj.save()
-            return obj
+        obj.save()
+        return obj
 
     @classmethod
     def mutate(cls, root, info, input):  # noqa