diff --git a/.dev-js/package.json b/.dev-js/package.json
index f1a3b8b143115ffb8f0fee2d452ec2830cd888a3..99b0989e1dde9b0f71fdc229bac3aef501c06398 100644
--- a/.dev-js/package.json
+++ b/.dev-js/package.json
@@ -6,7 +6,7 @@
     "eslint": "^8.26.0",
     "eslint-config-prettier": "^9.0.0",
     "eslint-plugin-vue": "^9.7.0",
-    "prettier": "^3.0.0",
+    "prettier": "^3.4.0",
     "stylelint": "^15.0.0",
     "stylelint-config-prettier": "^9.0.3",
     "stylelint-config-standard": "^34.0.0"
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 902a10933a40914e7119fec6733cbf139cd7f1d2..d26538a584e9bfb9e1d33f131adda94f7b2be7ea 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
+`4.0.0.dev9`_ - 2024-12-08
+--------------------------
+
+Added
+~~~~~
+
+* Substitution planning interface based on teacher absences.
+
 `4.0.0.dev8`_ - 2024-10-17
 --------------------------
 
@@ -432,3 +440,4 @@ Fixed
 .. _3.0.1: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0.1
 .. _3.0.2: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/3.0.2
 .. _4.0.0.dev8: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/4.0.0.dev8
+.. _4.0.0.dev9: https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos/-/tags/4.0.0.dev9
diff --git a/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue b/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue
index a6a737298d496c3a8b7acdcd7fe4d485c77cace2..2a53a497772191544df5cd65a8c2f14cde897c1a 100644
--- a/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue
+++ b/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue
@@ -11,6 +11,18 @@ export default {
       required: false,
       default: "name",
     },
+    alternativeAttr: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+  methods: {
+    getAttr(item) {
+      let val = item[this.attr];
+      if (val) return val;
+      return this.alternativeAttr ? item[this.alternativeAttr] : val;
+    },
   },
 };
 </script>
@@ -19,7 +31,7 @@ export default {
   <span v-bind="$attrs">
     <span v-for="(item, idx) in items" :key="idx">
       <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
-      {{ item[attr] }}{{ idx + 1 < items.length ? "," : "" }}
+      {{ getAttr(item) }}{{ idx + 1 < items.length ? "," : "" }}
     </span>
   </span>
 </template>
diff --git a/aleksis/apps/chronos/frontend/components/LessonInformation.vue b/aleksis/apps/chronos/frontend/components/LessonInformation.vue
new file mode 100644
index 0000000000000000000000000000000000000000..072745c4c5916a4a71881c77efcf4832a1af7208
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/LessonInformation.vue
@@ -0,0 +1,64 @@
+<script setup>
+import CancelledCalendarStatusChip from "aleksis.core/components/calendar/CancelledCalendarStatusChip.vue";
+import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
+
+import { DateTime } from "luxon";
+</script>
+
+<template>
+  <v-card-text>
+    <cancelled-calendar-status-chip v-if="lesson.cancelled" class="mr-2" />
+    <div
+      :class="{
+        'text-decoration-line-through': lesson.cancelled,
+        'text--secondary': lesson.cancelled,
+      }"
+    >
+      {{ $d(toDateTime(lesson.datetimeStart), "shortTime") }} –
+      {{ $d(toDateTime(lesson.datetimeEnd), "shortTime") }}
+      {{ getCourse(lesson)?.name }}
+    </div>
+    <subject-chip v-if="getSubject(lesson)" :subject="getSubject(lesson)" />
+  </v-card-text>
+</template>
+
+<script>
+export default {
+  name: "LessonInformation",
+  props: {
+    lesson: {
+      type: Object,
+      required: true,
+    },
+    cancelled: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  methods: {
+    toDateTime(dateString) {
+      return DateTime.fromISO(dateString);
+    },
+    getSubject(lesson) {
+      if (lesson.subject) {
+        return lesson.subject;
+      } else if (lesson.course?.subject) {
+        return lesson.course.subject;
+      } else if (lesson.amends?.subject) {
+        return lesson.amends.subject;
+      }
+      return undefined;
+    },
+
+    getCourse(lesson) {
+      if (lesson.course) {
+        return lesson.course;
+      } else if (lesson.amends?.course) {
+        return lesson.amends.course;
+      }
+      return undefined;
+    },
+  },
+};
+</script>
diff --git a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
index f11b2ed2ebc60b72895f3e682c4b66cc024fddad..e750d01e98cb1969233e176f42492f4d7900939a 100644
--- a/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
+++ b/aleksis/apps/chronos/frontend/components/NoTimetableCard.vue
@@ -25,7 +25,13 @@ export default {
     v-bind="$attrs"
   >
     <div class="text-center">
-      <mascot type="timetable" size="60" class="mb-4" />
+      <mascot
+        type="timetable"
+        class="mb-4"
+        max-width="min(80vw, 600px)"
+        max-height="min(50vh, 500px)"
+        contain
+      />
       <div class="text-h5 grey--text text--darken-2 mb-2">
         {{ $t(titleKey) }}
       </div>
diff --git a/aleksis/apps/chronos/frontend/components/SelectTimetable.vue b/aleksis/apps/chronos/frontend/components/SelectTimetable.vue
index 76543ac322a63cff1af8e948bbab5f81dd088aa1..5d4166431e14db4fcc412d7ddc7ed4fe69ce6008 100644
--- a/aleksis/apps/chronos/frontend/components/SelectTimetable.vue
+++ b/aleksis/apps/chronos/frontend/components/SelectTimetable.vue
@@ -1,8 +1,12 @@
 <script>
 import timetableTypes from "./timetableTypes";
+import Mascot from "aleksis.core/components/generic/mascot/Mascot.vue";
 
 export default {
   name: "SelectTimetable",
+  components: {
+    Mascot,
+  },
   props: {
     value: {
       type: Object,
@@ -13,13 +17,23 @@ export default {
       type: Array,
       required: true,
     },
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    limitHeight: {
+      default: false,
+      required: false,
+      type: Boolean,
+    },
   },
   data() {
     return {
       selected: null,
       selectedFull: null,
       search: "",
-      selectedTypes: ["GROUP", "TEACHER", "ROOM"],
+      selectedType: "GROUP",
       types: timetableTypes,
     };
   },
@@ -36,7 +50,7 @@ export default {
     availableTimetablesFiltered() {
       // Filter timetables by selected types
       return this.availableTimetables.filter((timetable) => {
-        return this.selectedTypes.indexOf(timetable.type) !== -1;
+        return this.selectedType == timetable.type;
       });
     },
   },
@@ -49,6 +63,7 @@ export default {
       <!-- Search field for timetables -->
       <v-text-field
         search
+        dense
         filled
         rounded
         clearable
@@ -57,11 +72,11 @@ export default {
         :placeholder="$t('chronos.timetable.search')"
         prepend-inner-icon="mdi-magnify"
         hide-details="auto"
-        class="mb-2"
+        class="mb-3"
       />
 
       <!-- Filter by timetable types -->
-      <v-btn-toggle v-model="selectedTypes" dense block multiple class="d-flex">
+      <v-btn-toggle v-model="selectedType" dense block class="d-flex" mandatory>
         <v-btn
           v-for="type in types"
           :key="type.id"
@@ -80,6 +95,10 @@ export default {
       :search="search"
       single-expand
       disable-pagination
+      hide-default-footer
+      :loading="loading"
+      :style="{ height: limitHeight ? '600px' : null }"
+      :class="{ 'overflow-auto': limitHeight }"
     >
       <template #default="{ items, isExpanded, expand }">
         <v-list class="scrollable-list">
@@ -107,9 +126,23 @@ export default {
         </v-list>
       </template>
       <template #loading>
-        <v-skeleton-loader
-          type="list-item-avatar,list-item-avatar,list-item-avatar"
-        />
+        <v-skeleton-loader type="list-item-avatar@10" />
+      </template>
+      <template #no-results>
+        <div class="d-flex flex-column align-center justify-center">
+          <mascot type="searching" width="33%" min-width="250px" />
+          <div class="mb-2">
+            {{ $t("$vuetify.dataIterator.noResultsText") }}
+          </div>
+        </div>
+      </template>
+      <template #no-data>
+        <div class="d-flex flex-column align-center justify-center">
+          <mascot type="ready_for_items" width="33%" min-width="250px" />
+          <div class="mb-2">
+            {{ $t("chronos.timetable.no_timetables_in_term") }}
+          </div>
+        </div>
       </template>
     </v-data-iterator>
   </div>
diff --git a/aleksis/apps/chronos/frontend/components/Substitutions.vue b/aleksis/apps/chronos/frontend/components/Substitutions.vue
index fd8558fbabc8af1291c05452dc2dc502a184ff6f..0dafca35aed4a2f49770a54ffdca15ffe83fcfa1 100644
--- a/aleksis/apps/chronos/frontend/components/Substitutions.vue
+++ b/aleksis/apps/chronos/frontend/components/Substitutions.vue
@@ -17,17 +17,23 @@ import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.v
     :show-select="false"
     :enable-create="false"
     :enable-edit="false"
+    :dense="true"
+    :items-per-page="-1"
+    :mobile-breakpoint="0"
   >
     <template #title>
-      <v-row class="d-flex align-center pt-2 pa-2">
-        <v-card-title class="text-h4">
-          {{ $d(new Date(date), "dateWithWeekday") }}
-        </v-card-title>
+      <v-card-title class="full-width flex-nowrap pb-0">
+        {{
+          $d(
+            new Date(date),
+            $vuetify.breakpoint.xs ? "shortWithWeekday" : "dateWithWeekday",
+          )
+        }}
         <v-spacer />
         <primary-action-button
-          class="mr-4"
           i18n-key="chronos.substitutions.print"
           icon-text="$print"
+          :icon="$vuetify.breakpoint.xs"
           :to="{
             name: 'chronos.printSubstitutionsForDate',
             params: {
@@ -35,50 +41,88 @@ import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.v
             },
           }"
         />
-      </v-row>
-      <v-card-text>
-        <div v-if="affectedTeachers.length > 0">
-          <strong>
-            {{ $t("chronos.substitutions.affected_teachers") }}:
-          </strong>
-          <person-chip
-            v-for="teacher in affectedTeachers"
-            :key="teacher.id"
-            class="ma-1"
-            :person="teacher"
-            :to="{
-              name: 'chronos.timetableWithId',
-              params: {
-                type: 'person',
-                id: teacher.id,
-              },
-            }"
-          />
-        </div>
-        <div v-if="affectedGroups.length > 0">
-          <strong> {{ $t("chronos.substitutions.affected_groups") }}: </strong>
-          <group-chip
-            v-for="group in affectedGroups"
-            class="ma-1"
-            :key="group.id"
-            :group="group"
-            format="short"
-          />
-        </div>
+      </v-card-title>
+
+      <v-card-text
+        v-if="affectedTeachers.length > 0 || affectedGroups.length > 0"
+        class="pb-0"
+      >
+        <v-expansion-panels accordion multiple flat>
+          <v-expansion-panel v-if="affectedTeachers.length > 0">
+            <v-expansion-panel-header class="px-0">
+              <strong>{{
+                $t("chronos.substitutions.affected_teachers")
+              }}</strong>
+            </v-expansion-panel-header>
+            <v-expansion-panel-content>
+              <person-chip
+                v-for="teacher in affectedTeachers"
+                :key="teacher.id"
+                class="ma-1"
+                :person="teacher"
+                small
+                :to="{
+                  name: 'chronos.timetableWithId',
+                  params: {
+                    type: 'person',
+                    id: teacher.id,
+                  },
+                }"
+              />
+            </v-expansion-panel-content>
+          </v-expansion-panel>
+
+          <v-expansion-panel v-if="affectedGroups.length > 0">
+            <v-expansion-panel-header class="px-0">
+              <strong>
+                {{ $t("chronos.substitutions.affected_groups") }}</strong
+              >
+            </v-expansion-panel-header>
+            <!-- TODO: Link to group-timetable as well -->
+            <!-- as soon as it becomes possible to resolve a -->
+            <!-- group-timetable from the lesson-event group too. -->
+            <v-expansion-panel-content class="px-0">
+              <group-chip
+                v-for="group in affectedGroups"
+                class="ma-1"
+                :key="group.id"
+                :group="group"
+                format="short"
+                small
+              />
+            </v-expansion-panel-content>
+          </v-expansion-panel>
+        </v-expansion-panels>
       </v-card-text>
     </template>
     <!-- TODO: Extract strike -> bold || normal pattern into own -->
     <!-- component and reuse? -->
     <template #groups="{ item: { oldGroups, newGroups } }">
       <span v-if="newGroups.length > 0">
-        <span class="strike-through" v-for="g in oldGroups" :key="g.id">{{
-          g.shortName
-        }}</span>
+        <span class="strike-through">
+          <lesson-event-link-iterator
+            :items="oldGroups"
+            attr="shortName"
+            alternative-attr="name"
+          />
+        </span>
         <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
         <span>&nbsp;→&nbsp;</span>
-        <strong v-for="g in newGroups" :key="g.id">{{ g.shortName }}</strong>
+        <strong>
+          <lesson-event-link-iterator
+            :items="newGroups"
+            attr="shortName"
+            alternative-attr="name"
+          />
+        </strong>
+      </span>
+      <span v-else>
+        <lesson-event-link-iterator
+          :items="oldGroups"
+          attr="shortName"
+          alternative-attr="name"
+        />
       </span>
-      <span v-else v-for="g in oldGroups" :key="g.id">{{ g.shortName }}</span>
     </template>
     <template #time="{ item: { startSlot, endSlot, startTime, endTime } }">
       <span v-if="startSlot && endSlot && startSlot === endSlot">
@@ -96,17 +140,29 @@ import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.v
     </template>
     <template #teachers="{ item: { oldTeachers, newTeachers } }">
       <span v-if="newTeachers.length > 0">
-        <span class="strike-through" v-for="t in oldTeachers" :key="t.id">
-          {{ t.shortName || t.fullName }}
+        <span class="strike-through">
+          <lesson-event-link-iterator
+            :items="oldTeachers"
+            attr="shortName"
+            alternative-attr="fullName"
+          />
         </span>
         <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
         <span>&nbsp;→&nbsp;</span>
-        <strong v-for="t in newTeachers" :key="t.id">
-          {{ t.shortName || t.fullName }}
+        <strong>
+          <lesson-event-link-iterator
+            :items="newTeachers"
+            attr="shortName"
+            alternative-attr="fullName"
+          />
         </strong>
       </span>
-      <span v-else v-for="t in oldTeachers" :key="t.id">
-        {{ t.shortName || t.fullName }}
+      <span v-else>
+        <lesson-event-link-iterator
+          :items="oldTeachers"
+          attr="shortName"
+          alternative-attr="fullName"
+        />
       </span>
     </template>
     <template #subject="{ item: { oldSubject, newSubject } }">
@@ -123,21 +179,33 @@ import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.v
     </template>
     <template #rooms="{ item: { oldRooms, newRooms } }">
       <span v-if="newRooms.length > 0">
-        <span class="strike-through" v-for="r in oldRooms" :key="r.id">{{
-          r.shortName || r.name
-        }}</span>
+        <span class="strike-through">
+          <lesson-event-link-iterator
+            :items="oldRooms"
+            attr="shortName"
+            alternative-attr="name"
+          />
+        </span>
         <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
         <span>&nbsp;→&nbsp;</span>
-        <strong v-for="r in newRooms" :key="r.id">{{
-          r.shortName || r.name
-        }}</strong>
+        <strong>
+          <lesson-event-link-iterator
+            :items="newRooms"
+            attr="shortName"
+            alternative-attr="name"
+          />
+        </strong>
+      </span>
+      <span v-else>
+        <lesson-event-link-iterator
+          :items="oldRooms"
+          attr="shortName"
+          alternative-attr="name"
+        />
       </span>
-      <span v-else v-for="r in oldRooms" :key="r.id">{{
-        r.shortName || r.name
-      }}</span>
     </template>
     <template #notes="{ item: { cancelled, notes } }">
-      <v-chip v-if="cancelled" color="green" text-color="white">
+      <v-chip v-if="cancelled" color="green" text-color="white" small>
         {{ $t("chronos.substitutions.cancelled") }}
       </v-chip>
       {{ notes }}
@@ -161,8 +229,11 @@ import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.v
 import { substitutionsForDate } from "./substitutions.graphql";
 import { DateTime } from "luxon";
 
+import LessonEventLinkIterator from "./LessonEventLinkIterator.vue";
+
 export default {
   name: "Substitutions",
+  components: { LessonEventLinkIterator },
   props: {
     date: {
       type: String,
@@ -230,4 +301,8 @@ export default {
 .strike-through {
   text-decoration: line-through;
 }
+.v-expansion-panel-content__wrap {
+  padding: 0;
+  padding-bottom: 4px;
+}
 </style>
diff --git a/aleksis/apps/chronos/frontend/components/Timetable.vue b/aleksis/apps/chronos/frontend/components/Timetable.vue
index 78947059bd524bcd829077d790dec3581bfe42a2..8e4edd36f62c851841cc54e6cbe8897800febe2d 100644
--- a/aleksis/apps/chronos/frontend/components/Timetable.vue
+++ b/aleksis/apps/chronos/frontend/components/Timetable.vue
@@ -4,6 +4,7 @@ import TimetableWrapper from "./TimetableWrapper.vue";
 
 <script>
 import { DateTime } from "luxon";
+import { gqlTimetableDays } from "./timetables.graphql";
 
 export default {
   name: "Timetable",
@@ -12,6 +13,7 @@ export default {
       calendarFocus: "",
       calendarType: "week",
       initialRouteFocusSet: false,
+      timetableDays: [1, 2, 3, 4, 5],
     };
   },
   methods: {
@@ -79,6 +81,11 @@ export default {
       });
     },
   },
+  apollo: {
+    timetableDays: {
+      query: gqlTimetableDays,
+    },
+  },
 };
 </script>
 
@@ -93,6 +100,8 @@ export default {
         ]"
         :params="{ type: selected.type, id: selected.objId }"
         ref="calendarWithControls"
+        :calendar-days-of-week="timetableDays"
+        scroll-target="first"
         @changeCalendarFocus="setCalendarFocus"
         @changeCalendarType="setCalendarType"
         @calendarReady="setInnerFocusAndType"
diff --git a/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue b/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue
index d3228681ac045848c0d04c98b96f01e7e7bda508..7c20ad9f35f640761fe780936fd64c817201436a 100644
--- a/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue
+++ b/aleksis/apps/chronos/frontend/components/TimetableWrapper.vue
@@ -134,6 +134,7 @@ export default {
           <select-timetable
             v-model="selected"
             @input="selectDialog = false"
+            :loading="$apollo.queries.availableTimetables.loading"
             :available-timetables="availableTimetables"
           />
         </v-card>
@@ -148,7 +149,9 @@ export default {
         <v-card>
           <select-timetable
             v-model="selected"
+            :loading="$apollo.queries.availableTimetables.loading"
             :available-timetables="availableTimetables"
+            limit-height
           />
         </v-card>
       </v-col>
@@ -161,7 +164,7 @@ export default {
 
         <!-- Calendar card-->
         <v-card v-else>
-          <div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown">
+          <div class="d-flex flex-column" v-if="$vuetify.breakpoint.mdAndDown">
             <v-card-title class="pt-2">
               <v-btn
                 icon
diff --git a/aleksis/apps/chronos/frontend/components/amendLesson.graphql b/aleksis/apps/chronos/frontend/components/amendLesson.graphql
index 5142b0b84c694333edf8ffce8b905fc4e622b1f3..02e0eea908dbb99244bd871a884088b1c0a5fb05 100644
--- a/aleksis/apps/chronos/frontend/components/amendLesson.graphql
+++ b/aleksis/apps/chronos/frontend/components/amendLesson.graphql
@@ -2,6 +2,8 @@ query gqlSubjects {
   amendableSubjects: subjects {
     id
     name
+    colourFg
+    colourBg
   }
 }
 
@@ -12,10 +14,33 @@ query gqlPersons {
   }
 }
 
+query gqlTeachers {
+  amendableTeachers: teachers {
+    id
+    fullName
+    shortName
+    subjectsAsTeacher {
+      id
+      name
+      shortName
+      colourFg
+      colourBg
+    }
+  }
+}
+
 query gqlRooms {
   amendableRooms: rooms {
     id
     name
+    shortName
+  }
+}
+
+query gqlGroups {
+  groups: timetableGroups {
+    id
+    name
   }
 }
 
@@ -68,8 +93,193 @@ mutation patchAmendLessons($input: [BatchPatchLessonEventInput]!) {
   }
 }
 
+mutation createOrUpdateSubstitutions($input: [SubstitutionInputType]!) {
+  createOrUpdateSubstitutions(input: $input) {
+    items: substitutions {
+      id
+      oldId
+      subject {
+        id
+        shortName
+        name
+        colourFg
+        colourBg
+        teachers {
+          id
+          shortName
+          fullName
+        }
+      }
+      teachers {
+        id
+        shortName
+        fullName
+      }
+      groups {
+        id
+      }
+      rooms {
+        id
+        shortName
+        name
+      }
+      course {
+        id
+        subject {
+          id
+          shortName
+          name
+          colourFg
+          colourBg
+          teachers {
+            id
+            shortName
+            fullName
+          }
+        }
+      }
+      amends {
+        id
+        teachers {
+          id
+          shortName
+          fullName
+        }
+        subject {
+          id
+          shortName
+          name
+          colourFg
+          colourBg
+          teachers {
+            id
+            shortName
+            fullName
+          }
+        }
+        groups {
+          id
+          shortName
+        }
+        rooms {
+          id
+          shortName
+          name
+        }
+        course {
+          id
+          name
+        }
+      }
+      datetimeStart
+      datetimeEnd
+      cancelled
+      comment
+    }
+  }
+}
+
 mutation deleteAmendLessons($ids: [ID]!) {
   deleteAmendLessons(ids: $ids) {
     deletionCount
   }
 }
+
+query amendedLessonsFromAbsences(
+  $objId: ID
+  $dateStart: Date!
+  $dateEnd: Date!
+  $incomplete: Boolean
+  $teacher: ID
+) {
+  items: amendedLessonsFromAbsences(
+    objId: $objId
+    dateStart: $dateStart
+    dateEnd: $dateEnd
+    incomplete: $incomplete
+    teacher: $teacher
+  ) {
+    id
+    oldId
+    subject {
+      id
+      shortName
+      name
+      colourFg
+      colourBg
+      teachers {
+        id
+        shortName
+        fullName
+      }
+    }
+    teachers {
+      id
+      shortName
+      fullName
+    }
+    groups {
+      id
+    }
+    course {
+      id
+      subject {
+        id
+        shortName
+        name
+        colourFg
+        colourBg
+        teachers {
+          id
+          shortName
+          fullName
+        }
+      }
+      name
+    }
+    rooms {
+      id
+      shortName
+      name
+    }
+    amends {
+      id
+      teachers {
+        id
+        shortName
+        fullName
+      }
+      subject {
+        id
+        shortName
+        name
+        colourFg
+        colourBg
+        teachers {
+          id
+          shortName
+          fullName
+        }
+      }
+      groups {
+        id
+        shortName
+      }
+      course {
+        id
+        name
+      }
+      rooms {
+        id
+        shortName
+        name
+      }
+      cancelled
+      comment
+    }
+    datetimeStart
+    datetimeEnd
+    cancelled
+    comment
+  }
+}
diff --git a/aleksis/apps/chronos/frontend/components/substitutions.graphql b/aleksis/apps/chronos/frontend/components/substitutions.graphql
index f747c6b56552193374115245f552b14bd6584df8..eb4da41ff0cccef96a6481a75c5179446b6dadf0 100644
--- a/aleksis/apps/chronos/frontend/components/substitutions.graphql
+++ b/aleksis/apps/chronos/frontend/components/substitutions.graphql
@@ -2,6 +2,7 @@ query substitutionsForDate($date: Date!) {
   items: substitutionsForDate(date: $date) {
     affectedTeachers {
       id
+      avatarContentUrl
       shortName
       fullName
     }
diff --git a/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionCard.vue b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionCard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..45bce13d7dd3d04616ef643fd6a62f35573a55cd
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionCard.vue
@@ -0,0 +1,430 @@
+<script setup>
+import SubstitutionInformation from "./SubstitutionInformation.vue";
+import TeacherField from "aleksis.apps.cursus/components/TeacherField.vue";
+import SubjectChipSelectField from "aleksis.apps.cursus/components/SubjectChipSelectField.vue";
+
+import createOrPatchMixin from "aleksis.core/mixins/createOrPatchMixin.js";
+import deleteMixin from "aleksis.core/mixins/deleteMixin.js";
+</script>
+
+<template>
+  <v-card class="my-1 full-width" :loading="loading">
+    <v-card-text
+      class="full-width main-body pa-2"
+      :class="{
+        vertical: $vuetify.breakpoint.mobile,
+      }"
+    >
+      <substitution-information :substitution="substitution" />
+
+      <subject-chip-select-field
+        :value="subject"
+        :disabled="loading"
+        :items="amendableSubjects"
+        @input="subjectInput"
+      />
+
+      <v-autocomplete
+        :value="substitutionRoomIDs"
+        multiple
+        chips
+        deletable-chips
+        dense
+        hide-details
+        outlined
+        :items="amendableRooms"
+        item-text="name"
+        item-value="id"
+        :disabled="loading"
+        :label="$t('chronos.substitutions.overview.rooms.label')"
+        @input="roomsInput"
+      >
+        <template #prepend-inner>
+          <template
+            v-if="roomsWithStatus.filter((t) => t.status === 'regular').length"
+          >
+            <v-chip
+              v-for="room in roomsWithStatus.filter(
+                (t) => t.status === 'regular',
+              )"
+              :key="room.id"
+              class="mb-1"
+              small
+            >
+              {{ room.shortName }}
+            </v-chip>
+          </template>
+          <template
+            v-if="roomsWithStatus.filter((t) => t.status === 'removed').length"
+          >
+            <v-chip
+              v-for="room in roomsWithStatus.filter(
+                (t) => t.status === 'removed',
+              )"
+              :key="room.id"
+              outlined
+              color="error"
+              class="mb-1"
+              small
+            >
+              <v-icon left small>mdi-cancel</v-icon>
+              <div class="text-decoration-line-through">
+                {{ room.shortName ? room.shortName : room.name }}
+              </div>
+            </v-chip>
+          </template>
+        </template>
+        <template #selection="data">
+          <v-chip
+            v-bind="data.attrs"
+            :input-value="data.selected"
+            v-if="getRoomStatus(data.item) === 'new'"
+            close
+            class="mb-1 mt-1"
+            small
+            outlined
+            color="success"
+            @click:close="removeRoom(data.item)"
+          >
+            <v-icon left small> mdi-plus </v-icon>
+            {{ data.item.shortName ? data.item.shortName : data.item.name }}
+          </v-chip>
+        </template>
+      </v-autocomplete>
+
+      <teacher-field
+        v-if="amendableTeachers.length > 0"
+        :priority-subject="subject"
+        :show-subjects="true"
+        :value="substitutionTeacherIDs"
+        chips
+        deletable-chips
+        dense
+        hide-details
+        outlined
+        :disabled="loading"
+        :label="$t('chronos.substitutions.overview.teacher.label')"
+        :custom-teachers="amendableTeachers"
+        @input="teachersInput"
+      >
+        <template #prepend-inner>
+          <v-tooltip bottom>
+            <template #activator="{ on, attrs }">
+              <v-chip
+                v-for="teacher in teachersWithStatus.filter(
+                  (t) => t.status === 'removed',
+                )"
+                :key="teacher.id"
+                outlined
+                color="error"
+                class="mb-1"
+                small
+                v-on="on"
+                v-bind="attrs"
+              >
+                <v-icon left small>mdi-account-off-outline</v-icon>
+                <div class="text-decoration-line-through">
+                  {{ teacher.fullName }}
+                </div>
+              </v-chip>
+            </template>
+            <span>{{
+              $t("chronos.substitutions.overview.teacher.status.absent")
+            }}</span>
+          </v-tooltip>
+        </template>
+        <template #selection="data">
+          <v-tooltip bottom>
+            <template #activator="{ on, attrs }">
+              <v-chip
+                v-bind="{ ...data.attrs, ...attrs }"
+                :input-value="data.selected"
+                close
+                class="mb-1 mt-1"
+                small
+                :outlined="getTeacherStatus(data.item) === 'new'"
+                :color="getTeacherStatus(data.item) === 'new' ? 'success' : ''"
+                @click:close="removeTeacher(data.item)"
+                v-on="on"
+              >
+                <v-icon left small v-if="getTeacherStatus(data.item) === 'new'">
+                  mdi-account-plus-outline
+                </v-icon>
+                {{ data.item.fullName }}
+              </v-chip>
+            </template>
+            <span>{{
+              getTeacherStatus(data.item) === "new"
+                ? $t("chronos.substitutions.overview.teacher.status.new")
+                : $t("chronos.substitutions.overview.teacher.status.regular")
+            }}</span>
+          </v-tooltip>
+        </template>
+      </teacher-field>
+
+      <v-text-field
+        dense
+        outlined
+        hide-details
+        :label="$t('chronos.substitutions.overview.comment')"
+        :value="substitution.comment"
+        @input="comment = $event"
+        @focusout="save(false)"
+        @keydown.enter="save(false)"
+      />
+
+      <v-btn-toggle
+        mandatory
+        dense
+        :color="substitution.cancelled ? 'error' : 'success'"
+        :disabled="loading"
+        class="justify-self-end"
+        :value="substitution.cancelled"
+        @change="save(false)"
+      >
+        <v-btn outlined :value="false" @click="cancelled = false">
+          {{ $t("chronos.substitutions.overview.cancel.not_cancelled") }}
+        </v-btn>
+        <v-btn outlined :value="true" @click="cancelled = true">
+          {{ $t("chronos.substitutions.overview.cancel.cancelled") }}
+        </v-btn>
+      </v-btn-toggle>
+    </v-card-text>
+    <v-divider />
+  </v-card>
+</template>
+
+<script>
+export default {
+  name: "SubstitutionCard",
+  emits: ["open", "close", "delete"],
+  mixins: [createOrPatchMixin, deleteMixin],
+  data() {
+    return {
+      loading: false,
+      teachers: [],
+      rooms: [],
+      substitutionSubject: null,
+      comment: null,
+      cancelled: null,
+    };
+  },
+  props: {
+    substitution: {
+      type: Object,
+      required: true,
+    },
+    amendableRooms: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    amendableSubjects: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    amendableTeachers: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+  },
+  methods: {
+    handleUpdateAfterCreateOrPatch(itemId) {
+      return (cached, incoming) => {
+        for (const object of incoming) {
+          console.log("summary: handleUpdateAfterCreateOrPatch", object);
+          // Replace the current substitution
+          const index = cached.findIndex(
+            (o) => o[itemId] === this.substitution.id,
+          );
+          // merged with the incoming partial substitution
+          // if creation of proper substitution from dummy one, set ID of substitution currently being edited as oldID so that key in overview doesn't change
+          cached[index] = {
+            ...this.substitution,
+            ...object,
+            oldId:
+              this.substitution.id !== object.id
+                ? this.substitution.id
+                : this.substitution.oldId,
+          };
+        }
+        return cached;
+      };
+    },
+    handleUpdateAfterDelete(ids, itemId) {
+      return (cached, incoming) => {
+        for (const id of ids) {
+          // Remove item from cached data or reset it to old ID, if present
+          const index = cached.findIndex((o) => o[itemId] === id);
+          if (cached[index].oldId) {
+            cached[index].id = cached[index].oldId;
+            cached[index].oldId = null;
+
+            // Clear dummy substitution
+            cached[index].teachers = [];
+            cached[index].rooms = [];
+            cached[index].subject = null;
+            cached[index].comment = "";
+            cached[index].cancelled = false;
+          } else {
+            this.$emit("delete", cached[index].datetimeStart);
+          }
+        }
+        return cached;
+      };
+    },
+    getTeacherStatus(teacher) {
+      return this.teachersWithStatus.find((t) => t.id === teacher.id)?.status;
+    },
+    getRoomStatus(room) {
+      return this.roomsWithStatus.find((r) => r.id === room.id)?.status;
+    },
+    removeTeacher(teacher) {
+      this.teachers = this.substitutionTeacherIDs.filter(
+        (t) => t !== teacher.id,
+      );
+      this.save(true);
+    },
+    removeRoom(room) {
+      this.rooms = this.substitutionRoomIDs.filter((r) => r !== room.id);
+      this.save(true);
+    },
+    subjectInput(subject) {
+      this.substitutionSubject = subject.id;
+      this.save();
+    },
+    teachersInput(teachers) {
+      this.teachers = teachers;
+      this.save();
+    },
+    roomsInput(rooms) {
+      this.rooms = rooms;
+      this.save();
+    },
+    cancelledInput(cancelled) {
+      this.cancelled = cancelled;
+      this.save();
+    },
+    save(allowEmpty = false) {
+      if (
+        this.teachers.length ||
+        this.rooms.length ||
+        this.substitutionSubject !== null ||
+        (this.comment !== null && this.comment !== "") ||
+        this.cancelled !== null
+      ) {
+        this.createOrPatch([
+          {
+            id: this.substitution.id,
+            ...((allowEmpty || this.teachers.length) && {
+              teachers: this.teachers,
+            }),
+            ...((allowEmpty || this.rooms.length) && { rooms: this.rooms }),
+            ...(this.substitutionSubject !== null && {
+              subject: this.substitutionSubject,
+            }),
+            ...(this.comment !== null &&
+              this.comment !== "" && { comment: this.comment }),
+            ...(this.cancelled !== null && { cancelled: this.cancelled }),
+            ...((this.teachers.length || this.rooms.length) && {
+              cancelled: false,
+            }),
+          },
+        ]);
+        this.teachers = [];
+        this.rooms = [];
+        this.substitutionSubject = null;
+        this.comment = null;
+        this.cancelled = null;
+      } else if (!this.substitution.id.startsWith("DUMMY")) {
+        this.delete([this.substitution]);
+      }
+    },
+  },
+  computed: {
+    substitutionTeacherIDs() {
+      return this.substitution.teachers.map((teacher) => teacher.id);
+    },
+    substitutionRoomIDs() {
+      return this.substitution.rooms.map((room) => room.id);
+    },
+    // Group teachers by their substitution status (regular, new, removed)
+    teachersWithStatus() {
+      // IDs of teachers of amended lesson
+      const oldIds = this.substitution.amends.teachers.map(
+        (teacher) => teacher.id,
+      );
+      // IDs of teachers of new substitution lesson
+      const newIds = this.substitution.teachers.map((teacher) => teacher.id);
+      const allTeachers = new Set(
+        this.substitution.amends.teachers.concat(this.substitution.teachers),
+      );
+      let teachersWithStatus = Array.from(allTeachers).map((teacher) => {
+        let status = "regular";
+        if (newIds.includes(teacher.id) && !oldIds.includes(teacher.id)) {
+          // Mark teacher as being new if they are only linked to the substitution lesson
+          status = "new";
+        } else if (
+          !newIds.includes(teacher.id) &&
+          oldIds.includes(teacher.id)
+        ) {
+          // Mark teacher as being rremoved if they are only linked to the amended lesson
+          status = "removed";
+        }
+        return { ...teacher, status: status };
+      });
+      return teachersWithStatus;
+    },
+    roomsWithStatus() {
+      const oldIds = this.substitution.amends.rooms.map((room) => room.id);
+      const newIds = this.substitution.rooms.map((room) => room.id);
+      const allRooms = new Set(
+        this.substitution.amends.rooms.concat(
+          this.substitution.rooms.filter((r) => !oldIds.includes(r.id)),
+        ),
+      );
+      let roomsWithStatus = Array.from(allRooms).map((room) => {
+        let status = "regular";
+        if (newIds.includes(room.id) && !oldIds.includes(room.id)) {
+          status = "new";
+        } else if (
+          newIds.length &&
+          !newIds.includes(room.id) &&
+          oldIds.includes(room.id)
+        ) {
+          status = "removed";
+        }
+        return { ...room, status: status };
+      });
+      return roomsWithStatus;
+    },
+    subject() {
+      if (this.substitution.subject) {
+        return this.substitution.subject;
+      } else if (this.substitution.course?.subject) {
+        return this.substitution.course.subject;
+      } else if (this.substitution.amends?.subject) {
+        return this.substitution.amends.subject;
+      }
+      return undefined;
+    },
+  },
+};
+</script>
+
+<style scoped>
+.main-body {
+  display: grid;
+  align-items: center;
+  grid-template-columns: 2fr 1fr 2fr 3fr 1fr 2fr;
+  gap: 1em;
+}
+.vertical {
+  grid-template-columns: 1fr;
+}
+.justify-self-end {
+  justify-self: end;
+}
+</style>
diff --git a/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionInformation.vue b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionInformation.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e64eb9c1999b45e5020583ec4b168385d88cf838
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionInformation.vue
@@ -0,0 +1,74 @@
+<script setup>
+import SubstitutionStatus from "./SubstitutionStatus.vue";
+
+import { DateTime } from "luxon";
+</script>
+
+<template>
+  <div class="full-width grid">
+    <div class="d-flex">
+      <substitution-status :substitution="substitution" />
+      <div class="text-right d-flex flex-column fit-content">
+        <time :datetime="substitution.datetimeStart" class="text-no-wrap">
+          {{ $d(toDateTime(substitution.datetimeStart), "shortTime") }}
+        </time>
+        <time :datetime="substitution.datetimeEnd" class="text-no-wrap">
+          {{ $d(toDateTime(substitution.datetimeEnd), "shortTime") }}
+        </time>
+      </div>
+    </div>
+    <span class="text-subtitle-1 font-weight-medium">
+      {{ course?.name }}
+    </span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "SubstitutionInformation",
+  props: {
+    substitution: {
+      type: Object,
+      required: true,
+    },
+  },
+  methods: {
+    toDateTime(dateString) {
+      return DateTime.fromISO(dateString);
+    },
+  },
+  computed: {
+    course() {
+      if (this.substitution.course) {
+        return this.substitution.course;
+      } else if (this.substitution.amends?.course) {
+        return this.substitution.amends.course;
+      }
+      return undefined;
+    },
+  },
+};
+</script>
+
+<style scoped>
+.grid {
+  display: grid;
+  align-items: center;
+  gap: 1em;
+  grid-template-columns: 1fr 1fr;
+  align-content: unset;
+}
+
+.grid:last-child {
+  justify-self: end;
+  justify-content: end;
+}
+
+.fit-content {
+  width: fit-content;
+}
+
+.gap {
+  gap: 0.25em;
+}
+</style>
diff --git a/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionLoader.vue b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionLoader.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8bb7e60a968e1b2e38b335d10b91a67e4de6250e
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionLoader.vue
@@ -0,0 +1,32 @@
+<template>
+  <v-card class="my-2 full-width">
+    <div class="full-width d-flex flex-column align-stretch flex-md-row">
+      <v-card-text>
+        <v-skeleton-loader
+          type="avatar, heading, chip"
+          class="d-flex full-width align-center gap"
+          height="100%"
+        />
+      </v-card-text>
+      <v-card-text>
+        <v-skeleton-loader
+          type="heading"
+          class="d-flex full-width align-center gap"
+          height="100%"
+        />
+      </v-card-text>
+      <v-card-text>
+        <v-skeleton-loader
+          type="button"
+          class="d-flex full-width align-center justify-end gap"
+          height="100%"
+        />
+      </v-card-text>
+    </div>
+  </v-card>
+</template>
+<script>
+export default {
+  name: "SubstitutionLoader",
+};
+</script>
diff --git a/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionOverview.vue b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionOverview.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6ec5bcec243090e8c574adf91f8f4727da887a3c
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionOverview.vue
@@ -0,0 +1,186 @@
+<script setup>
+import SubstitutionCard from "./SubstitutionCard.vue";
+import SubstitutionLoader from "./SubstitutionLoader.vue";
+import InfiniteScrollingDateSortedCRUDIterator from "aleksis.core/components/generic/InfiniteScrollingDateSortedCRUDIterator.vue";
+
+import {
+  amendedLessonsFromAbsences,
+  createOrUpdateSubstitutions,
+  deleteAmendLessons,
+  gqlGroups,
+  gqlRooms,
+  gqlSubjects,
+  gqlTeachers,
+} from "../amendLesson.graphql";
+
+import { DateTime } from "luxon";
+</script>
+
+<template>
+  <infinite-scrolling-date-sorted-c-r-u-d-iterator
+    :gql-query="gqlQuery"
+    :gql-additional-query-args="gqlQueryArgs"
+    :gql-patch-mutation="gqlPatchMutation"
+    :get-patch-data="gqlGetPatchData"
+    i18n-key="chronos.substitutions.overview"
+    :enable-search="true"
+    :enable-create="false"
+    :show-create="false"
+    :enable-delete="false"
+    :enable-edit="true"
+    :elevated="false"
+    :force-model-item-update="true"
+    :day-increment="3"
+    empty-icon="mdi-account-multiple-check-outline"
+    ref="iterator"
+  >
+    <template #additionalActions="{ attrs, on }">
+      <v-autocomplete
+        :items="groups"
+        item-text="name"
+        item-value="id"
+        clearable
+        filled
+        dense
+        hide-details
+        :placeholder="$t('chronos.substitutions.overview.filter.groups')"
+        :loading="$apollo.queries.groups.loading"
+        :value="objId"
+        @input="changeGroup"
+        @click:clear="changeGroup"
+      />
+      <v-autocomplete
+        :items="amendableTeachers"
+        item-text="fullName"
+        item-value="id"
+        clearable
+        filled
+        dense
+        hide-details
+        :placeholder="$t('chronos.substitutions.overview.filter.teachers')"
+        :loading="$apollo.queries.amendableTeachers.loading"
+        :value="teacherId"
+        @input="changeTeacher"
+        @click:clear="changeTeacher"
+      />
+      <v-switch
+        :loading="$apollo.queries.groups.loading"
+        :label="$t('chronos.substitutions.overview.filter.missing')"
+        v-model="incomplete"
+        dense
+        inset
+        hide-details
+        class="ml-6"
+      />
+
+      <v-alert type="info" outlined dense class="full-width">
+        <v-row align="center" no-gutters>
+          <v-col cols="12" md="9">
+            {{ $t("chronos.substitutions.overview.info_alert.text") }}
+          </v-col>
+          <v-col cols="12" md="3" align="right">
+            <v-btn
+              color="info"
+              outlined
+              small
+              :to="{ name: 'kolego.absences' }"
+            >
+              {{ $t("chronos.substitutions.overview.info_alert.button") }}
+            </v-btn>
+          </v-col>
+        </v-row>
+      </v-alert>
+    </template>
+
+    <template #item="{ item, lastQuery }">
+      <substitution-card
+        :substitution="item"
+        :affected-query="lastQuery"
+        :is-create="false"
+        :gql-patch-mutation="gqlPatchMutation"
+        :gql-delete-mutation="gqlDeleteMutation"
+        :amendable-rooms="amendableRooms"
+        :amendable-subjects="amendableSubjects"
+        :amendable-teachers="amendableTeachers"
+        @delete="handleDelete"
+      />
+    </template>
+
+    <template #itemLoader>
+      <substitution-loader />
+    </template>
+  </infinite-scrolling-date-sorted-c-r-u-d-iterator>
+</template>
+
+<script>
+export default {
+  name: "SubstitutionOverview",
+  props: {
+    objId: {
+      type: [Number, String],
+      required: false,
+      default: null,
+    },
+    teacherId: {
+      type: [Number, String],
+      required: false,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      gqlQuery: amendedLessonsFromAbsences,
+      gqlPatchMutation: createOrUpdateSubstitutions,
+      gqlDeleteMutation: deleteAmendLessons,
+      groups: [],
+      amendableTeachers: [],
+      incomplete: false,
+    };
+  },
+  methods: {
+    gqlGetPatchData(item) {
+      return { id: item.id, teachers: item.teachers };
+    },
+    changeGroup(group) {
+      this.$router.push({
+        name: "chronos.substitutionOverview",
+        params: {
+          objId: group ? group : "all",
+          teacherId: this.teacherId,
+        },
+        hash: this.$route.hash,
+      });
+      this.$refs.iterator.resetDate();
+    },
+    changeTeacher(teacher) {
+      this.$router.push({
+        name: "chronos.substitutionOverview",
+        params: {
+          teacherId: teacher ? teacher : "all",
+          objId: this.objId,
+        },
+        hash: this.$route.hash,
+      });
+      this.$refs.iterator.resetDate();
+    },
+    handleDelete(datetime) {
+      this.$refs.iterator.refetchDay(DateTime.fromISO(datetime).toISODate());
+    },
+  },
+  computed: {
+    gqlQueryArgs() {
+      return {
+        objId: this.objId === "all" ? null : Number(this.objId),
+        teacher: this.teacherId === "all" ? null : Number(this.teacherId),
+        incomplete: !!this.incomplete,
+      };
+    },
+  },
+  apollo: {
+    groups: gqlGroups,
+    amendableRooms: gqlRooms,
+    amendableSubjects: gqlSubjects,
+    amendableTeachers: gqlTeachers,
+  },
+};
+</script>
diff --git a/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionStatus.vue b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionStatus.vue
new file mode 100644
index 0000000000000000000000000000000000000000..83990f4539b0fbbe185d3f7ad48414d06539cd8c
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/substitutions/SubstitutionStatus.vue
@@ -0,0 +1,79 @@
+<template>
+  <v-tooltip bottom>
+    <template #activator="{ on, attrs }">
+      <v-icon
+        :color="currentStatus?.color"
+        class="mr-md-4"
+        v-on="on"
+        v-bind="attrs"
+        >{{ currentStatus?.icon }}</v-icon
+      >
+    </template>
+    <span>{{ currentStatus?.text }}</span>
+  </v-tooltip>
+</template>
+
+<script>
+export default {
+  name: "SubstitutionStatus",
+  data() {
+    return {
+      statusChoices: [
+        {
+          name: "unedited",
+          text: this.$t("chronos.substitutions.overview.status.unedited"),
+          icon: "mdi-calendar-question-outline",
+          color: "warning",
+        },
+        {
+          name: "substituted",
+          text: this.$t("chronos.substitutions.overview.status.substituted"),
+          icon: "mdi-calendar-edit-outline",
+          color: "success",
+        },
+        {
+          name: "cancelled",
+          text: this.$t("chronos.substitutions.overview.status.cancelled"),
+          icon: "mdi-cancel",
+          color: "error",
+        },
+      ],
+      statusTimeout: null,
+      currentStatusName: "",
+    };
+  },
+  props: {
+    substitution: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    currentStatus() {
+      return this.statusChoices.find((s) => s.name === this.currentStatusName);
+    },
+  },
+  methods: {
+    updateStatus() {
+      if (this.substitution.id.startsWith("DUMMY")) {
+        this.currentStatusName = "unedited";
+      } else if (this.substitution.cancelled) {
+        this.currentStatusName = "cancelled";
+      } else {
+        this.currentStatusName = "substituted";
+      }
+    },
+  },
+  watch: {
+    substitution: {
+      handler() {
+        this.updateStatus();
+      },
+      deep: true,
+    },
+  },
+  mounted() {
+    this.updateStatus();
+  },
+};
+</script>
diff --git a/aleksis/apps/chronos/frontend/components/timetables.graphql b/aleksis/apps/chronos/frontend/components/timetables.graphql
index 5bbe46f734f1981ad1c2648d50a8a6e8d7bd04fc..6e575f2726e920e31656c2470495b12399532132 100644
--- a/aleksis/apps/chronos/frontend/components/timetables.graphql
+++ b/aleksis/apps/chronos/frontend/components/timetables.graphql
@@ -7,3 +7,7 @@ query gqlAvailableTimetables {
     shortName
   }
 }
+
+query gqlTimetableDays {
+  timetableDays
+}
diff --git a/aleksis/apps/chronos/frontend/index.js b/aleksis/apps/chronos/frontend/index.js
index 1893e4f184e67177fb6f3d0e9d2f8d661c643d55..34a0380b03bc23645f765e08149cb0ce37a9a3a2 100644
--- a/aleksis/apps/chronos/frontend/index.js
+++ b/aleksis/apps/chronos/frontend/index.js
@@ -1,5 +1,5 @@
 import { hasPersonValidator } from "aleksis.core/routeValidators";
-import Timetable from "./components/Timetable.vue";
+
 import Substitutions from "./components/Substitutions.vue";
 import { DateTime } from "luxon";
 
@@ -14,7 +14,7 @@ export default {
   children: [
     {
       path: "timetable/",
-      component: Timetable,
+      component: () => import("./components/Timetable.vue"),
       name: "chronos.timetable",
       meta: {
         inMenu: true,
@@ -27,7 +27,7 @@ export default {
     },
     {
       path: "timetable/:type/:id/",
-      component: Timetable,
+      component: () => import("./components/Timetable.vue"),
       name: "chronos.timetableWithId",
       meta: {
         permission: "chronos.view_timetable_overview_rule",
@@ -36,7 +36,7 @@ export default {
       children: [
         {
           path: ":view(month|week|day)/:year(\\d\\d\\d\\d)/:month(\\d\\d)/:day(\\d\\d)/",
-          component: Timetable,
+          component: () => import("./components/Timetable.vue"),
           name: "chronos.timetableWithIdAndParams",
           meta: {
             permission: "chronos.view_timetable_overview_rule",
@@ -45,6 +45,41 @@ export default {
         },
       ],
     },
+    {
+      path: "substitution_overview/",
+      component: () =>
+        import("./components/substitutions/SubstitutionOverview.vue"),
+      redirect: () => {
+        return {
+          path: "substitution_overview/group/all/person/all/",
+          hash: "#" + DateTime.now().toISODate(),
+        };
+      },
+      name: "chronos.substitutionOverviewLanding",
+      props: true,
+      meta: {
+        inMenu: true,
+        icon: "mdi-account-convert-outline",
+        iconActive: "mdi-account-convert",
+        titleKey: "chronos.substitutions.overview.menu_title",
+        toolbarTitle: "chronos.substitutions.overview.menu_title",
+        permission: "chronos.view_substitution_overview_rule",
+      },
+      children: [
+        {
+          path: "group/:objId(all|\\d+)?/person/:teacherId(all|\\d+)?/",
+          component: () =>
+            import("./components/substitutions/SubstitutionOverview.vue"),
+          name: "chronos.substitutionOverview",
+          meta: {
+            titleKey: "chronos.substitutions.overview.menu_title",
+            toolbarTitle: "chronos.substitutions.overview.menu_title",
+            permission: "chronos.view_substitution_overview_rule",
+            fullWidth: true,
+          },
+        },
+      ],
+    },
     {
       path: "substitutions/print/",
       component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
diff --git a/aleksis/apps/chronos/frontend/messages/de.json b/aleksis/apps/chronos/frontend/messages/de.json
index a7c50a848e25f14b0cc24a36f0cf80df2e0efd94..174e7a1911a1d7c57f7d2da763772cf0d3983c62 100644
--- a/aleksis/apps/chronos/frontend/messages/de.json
+++ b/aleksis/apps/chronos/frontend/messages/de.json
@@ -22,20 +22,57 @@
     },
     "menu_title": "Stundenpläne",
     "substitutions": {
+      "affected_groups": "Betroffene Gruppen",
+      "affected_teachers": "Betroffene Lehrkräfte",
+      "all_day": "Ganztägig",
+      "cancelled": "Entfällt",
+      "groups": "Gruppen",
       "menu_title": "Vertretungen",
+      "no_substitutions": "Keine Vertretungen",
+      "notes": "Notizen",
+      "overview": {
+        "cancel": {
+          "cancelled": "Fällt aus",
+          "not_cancelled": "Findet statt"
+        },
+        "comment": "Kommentar",
+        "filter": {
+          "groups": "Nach Gruppen filtern",
+          "missing": "Nur unvollständige Stunden anzeigen",
+          "teachers": "Nach Lehrkraft filtern"
+        },
+        "info_alert": {
+          "button": "Abwesenheiten verwalten",
+          "text": "Diese Seite zeigt alle Unterrichtsstunden die mit bekannten Abwesenheiten von ihren entsprechenden Lehrkräften kollidieren."
+        },
+        "menu_title": "Vertretungen planen",
+        "rooms": {
+          "label": "Räume"
+        },
+        "status": {
+          "cancelled": "Entfällt",
+          "substituted": "Vertreten",
+          "unedited": "Unbearbeitet"
+        },
+        "subject": {
+          "label": "Fach"
+        },
+        "teacher": {
+          "label": "Lehrkräfte",
+          "status": {
+            "absent": "Abwesend",
+            "new": "Vertretungslehrkraft",
+            "regular": "Regulär"
+          }
+        },
+        "title_plural": "Vertretungen planen"
+      },
       "print": "Drucken",
-      "groups": "Gruppen",
-      "time": "Zeit",
-      "teachers": "Lehrer",
-      "subject": "Fach",
       "rooms": "Räume",
-      "notes": "Notizen",
+      "subject": "Fach",
       "supervision": "Aufsicht",
-      "cancelled": "Entfällt",
-      "affected_teachers": "Betroffene Lehrer",
-      "affected_groups": "Betroffene Gruppen",
-      "all_day": "Ganztägig",
-      "no_substitutions": "Keine Vertretungen"
+      "teachers": "Lehrkräfte",
+      "time": "Zeit"
     },
     "supervisions": {
       "menu_title_daily": "Aufsichten",
@@ -50,6 +87,7 @@
         "description": "Wählen Sie auf der linken Seite einen Stundenplan aus, um ihn hier anzuzeigen",
         "title": "Kein Stundenplan ausgewählt"
       },
+      "no_timetables_in_term": "Keine Stundenpläne im ausgewählten Schuljahr",
       "prev": "Vorheriger Stundenplan",
       "search": "Stundenpläne suchen",
       "select": "Stundenplan auswählen",
diff --git a/aleksis/apps/chronos/frontend/messages/en.json b/aleksis/apps/chronos/frontend/messages/en.json
index 7ca0d3ee9fcc7774032eb2c2fa13950c361d898f..facaeb7223f8f34551f36ee14563008f31592de6 100644
--- a/aleksis/apps/chronos/frontend/messages/en.json
+++ b/aleksis/apps/chronos/frontend/messages/en.json
@@ -17,27 +17,12 @@
         "groups": "Groups",
         "teachers": "Teachers",
         "rooms": "Rooms"
-      }
+      },
+      "no_timetables_in_term": "No timetables in the selected school term."
     },
     "lessons": {
       "menu_title_daily": "Daily lessons"
     },
-    "substitutions": {
-      "menu_title": "Substitutions",
-      "print": "Print",
-      "groups": "Groups",
-      "time": "Time",
-      "teachers": "Teachers",
-      "subject": "Subject",
-      "rooms": "Rooms",
-      "notes": "Notes",
-      "supervision": "Supervision",
-      "cancelled": "Cancelled",
-      "affected_teachers": "Affected teachers",
-      "affected_groups": "Affected groups",
-      "all_day": "All day",
-      "no_substitutions": "No substitutions"
-    },
     "supervisions": {
       "title": "Supervision",
       "menu_title_daily": "Daily supervisions"
@@ -58,6 +43,59 @@
         "cancelled": "Cancelled",
         "comment": "Comment"
       }
+    },
+    "substitutions": {
+      "menu_title": "Substitutions",
+      "print": "Print",
+      "groups": "Groups",
+      "time": "Time",
+      "teachers": "Teachers",
+      "subject": "Subject",
+      "rooms": "Rooms",
+      "notes": "Notes",
+      "supervision": "Supervision",
+      "cancelled": "Cancelled",
+      "affected_teachers": "Affected teachers",
+      "affected_groups": "Affected groups",
+      "all_day": "All day",
+      "no_substitutions": "No substitutions",
+      "overview": {
+        "menu_title": "Plan substitutions",
+        "title_plural": "Plan substitutions",
+        "info_alert": {
+          "text": "This page shows all lessons that collide with known absences of their respective teachers.",
+          "button": "Manage absences"
+        },
+        "filter": {
+          "groups": "Filter by groups",
+          "teachers": "Filter by teacher",
+          "missing": "Only show incomplete lessons"
+        },
+        "cancel": {
+          "cancelled": "Cancelled",
+          "not_cancelled": "Taking place"
+        },
+        "status": {
+          "unedited": "Unedited",
+          "substituted": "Substituted",
+          "cancelled": "Cancelled"
+        },
+        "teacher": {
+          "label": "Teachers",
+          "status": {
+            "absent": "Absent",
+            "regular": "Regular",
+            "new": "Substitution teacher"
+          }
+        },
+        "rooms": {
+          "label": "Rooms"
+        },
+        "subject": {
+          "label": "Subject"
+        },
+        "comment": "Comment"
+      }
     }
   }
 }
diff --git a/aleksis/apps/chronos/frontend/messages/ru.json b/aleksis/apps/chronos/frontend/messages/ru.json
index fb663b2ba0a8d4ea0cdb26857b19f8e7856a1816..6f6c5c9a26664c1c2a76a8bd68dfc5cf5137a51b 100644
--- a/aleksis/apps/chronos/frontend/messages/ru.json
+++ b/aleksis/apps/chronos/frontend/messages/ru.json
@@ -11,7 +11,23 @@
     },
     "menu_title": "Расписания",
     "substitutions": {
-      "menu_title": "Замены"
+      "cancelled": "Отменено",
+      "groups": "Группы",
+      "menu_title": "Замены",
+      "notes": "Заметки",
+      "overview": {
+        "cancel": {
+          "cancelled": "Отменено"
+        },
+        "rooms": {
+          "label": "Комнаты"
+        },
+        "status": {
+          "cancelled": "Отменено"
+        }
+      },
+      "rooms": "Комнаты",
+      "time": "Время"
     },
     "supervisions": {
       "menu_title_daily": "Ежедневные наблюдения"
diff --git a/aleksis/apps/chronos/frontend/messages/uk.json b/aleksis/apps/chronos/frontend/messages/uk.json
index 09d0c8ac28b91229ad842db89ed7c0b65514af03..4c18497f9f7f6566b497060c999292f077663d82 100644
--- a/aleksis/apps/chronos/frontend/messages/uk.json
+++ b/aleksis/apps/chronos/frontend/messages/uk.json
@@ -3,25 +3,76 @@
     "event": {
       "amend": {
         "cancelled": "Скасовано",
-        "rooms": "Кімнати"
-      }
+        "comment": "Коментар",
+        "delete_button": "Скинути",
+        "delete_dialog": "Ви дійсно хочете видалити цю заміну?",
+        "delete_success": "Заміна успішно видалена.",
+        "edit_button": "Змінити",
+        "rooms": "Кімнати",
+        "subject": "Предмет",
+        "teachers": "Викладачі",
+        "title": "Змінити урок"
+      },
+      "current_changes": "Поточні зміни",
+      "no_room": "Без кімнат",
+      "no_teacher": "Без викладачів"
     },
     "lessons": {
       "menu_title_daily": "Щоденні уроки"
     },
     "menu_title": "Розклади",
     "substitutions": {
-      "menu_title": "Заміни"
+      "cancelled": "Скасовано",
+      "groups": "Групи",
+      "menu_title": "Заміни",
+      "notes": "Нотатки",
+      "overview": {
+        "cancel": {
+          "cancelled": "Скасовано"
+        },
+        "comment": "Коментар",
+        "filter": {
+          "missing": "Показати лише неповні уроки"
+        },
+        "rooms": {
+          "label": "Кімнати"
+        },
+        "status": {
+          "cancelled": "Скасовано"
+        },
+        "subject": {
+          "label": "Предмет"
+        },
+        "teacher": {
+          "label": "Викладачі"
+        }
+      },
+      "rooms": "Кімнати",
+      "subject": "Предмет",
+      "supervision": "Контроль",
+      "teachers": "Викладачі",
+      "time": "Час"
     },
     "supervisions": {
-      "menu_title_daily": "Щоденні спостереження"
+      "menu_title_daily": "Щоденні спостереження",
+      "title": "Контроль"
     },
     "timetable": {
+      "menu_title": "Розклади",
       "menu_title_all": "Усі розклади",
       "menu_title_my": "Мій розклад",
+      "next": "Наступний розклад",
+      "no_timetable_selected": {
+        "description": "Виберіть розклад занять ліворуч для перегляду його тут",
+        "title": "Розклад не обраний"
+      },
+      "prev": "Попередній розклад",
+      "search": "Пошук розкладів",
+      "select": "Оберіть розклад",
       "types": {
         "groups": "Групи",
-        "rooms": "Кімнати"
+        "rooms": "Кімнати",
+        "teachers": "Викладачі"
       }
     }
   }
diff --git a/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
index 67e0bba4ca0e30d6ab451ba309c91fd6048dada5..48f30337626dd0300b7bab3854942507c9f1589f 100644
--- a/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/ar/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,14 +18,34 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+msgid "Can manage group substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr ""
 
+#: aleksis/apps/chronos/model_extensions.py:22
+msgid "Can view group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:26
+msgid "Can view person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:30
+msgid "Can view room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:34
+msgid "Can view course timetable"
+msgstr ""
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr ""
@@ -63,88 +83,112 @@ msgid "Can view all person timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:150
-msgid "Can view timetable overview"
+msgid "Can view all course timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:151
+msgid "Can view timetable overview"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+msgid "Can view all room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:154
+msgid "Can view all group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:155
+msgid "Can view all person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:156
+msgid "Can manage all substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr ""
 
@@ -196,6 +240,38 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr ""
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+msgid "Friday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr ""
diff --git a/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
index d53ce3b87c37282efc92294219a0fa18d0cdc4d2..c64f0b51eb786c020e9911d4ebe3c1612b72bd84 100644
--- a/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: unnamed project\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: 2024-10-17 11:54+0200\n"
 "Last-Translator: Jonathan Weth <git@jonathanewth.de>\n"
 "Language-Team: English\n"
@@ -19,14 +19,44 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Gtranslator 47.0\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr "Kann Gruppenstundenpläne sehen"
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+#, fuzzy
+#| msgid "Manage substitution"
+msgid "Can manage group substitutions"
+msgstr "Vertretung verwalten"
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr "Kann Personenstundenpläne sehen"
 
+#: aleksis/apps/chronos/model_extensions.py:22
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view group supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/model_extensions.py:26
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view person supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/model_extensions.py:30
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view room supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/model_extensions.py:34
+#, fuzzy
+#| msgid "Can view group timetable"
+msgid "Can view course timetable"
+msgstr "Kann Gruppenstundenpläne sehen"
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr "Anzahl der Tage, die im Plan angezeigt werden"
@@ -64,88 +94,122 @@ msgid "Can view all person timetables"
 msgstr "Kann alle Personenstundenpläne sehen"
 
 #: aleksis/apps/chronos/models.py:150
+#, fuzzy
+#| msgid "Can view all group timetables"
+msgid "Can view all course timetables"
+msgstr "Kann alle Gruppenstundenpläne sehen"
+
+#: aleksis/apps/chronos/models.py:151
 msgid "Can view timetable overview"
 msgstr "Kann Stundenplanübersicht sehen"
 
-#: aleksis/apps/chronos/models.py:151
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr "Kann Vertretungstabelle sehen"
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view all room supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/models.py:154
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view all group supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/models.py:155
+#, fuzzy
+#| msgid "Can view all supervisions per day"
+msgid "Can view all person supervisions"
+msgstr "Kann alle Tagesaufsichten sehen"
+
+#: aleksis/apps/chronos/models.py:156
+#, fuzzy
+#| msgid "Manage substitution"
+msgid "Can manage all substitutions"
+msgstr "Vertretung verwalten"
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr "Unterrichtsstunden"
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr "Name"
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr "Startzeitfenster-Nummer"
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr "Endzeitfenster-Nummer"
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr "Kurs"
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr "Gruppen"
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr "Räume"
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr "Lehrkräfte"
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr "Fach"
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr "Entfall"
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr "Kommentar"
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr "{} (statt {})"
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr "Unterrichtsstunde"
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 msgid "Lesson Event"
 msgstr "Unterrichtsstunde"
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 msgid "Lesson Events"
 msgstr "Unterrichtsstunden"
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr "Aufsichten"
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr "Aufsicht: {}"
 
@@ -158,12 +222,8 @@ msgid "Use parent groups in timetable views"
 msgstr "Elterngruppen in Stundenplanansichten benutzen"
 
 #: aleksis/apps/chronos/preferences.py:31
-msgid ""
-"If a lesson or substitution has only one group and this group has parent "
-"groups, show the parent groups instead of the original group."
-msgstr ""
-"Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe "
-"Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
+msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
+msgstr "Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
 
 #: aleksis/apps/chronos/preferences.py:44
 msgid "Relevant days for substitution plans"
@@ -175,21 +235,15 @@ msgstr "Zeit zu der Vertretungspläne zum nächsten Tag wechseln"
 
 #: aleksis/apps/chronos/preferences.py:70
 msgid "Number of days shown on substitutions print view"
-msgstr ""
-"Anzahl der Tage, die in der Druckansicht des Vertretungsplanes angezeigt "
-"werden soll"
+msgstr "Anzahl der Tage, die in der Druckansicht des Vertretungsplanes angezeigt werden soll"
 
 #: aleksis/apps/chronos/preferences.py:78
 msgid "Show header box in substitution views"
 msgstr "Kopfbox in Vertretungsplänen anzeigen"
 
 #: aleksis/apps/chronos/preferences.py:88
-msgid ""
-"Show parent groups in header box in substitution views instead of original "
-"groups"
-msgstr ""
-"Elterngruppen in der Kopfbox in Vertretungsplänen an Stelle der eigentlichen "
-"Gruppen anzeigen"
+msgid "Show parent groups in header box in substitution views instead of original groups"
+msgstr "Elterngruppen in der Kopfbox in Vertretungsplänen an Stelle der eigentlichen Gruppen anzeigen"
 
 #: aleksis/apps/chronos/preferences.py:99
 msgid "Group types to show in timetables"
@@ -207,6 +261,40 @@ msgstr "Unterrichtsstunden-Kalenderfarbe"
 msgid "Supervision calendar feed color"
 msgstr "Aufsichts-Kalenderfarbe"
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr "Sonntag"
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr "Montag"
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr "Dienstag"
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr "Donnerstag"
+
+#: aleksis/apps/chronos/preferences.py:139
+#, fuzzy
+#| msgid "Holiday"
+msgid "Friday"
+msgstr "Ferien"
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr "Samstag"
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr "Abwesende Lehrkräfte"
@@ -277,11 +365,8 @@ msgstr "Bereiche"
 #~ msgid "The validity range must be within the school term."
 #~ msgstr "Der Gültigkeitsbereich muss innerhalb des Schuljahres liegen."
 
-#~ msgid ""
-#~ "There is already a validity range for this time or a part of this time."
-#~ msgstr ""
-#~ "Es gibt bereits einen Gültigkeitsbereich für diesen Zeitraum oder einen "
-#~ "Teil diesen Zeitraumes."
+#~ msgid "There is already a validity range for this time or a part of this time."
+#~ msgstr "Es gibt bereits einen Gültigkeitsbereich für diesen Zeitraum oder einen Teil diesen Zeitraumes."
 
 #~ msgid "Validity range"
 #~ msgstr "Gültigkeitsbereich"
@@ -341,8 +426,7 @@ msgstr "Bereiche"
 #~ msgstr "Entfällt für Lehrkräfte?"
 
 #~ msgid "Lessons can only be either substituted or cancelled."
-#~ msgstr ""
-#~ "Unterrichtsstunden können nur entweder vertreten werden oder ausfallen."
+#~ msgstr "Unterrichtsstunden können nur entweder vertreten werden oder ausfallen."
 
 #~ msgid "Lesson substitution"
 #~ msgstr "Vertretung"
@@ -395,9 +479,6 @@ msgstr "Bereiche"
 #~ msgid "Comments"
 #~ msgstr "Kommentare"
 
-#~ msgid "Holiday"
-#~ msgstr "Ferien"
-
 #~ msgid "Holidays"
 #~ msgstr "Ferien"
 
@@ -456,61 +537,37 @@ msgstr "Bereiche"
 #~ msgid "Can view all lessons per day"
 #~ msgstr "Kann alle Tagesstunden sehen"
 
-#~ msgid "Can view all supervisions per day"
-#~ msgstr "Kann alle Tagesaufsichten sehen"
-
 #~ msgid "Shorten groups in timetable views"
 #~ msgstr "Gruppen in Stundenplanansichten kürzen"
 
 #~ msgid "If there are more groups than the set limit, they will be collapsed."
-#~ msgstr ""
-#~ "Wenn es mehr Gruppen als das gesetzte Limit gibt, werden die "
-#~ "Gruppenangaben gekürzt."
+#~ msgstr "Wenn es mehr Gruppen als das gesetzte Limit gibt, werden die Gruppenangaben gekürzt."
 
 #~ msgid "Limit of groups for shortening of groups"
 #~ msgstr "Anzahl der Gruppen, ab der gekürzt wird"
 
-#~ msgid ""
-#~ "If a user activates shortening of groups,they will be collapsed if there "
-#~ "are more groups than this limit."
-#~ msgstr ""
-#~ "Wenn Benutzer*innen die Kürzung von Gruppen aktiviert hat, werden sie ab "
-#~ "diesem Limit gekürzt."
+#~ msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgstr "Wenn Benutzer*innen die Kürzung von Gruppen aktiviert hat, werden sie ab diesem Limit gekürzt."
 
-#~ msgid ""
-#~ "How many days in advance users should be notified about timetable changes?"
-#~ msgstr ""
-#~ "Wie viele Tage im Voraus sollen Benutzer über Stundenplanänderungen "
-#~ "benachrichtigt werden?"
+#~ msgid "How many days in advance users should be notified about timetable changes?"
+#~ msgstr "Wie viele Tage im Voraus sollen Benutzer über Stundenplanänderungen benachrichtigt werden?"
 
 #~ msgid "Time for sending notifications about timetable changes"
-#~ msgstr ""
-#~ "Zeitpunkt zum Senden von Benachrichtigungen über Stundenplanänderungen"
+#~ msgstr "Zeitpunkt zum Senden von Benachrichtigungen über Stundenplanänderungen"
 
-#~ msgid ""
-#~ "This is only used for scheduling notifications which doesn't affect the "
-#~ "time period configured above. All other notifications affecting the next "
-#~ "days are sent immediately."
-#~ msgstr ""
-#~ "Dies wird nur benutzt, um Benachrichtigungen zu planen, welche nicht den "
-#~ "oben konfigurierten Zeitraum betreffen. Alle weiteren Benachrichtigungen, "
-#~ "die die nächsten Tage betreffen, werden sofort gesendet."
+#~ msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
+#~ msgstr "Dies wird nur benutzt, um Benachrichtigungen zu planen, welche nicht den oben konfigurierten Zeitraum betreffen. Alle weiteren Benachrichtigungen, die die nächsten Tage betreffen, werden sofort gesendet."
 
 #~ msgid "Send notifications for current timetable changes"
 #~ msgstr "Benachrichtigungen für aktuelle Stundenplanänderungen verschicken"
 
 #, python-brace-format
-#~ msgid ""
-#~ "The {subject} lesson in the {period}. period on {day} has been cancelled."
+#~ msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
 #~ msgstr "Die {subject}-Stunde in der {period}. Stunde am {day} fällt aus."
 
 #, python-brace-format
-#~ msgid ""
-#~ "The {subject} lesson in the {period}. period on {day} has some current "
-#~ "changes."
-#~ msgstr ""
-#~ "Die {subject}-Stunde in der {period}. Stunde am {day} hat aktuelle "
-#~ "Änderungen."
+#~ msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
+#~ msgstr "Die {subject}-Stunde in der {period}. Stunde am {day} hat aktuelle Änderungen."
 
 #, python-brace-format
 #~ msgid "The teacher {old} is substituted by {new}."
@@ -531,20 +588,12 @@ msgstr "Bereiche"
 #~ msgstr "Es gibt einen zusätzlichen Hinweis: {comment}."
 
 #, python-brace-format
-#~ msgid ""
-#~ "There is an event that starts on {date_start}, {period_from}. period and "
-#~ "ends on {date_end}, {period_to}. period:"
-#~ msgstr ""
-#~ "Es gibt eine Veranstaltung, die am {date_start} in der {period_from}. "
-#~ "Stunde startet und am {date_end} in der {period_to}. Stunde endet:"
+#~ msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
+#~ msgstr "Es gibt eine Veranstaltung, die am {date_start} in der {period_from}. Stunde startet und am {date_end} in der {period_to}. Stunde endet:"
 
 #, python-brace-format
-#~ msgid ""
-#~ "There is an event on {date} from the {period_from}. period to the "
-#~ "{period_to}. period:"
-#~ msgstr ""
-#~ "Es gibt eine Veranstaltung am {date} von der {period_from}. Stunde bis "
-#~ "zur {period_to}. Stunde:"
+#~ msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
+#~ msgstr "Es gibt eine Veranstaltung am {date} von der {period_from}. Stunde bis zur {period_to}. Stunde:"
 
 #, python-brace-format
 #~ msgid "Groups: {groups}"
@@ -575,12 +624,8 @@ msgstr "Bereiche"
 #~ msgstr "Kommentar: {comment}."
 
 #, python-brace-format
-#~ msgid ""
-#~ "The supervision of {old} on {date} between the {period_from}. period and "
-#~ "the {period_to}. period in the area {area} is substituted by {new}."
-#~ msgstr ""
-#~ "Die Aufsicht von {old} am {date} zwischen der {period_from}. Stunde un "
-#~ "der {period_to}. Stunde im Gebiet {area} wird von {new} vertreten."
+#~ msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
+#~ msgstr "Die Aufsicht von {old} am {date} zwischen der {period_from}. Stunde un der {period_to}. Stunde im Gebiet {area} wird von {new} vertreten."
 
 #~ msgid "Timetable"
 #~ msgstr "Stundenplan"
@@ -618,9 +663,6 @@ msgstr "Bereiche"
 #~ msgid "Substitution"
 #~ msgstr "Vertretung"
 
-#~ msgid "Manage substitution"
-#~ msgstr "Vertretung verwalten"
-
 #~ msgid "No teachers timetables available."
 #~ msgstr "Keine Stundenpläne von Lehrkräften vorhanden."
 
@@ -697,20 +739,11 @@ msgstr "Bereiche"
 #~ msgid "The substitution has been deleted."
 #~ msgstr "Die Vertretung wurde gelöscht."
 
-#~ msgid ""
-#~ "If an lesson or substitution has only one group and this group has parent "
-#~ "groups, show the parent groups instead of the original group."
-#~ msgstr ""
-#~ "Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe "
-#~ "Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen "
-#~ "Gruppe."
+#~ msgid "If an lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
+#~ msgstr "Wenn eine Stunde oder Vertretung nur eine Gruppe hat und diese Gruppe Elterngruppen hat, zeige die Elterngruppen anstelle der eigentlichen Gruppe."
 
-#~ msgid ""
-#~ "If an user activates shortening of groups,they will be collapsed if there "
-#~ "are more groups than this limit."
-#~ msgstr ""
-#~ "Wenn ein Benutzer die Kürzung von Gruppen aktiviert hat, werden sie ab "
-#~ "diesem Limit gekürzt."
+#~ msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
+#~ msgstr "Wenn ein Benutzer die Kürzung von Gruppen aktiviert hat, werden sie ab diesem Limit gekürzt."
 
 #~ msgid "View class register of the current week"
 #~ msgstr "Klassenbuch der aktuellen Woche anzeigen"
@@ -784,34 +817,12 @@ msgstr "Bereiche"
 #~ msgid "Effective end period of event"
 #~ msgstr "Effektive Endstunde der Veranstaltung"
 
-#~ msgid ""
-#~ "If there are more groups than this limit and CHRONOS_SHORTEN_GROUPS is "
-#~ "enabled, add text collapsible."
-#~ msgstr ""
-#~ "Wenn es mehr Gruppen als dieses Limit gibt und CHRONOS_SHORTEN_GROUPS "
-#~ "aktiviert ist, werden die Gruppenangaben gekürzt."
+#~ msgid "If there are more groups than this limit and CHRONOS_SHORTEN_GROUPS is enabled, add text collapsible."
+#~ msgstr "Wenn es mehr Gruppen als dieses Limit gibt und CHRONOS_SHORTEN_GROUPS aktiviert ist, werden die Gruppenangaben gekürzt."
 
 #~ msgid "Abbreviation of subject in timetable"
 #~ msgstr "Kürzel des Faches im Stundenplan"
 
-#~ msgid "Sunday"
-#~ msgstr "Sonntag"
-
-#~ msgid "Monday"
-#~ msgstr "Montag"
-
-#~ msgid "Tuesday"
-#~ msgstr "Dienstag"
-
-#~ msgid "Wednesday"
-#~ msgstr "Mittwoch"
-
-#~ msgid "Thursday"
-#~ msgstr "Donnerstag"
-
-#~ msgid "Saturday"
-#~ msgstr "Samstag"
-
 #~ msgid "Save"
 #~ msgstr "Speichern"
 
diff --git a/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
index cea5c852f6887401bcd06dc8ae9fb07cdb4b222d..b9def12e9b9cd647d3d85f92828114453926638b 100644
--- a/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: 2021-06-16 11:59+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: French <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/fr/>\n"
@@ -18,14 +18,34 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n > 1;\n"
 "X-Generator: Weblate 4.4\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+msgid "Can manage group substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr ""
 
+#: aleksis/apps/chronos/model_extensions.py:22
+msgid "Can view group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:26
+msgid "Can view person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:30
+msgid "Can view room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:34
+msgid "Can view course timetable"
+msgstr ""
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr ""
@@ -63,92 +83,116 @@ msgid "Can view all person timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:150
-msgid "Can view timetable overview"
+msgid "Can view all course timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:151
+msgid "Can view timetable overview"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+msgid "Can view all room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:154
+msgid "Can view all group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:155
+msgid "Can view all person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:156
+msgid "Can manage all substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr "Profs"
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr "Sujet"
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr "Cours"
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Event"
 msgstr "Cours"
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Events"
 msgstr "Cours"
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr ""
 
@@ -200,6 +244,38 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr ""
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+msgid "Friday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr ""
diff --git a/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
index 8d0616a26f634a0470c7980ef1529cd4a1642a10..26cde2d780c2ca05a4dbc69569871856efa6f8ce 100644
--- a/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/la/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: 2020-08-23 13:49+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/la/>\n"
@@ -18,14 +18,34 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 "X-Generator: Weblate 4.1.1\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+msgid "Can manage group substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr ""
 
+#: aleksis/apps/chronos/model_extensions.py:22
+msgid "Can view group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:26
+msgid "Can view person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:30
+msgid "Can view room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:34
+msgid "Can view course timetable"
+msgstr ""
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr ""
@@ -63,88 +83,112 @@ msgid "Can view all person timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:150
-msgid "Can view timetable overview"
+msgid "Can view all course timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:151
+msgid "Can view timetable overview"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+msgid "Can view all room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:154
+msgid "Can view all group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:155
+msgid "Can view all person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:156
+msgid "Can manage all substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr ""
 
@@ -196,6 +240,38 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr ""
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+msgid "Friday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr ""
diff --git a/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
index e7015c68a7c47522f5d628d897256ea935a950ed..fe29db94609091b882fc7088f35760d3e4af793a 100644
--- a/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/nb_NO/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,14 +17,34 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+msgid "Can manage group substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr ""
 
+#: aleksis/apps/chronos/model_extensions.py:22
+msgid "Can view group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:26
+msgid "Can view person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:30
+msgid "Can view room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:34
+msgid "Can view course timetable"
+msgstr ""
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr ""
@@ -62,88 +82,112 @@ msgid "Can view all person timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:150
-msgid "Can view timetable overview"
+msgid "Can view all course timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:151
+msgid "Can view timetable overview"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+msgid "Can view all room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:154
+msgid "Can view all group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:155
+msgid "Can view all person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:156
+msgid "Can manage all substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr ""
 
@@ -195,6 +239,38 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr ""
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+msgid "Friday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr ""
diff --git a/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
index 05b241d475490fd91d69b5e92ee3d1cf8a598a12..30010aa14a2911c05e72b66b40d920e43ab50bfb 100644
--- a/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/ru/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: 2023-05-26 04:38+0000\n"
 "Last-Translator: Serhii Horichenko <m@sgg.im>\n"
 "Language-Team: Russian <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/ru/>\n"
@@ -18,14 +18,44 @@ msgstr ""
 "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
 "X-Generator: Weblate 4.12.1\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr "Может просматривать групповые расписания"
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+#, fuzzy
+#| msgid "Manage substitution"
+msgid "Can manage group substitutions"
+msgstr "Управление заменами"
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr "Может просматривать личные расписания"
 
+#: aleksis/apps/chronos/model_extensions.py:22
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view group supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/model_extensions.py:26
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view person supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/model_extensions.py:30
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view room supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/model_extensions.py:34
+#, fuzzy
+#| msgid "Can view group timetable"
+msgid "Can view course timetable"
+msgstr "Может просматривать групповые расписания"
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr "Количество дней для отображения в плане"
@@ -63,98 +93,132 @@ msgid "Can view all person timetables"
 msgstr "Может просматривать расписания всех людей"
 
 #: aleksis/apps/chronos/models.py:150
+#, fuzzy
+#| msgid "Can view all group timetables"
+msgid "Can view all course timetables"
+msgstr "Может просматривать расписания всех групп"
+
+#: aleksis/apps/chronos/models.py:151
 msgid "Can view timetable overview"
 msgstr "Может просматривать общее расписание"
 
-#: aleksis/apps/chronos/models.py:151
+#: aleksis/apps/chronos/models.py:152
 #, fuzzy
 #| msgid "No substitutions available."
 msgid "Can view substitutions table"
 msgstr "Замены недоступны."
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view all room supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/models.py:154
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view all group supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/models.py:155
+#, fuzzy
+#| msgid "Can view all lessons per day"
+msgid "Can view all person supervisions"
+msgstr "Может просматривать все уроки за день"
+
+#: aleksis/apps/chronos/models.py:156
+#, fuzzy
+#| msgid "Manage substitution"
+msgid "Can manage all substitutions"
+msgstr "Управление заменами"
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr "Полное имя"
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 #, fuzzy
 #| msgid "Start time"
 msgid "Start slot number"
 msgstr "Время начала"
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 #, fuzzy
 #| msgid "End time"
 msgid "End slot number"
 msgstr "Время окончания"
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr "Группы"
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr "Комнаты"
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr "Преподаватели"
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr "Предмет"
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr "Отменено"
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr "Коментарий"
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 #, fuzzy
 #| msgid "Lesson"
 msgid "Lesson Event"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 #, fuzzy
 #| msgid "Lessons"
 msgid "Lesson Events"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr "Контроли"
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 #, fuzzy
 #| msgid "Supervision"
 msgid "Supervision: {}"
@@ -216,6 +280,40 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr "Зона контроля"
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+#, fuzzy
+#| msgid "Holiday"
+msgid "Friday"
+msgstr "Выходной"
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr "Отсутствующие преподаватели"
@@ -402,9 +500,6 @@ msgstr ""
 #~ msgid "Comments"
 #~ msgstr "Комментарии"
 
-#~ msgid "Holiday"
-#~ msgstr "Выходной"
-
 #~ msgid "Holidays"
 #~ msgstr "Выходные"
 
@@ -463,11 +558,6 @@ msgstr ""
 #~ msgid "Can view all lessons per day"
 #~ msgstr "Может просматривать все уроки за день"
 
-#, fuzzy
-#~| msgid "Can view all lessons per day"
-#~ msgid "Can view all supervisions per day"
-#~ msgstr "Может просматривать все уроки за день"
-
 #~ msgid "Shorten groups in timetable views"
 #~ msgstr "Сокращать группы в обзорах расписания"
 
@@ -598,9 +688,6 @@ msgstr ""
 #~ msgid "Substitution"
 #~ msgstr "Замена"
 
-#~ msgid "Manage substitution"
-#~ msgstr "Управление заменами"
-
 #~ msgid "No teachers timetables available."
 #~ msgstr "Расписания преподавателей недоступны."
 
diff --git a/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
index e7015c68a7c47522f5d628d897256ea935a950ed..fe29db94609091b882fc7088f35760d3e4af793a 100644
--- a/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/tr_TR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,14 +17,34 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
 msgid "Can view group timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:14
+msgid "Can manage group substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:18
 msgid "Can view person timetable"
 msgstr ""
 
+#: aleksis/apps/chronos/model_extensions.py:22
+msgid "Can view group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:26
+msgid "Can view person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:30
+msgid "Can view room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/model_extensions.py:34
+msgid "Can view course timetable"
+msgstr ""
+
 #: aleksis/apps/chronos/models.py:48
 msgid "Number of days shown in the plan"
 msgstr ""
@@ -62,88 +82,112 @@ msgid "Can view all person timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:150
-msgid "Can view timetable overview"
+msgid "Can view all course timetables"
 msgstr ""
 
 #: aleksis/apps/chronos/models.py:151
+msgid "Can view timetable overview"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:152
 msgid "Can view substitutions table"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+msgid "Can view all room supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:154
+msgid "Can view all group supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:155
+msgid "Can view all person supervisions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:156
+msgid "Can manage all substitutions"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:164
 msgid "Lessons"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168
 msgid "Name"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:166
+#: aleksis/apps/chronos/models.py:171
 msgid "Start slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:169
+#: aleksis/apps/chronos/models.py:174
 msgid "End slot number"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:178
 msgid "Course"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
 msgid "Groups"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
 msgid "Rooms"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
 msgid "Teachers"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
 msgid "Subject"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
 msgid "Cancelled"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215
 msgid "Comment"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
+msgstr ""
+
+#: aleksis/apps/chronos/models.py:262 aleksis/apps/chronos/models.py:274
+#: aleksis/apps/chronos/models.py:286
 msgid "{} (instead of {})"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
 msgid "Lesson"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:467
+#: aleksis/apps/chronos/models.py:630
 msgid "Lesson Event"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:468
+#: aleksis/apps/chronos/models.py:631
 msgid "Lesson Events"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638
 msgid "Supervisions"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:483
+#: aleksis/apps/chronos/models.py:646
 msgid "Supervision: {}"
 msgstr ""
 
@@ -195,6 +239,38 @@ msgstr ""
 msgid "Supervision calendar feed color"
 msgstr ""
 
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
+msgid "Friday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
+
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
 msgstr ""
diff --git a/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po b/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
index 37c39e991658af7a313c6d082abb5bad239f057c..84a66bce3d50f329a5254a4f7e4409a39b97e179 100644
--- a/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
+++ b/aleksis/apps/chronos/locale/uk/LC_MESSAGES/django.po
@@ -7,158 +7,226 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-17 10:48+0200\n"
+"POT-Creation-Date: 2024-12-08 13:24+0100\n"
 "PO-Revision-Date: 2022-06-22 19:59+0000\n"
 "Last-Translator: Serhii Horichenko <m@sgg.im>\n"
-"Language-Team: Ukrainian <https://translate.edugit.org/projects/aleksis/aleksis-app-chronos/uk/>\n"
+"Language-Team: Ukrainian <https://translate.edugit.org/projects/aleksis/"
+"aleksis-app-chronos/uk/>\n"
 "Language: uk\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != "
+"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % "
+"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || "
+"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
 "X-Generator: Weblate 4.12.1\n"
 
-#: aleksis/apps/chronos/model_extensions.py:9
+#: aleksis/apps/chronos/model_extensions.py:10
+#: aleksis/apps/chronos/model_extensions.py:145
 msgid "Can view group timetable"
-msgstr "Може переглядати групові розклади"
+msgstr "Може бачити групові розклади"
+
+#: aleksis/apps/chronos/model_extensions.py:14
+#, fuzzy
+msgid "Can manage group substitutions"
+msgstr "Керування замінами"
 
-#: aleksis/apps/chronos/model_extensions.py:13
+#: aleksis/apps/chronos/model_extensions.py:18
+#: aleksis/apps/chronos/model_extensions.py:149
 msgid "Can view person timetable"
-msgstr "Може переглядати особисті розклади"
+msgstr "Може бачити особисті розклади"
+
+#: aleksis/apps/chronos/model_extensions.py:22
+#, fuzzy
+msgid "Can view group supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
+
+#: aleksis/apps/chronos/model_extensions.py:26
+#, fuzzy
+msgid "Can view person supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
 
-#: aleksis/apps/chronos/models.py:48
+#: aleksis/apps/chronos/model_extensions.py:30
+#, fuzzy
+msgid "Can view room supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
+
+#: aleksis/apps/chronos/model_extensions.py:34
+#, fuzzy
+msgid "Can view course timetable"
+msgstr "Може переглядати групові розклади"
+
+#: aleksis/apps/chronos/models.py:48 aleksis/apps/chronos/models.py:1194
 msgid "Number of days shown in the plan"
 msgstr "Кількість днів для відображення у плані"
 
-#: aleksis/apps/chronos/models.py:52
+#: aleksis/apps/chronos/models.py:52 aleksis/apps/chronos/models.py:1198
 msgid "Show header box"
 msgstr "Показати заголовок"
 
 #: aleksis/apps/chronos/models.py:53 aleksis/apps/chronos/preferences.py:79
+#: aleksis/apps/chronos/models.py:1199 aleksis/apps/chronos/preferences.py:100
 msgid "The header box shows affected teachers/groups."
 msgstr "Заголовок показує викладачів/групи на яких це впливає."
 
-#: aleksis/apps/chronos/models.py:60
+#: aleksis/apps/chronos/models.py:60 aleksis/apps/chronos/models.py:1206
 msgid "Revision which triggered the last update"
 msgstr "Ревізія, яка запустила останнє оновлення"
 
-#: aleksis/apps/chronos/models.py:132
+#: aleksis/apps/chronos/models.py:132 aleksis/apps/chronos/models.py:1278
 msgid "Automatic plan"
 msgstr "Автоматичний план"
 
-#: aleksis/apps/chronos/models.py:133
+#: aleksis/apps/chronos/models.py:133 aleksis/apps/chronos/models.py:1279
 msgid "Automatic plans"
 msgstr "Автоматичні плани"
 
-#: aleksis/apps/chronos/models.py:147
+#: aleksis/apps/chronos/models.py:147 aleksis/apps/chronos/models.py:1293
 msgid "Can view all room timetables"
-msgstr "Може переглядати розклади усіх кімнат"
+msgstr "Може бачити розклади усіх кімнат"
 
-#: aleksis/apps/chronos/models.py:148
+#: aleksis/apps/chronos/models.py:148 aleksis/apps/chronos/models.py:1294
 msgid "Can view all group timetables"
-msgstr "Може переглядати розклади усіх груп"
+msgstr "Може бачити розклади усіх груп"
 
-#: aleksis/apps/chronos/models.py:149
+#: aleksis/apps/chronos/models.py:149 aleksis/apps/chronos/models.py:1295
 msgid "Can view all person timetables"
-msgstr "Може переглядати розклади усіх осіб"
+msgstr "Може бачити розклади усіх осіб"
 
 #: aleksis/apps/chronos/models.py:150
+#, fuzzy
+msgid "Can view all course timetables"
+msgstr "Може переглядати розклади усіх груп"
+
+#: aleksis/apps/chronos/models.py:151 aleksis/apps/chronos/models.py:1296
 msgid "Can view timetable overview"
-msgstr "Може переглядати загальний розклад"
+msgstr "Може бачити загальний розклад"
 
-#: aleksis/apps/chronos/models.py:151
+#: aleksis/apps/chronos/models.py:152
 #, fuzzy
-#| msgid "No substitutions available."
 msgid "Can view substitutions table"
 msgstr "Заміни недоступні."
 
-#: aleksis/apps/chronos/models.py:159
+#: aleksis/apps/chronos/models.py:153
+#, fuzzy
+msgid "Can view all room supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
+
+#: aleksis/apps/chronos/models.py:154
+#, fuzzy
+msgid "Can view all group supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
+
+#: aleksis/apps/chronos/models.py:155
+#, fuzzy
+msgid "Can view all person supervisions"
+msgstr "Може переглядати усі уроки окремого дня"
+
+#: aleksis/apps/chronos/models.py:156
+#, fuzzy
+msgid "Can manage all substitutions"
+msgstr "Керування замінами"
+
+#: aleksis/apps/chronos/models.py:164 aleksis/apps/chronos/models.py:395
+#: aleksis/apps/chronos/models.py:1306
 msgid "Lessons"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:163
+#: aleksis/apps/chronos/models.py:168 aleksis/apps/chronos/models.py:90
+#: aleksis/apps/chronos/models.py:626 aleksis/apps/chronos/models.py:1310
 msgid "Name"
 msgstr "Повне ім'я"
 
-#: aleksis/apps/chronos/models.py:166
-#, fuzzy
-#| msgid "Start time"
+#: aleksis/apps/chronos/models.py:1313
 msgid "Start slot number"
-msgstr "Час початку"
+msgstr "Номер першого слота"
 
-#: aleksis/apps/chronos/models.py:169
-#, fuzzy
-#| msgid "End time"
+#: aleksis/apps/chronos/models.py:1316
 msgid "End slot number"
-msgstr "Час закінчення"
+msgstr "Номер останнього слота"
 
-#: aleksis/apps/chronos/models.py:173
+#: aleksis/apps/chronos/models.py:1320
 msgid "Course"
-msgstr ""
+msgstr "Курс"
 
-#: aleksis/apps/chronos/models.py:179
+#: aleksis/apps/chronos/models.py:184
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:1
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:26
+#: aleksis/apps/chronos/models.py:360 aleksis/apps/chronos/models.py:995
+#: aleksis/apps/chronos/models.py:1132 aleksis/apps/chronos/models.py:1326
 msgid "Groups"
 msgstr "Групи"
 
-#: aleksis/apps/chronos/models.py:185
+#: aleksis/apps/chronos/models.py:190
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:4
+#: aleksis/apps/chronos/models.py:996 aleksis/apps/chronos/models.py:1332
 msgid "Rooms"
 msgstr "Кімнати"
 
-#: aleksis/apps/chronos/models.py:191
+#: aleksis/apps/chronos/models.py:196
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:3
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:28
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:1
+#: aleksis/apps/chronos/models.py:352 aleksis/apps/chronos/models.py:422
+#: aleksis/apps/chronos/models.py:998 aleksis/apps/chronos/models.py:1137
+#: aleksis/apps/chronos/models.py:1338
 msgid "Teachers"
 msgstr "Викладачі"
 
-#: aleksis/apps/chronos/models.py:198
+#: aleksis/apps/chronos/models.py:203
 #: aleksis/apps/chronos/templates/chronos/lesson_event_description.txt:2
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:29
+#: aleksis/apps/chronos/models.py:340 aleksis/apps/chronos/models.py:349
+#: aleksis/apps/chronos/models.py:416 aleksis/apps/chronos/models.py:1129
+#: aleksis/apps/chronos/models.py:1345
 msgid "Subject"
 msgstr "Предмет"
 
-#: aleksis/apps/chronos/models.py:206
+#: aleksis/apps/chronos/models.py:211
 #: aleksis/apps/chronos/templates/chronos/partials/subs/badge.html:4
+#: aleksis/apps/chronos/models.py:1353
 msgid "Cancelled"
 msgstr "Скасовано"
 
-#: aleksis/apps/chronos/models.py:210
+#: aleksis/apps/chronos/models.py:215 aleksis/apps/chronos/models.py:433
+#: aleksis/apps/chronos/models.py:692 aleksis/apps/chronos/models.py:734
+#: aleksis/apps/chronos/models.py:1147 aleksis/apps/chronos/models.py:1357
 msgid "Comment"
 msgstr "Коментар"
 
-#: aleksis/apps/chronos/models.py:254 aleksis/apps/chronos/models.py:266
-#: aleksis/apps/chronos/models.py:278
-msgid "{} (instead of {})"
+#: aleksis/apps/chronos/models.py:220
+msgid "Is this a current change?"
 msgstr ""
 
-#: aleksis/apps/chronos/models.py:283 aleksis/apps/chronos/models.py:307
+#: aleksis/apps/chronos/models.py:1401 aleksis/apps/chronos/models.py:1413
+#: aleksis/apps/chronos/models.py:1425
+msgid "{} (instead of {})"
+msgstr "{} (замість {})"
+
+#: aleksis/apps/chronos/models.py:291 aleksis/apps/chronos/models.py:315
+#: aleksis/apps/chronos/models.py:394 aleksis/apps/chronos/models.py:490
+#: aleksis/apps/chronos/models.py:716 aleksis/apps/chronos/models.py:1430
+#: aleksis/apps/chronos/models.py:1454
 msgid "Lesson"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:467
-#, fuzzy
-#| msgid "Lesson"
+#: aleksis/apps/chronos/models.py:1614
 msgid "Lesson Event"
 msgstr "Урок"
 
-#: aleksis/apps/chronos/models.py:468
-#, fuzzy
-#| msgid "Lessons"
+#: aleksis/apps/chronos/models.py:1615
 msgid "Lesson Events"
 msgstr "Уроки"
 
-#: aleksis/apps/chronos/models.py:475
+#: aleksis/apps/chronos/models.py:638 aleksis/apps/chronos/models.py:924
+#: aleksis/apps/chronos/models.py:1622
 msgid "Supervisions"
-msgstr "Контролі"
+msgstr "Наглядачі"
 
-#: aleksis/apps/chronos/models.py:483
-#, fuzzy
-#| msgid "Supervision"
+#: aleksis/apps/chronos/models.py:1630
 msgid "Supervision: {}"
-msgstr "Контроль"
+msgstr "Контроль: {}"
 
 #: aleksis/apps/chronos/preferences.py:21
 msgid "Timetables"
@@ -169,52 +237,87 @@ msgid "Use parent groups in timetable views"
 msgstr "Використовувати батьківські групи в огляді розкладу"
 
 #: aleksis/apps/chronos/preferences.py:31
-#, fuzzy
-#| msgid "If an lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
-msgid "If a lesson or substitution has only one group and this group has parent groups, show the parent groups instead of the original group."
-msgstr "Якщо урок або заміна має лише одну групу і ця група має батьківські групи, відображати ці батьківські групи замість дійсної групи."
+msgid ""
+"If a lesson or substitution has only one group and this group has parent "
+"groups, show the parent groups instead of the original group."
+msgstr ""
+"Якщо урок або заміна має лише одну групу і ця група має батьківські групи, "
+"відображати ці батьківські групи замість дійсної групи."
 
-#: aleksis/apps/chronos/preferences.py:44
-#, fuzzy
-#| msgid "Lesson substitutions"
+#: aleksis/apps/chronos/preferences.py:65
 msgid "Relevant days for substitution plans"
-msgstr "Заміни уроків"
+msgstr "Відповідні дні для планів заміни"
 
-#: aleksis/apps/chronos/preferences.py:61
+#: aleksis/apps/chronos/preferences.py:82
 msgid "Time when substitution plans switch to the next day"
-msgstr ""
+msgstr "Час перемикання планів замін на наступний день"
 
 #: aleksis/apps/chronos/preferences.py:70
+#: aleksis/apps/chronos/preferences.py:91
 msgid "Number of days shown on substitutions print view"
 msgstr "Кількість днів для відображення у друкованому вигляді замін"
 
 #: aleksis/apps/chronos/preferences.py:78
+#: aleksis/apps/chronos/preferences.py:99
 msgid "Show header box in substitution views"
 msgstr "Відображати заголовок в огляді замін"
 
 #: aleksis/apps/chronos/preferences.py:88
-msgid "Show parent groups in header box in substitution views instead of original groups"
-msgstr "У заголовку в огляді замін відображати батьківські групи замість дійсних груп"
+#: aleksis/apps/chronos/preferences.py:109
+msgid ""
+"Show parent groups in header box in substitution views instead of original "
+"groups"
+msgstr ""
+"У заголовку в огляді замін відображати батьківські групи замість дійсних груп"
 
-#: aleksis/apps/chronos/preferences.py:99
-#, fuzzy
-#| msgid "Shorten groups in timetable views"
+#: aleksis/apps/chronos/preferences.py:158
 msgid "Group types to show in timetables"
-msgstr "Скорочувати групи в огляді розкладу"
+msgstr "Типи груп для зображення в розкладах"
 
-#: aleksis/apps/chronos/preferences.py:100
+#: aleksis/apps/chronos/preferences.py:159
 msgid "If you leave it empty, all groups will be shown."
-msgstr ""
+msgstr "Якщо залишити порожнім, будуть показані усі групи."
 
-#: aleksis/apps/chronos/preferences.py:110
+#: aleksis/apps/chronos/preferences.py:169
 msgid "Lesson calendar feed color"
+msgstr "Колір календаря для уроків"
+
+#: aleksis/apps/chronos/preferences.py:181
+msgid "Supervision calendar feed color"
+msgstr "Колір календаря для нагляду"
+
+#: aleksis/apps/chronos/preferences.py:132
+msgid "Days of the week that appear in the timetable"
 msgstr ""
 
-#: aleksis/apps/chronos/preferences.py:122
+#: aleksis/apps/chronos/preferences.py:134
+msgid "Sunday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:135
+msgid "Monday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:136
+msgid "Tuesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:137
+msgid "Wednesday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:138
+msgid "Thursday"
+msgstr ""
+
+#: aleksis/apps/chronos/preferences.py:139
 #, fuzzy
-#| msgid "Supervision area"
-msgid "Supervision calendar feed color"
-msgstr "Зона контролю"
+msgid "Friday"
+msgstr "Вихідний"
+
+#: aleksis/apps/chronos/preferences.py:140
+msgid "Saturday"
+msgstr ""
 
 #: aleksis/apps/chronos/templates/chronos/partials/headerbox.html:10
 msgid "Absent teachers"
@@ -234,9 +337,10 @@ msgstr "На які групи впливає"
 
 #: aleksis/apps/chronos/templates/chronos/partials/subs/period.html:12
 msgid "all day"
-msgstr ""
+msgstr "увесь день"
 
 #: aleksis/apps/chronos/templates/chronos/partials/subs/subject.html:3
+#: aleksis/apps/chronos/models.py:923 aleksis/apps/chronos/models.py:936
 msgid "Supervision"
 msgstr "Контроль"
 
@@ -249,12 +353,12 @@ msgid "Substitutions"
 msgstr "Заміни"
 
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:27
-#, fuzzy
-#| msgid "Timetable"
 msgid "Time"
-msgstr "Розклад"
+msgstr "Час"
 
 #: aleksis/apps/chronos/templates/chronos/substitutions_print.html:30
+#: aleksis/apps/chronos/models.py:425 aleksis/apps/chronos/models.py:504
+#: aleksis/apps/chronos/models.py:673 aleksis/apps/chronos/models.py:1144
 msgid "Room"
 msgstr "Кімната"
 
@@ -268,305 +372,428 @@ msgstr "Заміни недоступні."
 
 #: aleksis/apps/chronos/templates/chronos/supervision_event_description.txt:2
 msgid "Areas"
-msgstr ""
+msgstr "Області"
 
-#~ msgid "Linked validity range"
-#~ msgstr "Пов'язаний діапазон"
+#: aleksis/apps/chronos/mixins.py:25
+msgid "Linked validity range"
+msgstr "Пов'язана область дії"
 
-#~ msgid "School term"
-#~ msgstr "Навчальний рік"
+#: aleksis/apps/chronos/models.py:87
+msgid "School term"
+msgstr "Навчальний рік"
 
-#~ msgid "Start date"
-#~ msgstr "Дата початку"
+#: aleksis/apps/chronos/models.py:92 aleksis/apps/chronos/models.py:676
+#: aleksis/apps/chronos/models.py:747 aleksis/apps/chronos/models.py:979
+msgid "Start date"
+msgstr "Дата початку"
 
-#~ msgid "End date"
-#~ msgstr "Дата закінчення"
+#: aleksis/apps/chronos/models.py:93 aleksis/apps/chronos/models.py:677
+#: aleksis/apps/chronos/models.py:748 aleksis/apps/chronos/models.py:980
+msgid "End date"
+msgstr "Дата закінчення"
 
-#~ msgid "The start date must be earlier than the end date."
-#~ msgstr "Початкова дата повинна бути раніше кінцевої."
+#: aleksis/apps/chronos/models.py:112
+msgid "The start date must be earlier than the end date."
+msgstr "Початкова дата повинна бути раніше кінцевої дати."
 
-#~ msgid "The validity range must be within the school term."
-#~ msgstr "Контрольний період повинен бути в рамках навчального року."
+#: aleksis/apps/chronos/models.py:118
+msgid "The validity range must be within the school term."
+msgstr "Область дії повинна бути в межах навчального року."
 
-#~ msgid "There is already a validity range for this time or a part of this time."
-#~ msgstr "На цей час або на частину цього часу контрольний період вже призначений."
+#: aleksis/apps/chronos/models.py:125
+msgid "There is already a validity range for this time or a part of this time."
+msgstr "На цей час або на частину цього часу область дії вже призначена."
 
-#~ msgid "Validity range"
-#~ msgstr "Контрольний період"
-
-#~ msgid "Validity ranges"
-#~ msgstr "Контрольні періоди"
+#: aleksis/apps/chronos/models.py:132
+msgid "Validity range"
+msgstr "Область дії"
 
-#~ msgid "Week day"
-#~ msgstr "День тижня"
+#: aleksis/apps/chronos/models.py:133
+msgid "Validity ranges"
+msgstr "Області дії"
 
-#~ msgid "Number of period"
-#~ msgstr "Номер урока"
+#: aleksis/apps/chronos/models.py:149
+msgid "Week day"
+msgstr "День тижня"
 
-#~ msgid "Start time"
-#~ msgstr "Час початку"
+#: aleksis/apps/chronos/models.py:151
+msgid "Number of period"
+msgstr "Номер уроку"
 
-#~ msgid "End time"
-#~ msgstr "Час закінчення"
+#: aleksis/apps/chronos/models.py:153
+msgid "Start time"
+msgstr "Час початку"
 
-#~ msgid "Time period"
-#~ msgstr "Час уроку"
+#: aleksis/apps/chronos/models.py:154
+msgid "End time"
+msgstr "Час закінчення"
 
-#~ msgid "Time periods"
-#~ msgstr "Час уроків"
+#: aleksis/apps/chronos/models.py:324 aleksis/apps/chronos/models.py:496
+#: aleksis/apps/chronos/models.py:1122
+msgid "Time period"
+msgstr "Час уроку"
 
-#~ msgid "Short name"
-#~ msgstr "Коротке ім'я"
+#: aleksis/apps/chronos/models.py:325
+msgid "Time periods"
+msgstr "Час уроків"
 
-#~ msgid "Long name"
-#~ msgstr "Довге ім'я"
+#: aleksis/apps/chronos/models.py:329 aleksis/apps/chronos/models.py:625
+#: aleksis/apps/chronos/models.py:793 aleksis/apps/chronos/models.py:810
+msgid "Short name"
+msgstr "Коротке ім'я"
 
-#~ msgid "Foreground colour"
-#~ msgstr "Основний колір"
+#: aleksis/apps/chronos/models.py:330 aleksis/apps/chronos/models.py:794
+#: aleksis/apps/chronos/models.py:811
+msgid "Long name"
+msgstr "Повна назва"
 
-#~ msgid "Background colour"
-#~ msgstr "Колір фону"
+#: aleksis/apps/chronos/models.py:332
+msgid "Foreground colour"
+msgstr "Колір переднього плану"
 
-#~ msgid "Subjects"
-#~ msgstr "Предмети"
+#: aleksis/apps/chronos/models.py:333
+msgid "Background colour"
+msgstr "Колір фону"
 
-#~ msgid "Periods"
-#~ msgstr "Години"
+#: aleksis/apps/chronos/models.py:341
+msgid "Subjects"
+msgstr "Предмети"
 
-#~ msgid "Week"
-#~ msgstr "Тиждень"
+#: aleksis/apps/chronos/models.py:358
+msgid "Periods"
+msgstr "Години"
 
-#~ msgid "Year"
-#~ msgstr "Рік"
+#: aleksis/apps/chronos/models.py:403 aleksis/apps/chronos/models.py:1116
+msgid "Week"
+msgstr "Тиждень"
 
-#~ msgid "Lesson period"
-#~ msgstr "Урок за розкладом"
+#: aleksis/apps/chronos/models.py:404 aleksis/apps/chronos/models.py:1117
+msgid "Year"
+msgstr "Рік"
 
-#~ msgid "Cancelled?"
-#~ msgstr "Скасовано?"
+#: aleksis/apps/chronos/models.py:407 aleksis/apps/chronos/models.py:620
+msgid "Lesson period"
+msgstr "Урок за розкладом"
 
-#~ msgid "Cancelled for teachers?"
-#~ msgstr "Скасовано для викладачів?"
+#: aleksis/apps/chronos/models.py:428
+msgid "Cancelled?"
+msgstr "Скасовано?"
 
-#~ msgid "Lessons can only be either substituted or cancelled."
-#~ msgstr "Уроки можуть бути лише або замінені, або скасовані."
+#: aleksis/apps/chronos/models.py:430
+msgid "Cancelled for teachers?"
+msgstr "Скасовано для викладачів?"
 
-#~ msgid "Lesson substitution"
-#~ msgstr "Заміна уроку"
+#: aleksis/apps/chronos/models.py:437
+msgid "Lessons can only be either substituted or cancelled."
+msgstr "Уроки можуть бути лише або замінені, або скасовані."
 
-#~ msgid "Lesson substitutions"
-#~ msgstr "Заміни уроків"
+#: aleksis/apps/chronos/models.py:477
+msgid "Lesson substitution"
+msgstr "Заміна уроку"
 
-#~ msgid "Lesson periods"
-#~ msgstr "Навчальні години"
+#: aleksis/apps/chronos/models.py:478
+msgid "Lesson substitutions"
+msgstr "Заміни уроків"
 
-#~ msgid "Absence reason"
-#~ msgstr "Причина відсутності"
+#: aleksis/apps/chronos/models.py:621
+msgid "Lesson periods"
+msgstr "Навчальні години"
 
-#~ msgid "Absence reasons"
-#~ msgstr "Причини відсутності"
+#: aleksis/apps/chronos/models.py:635 aleksis/apps/chronos/models.py:648
+msgid "Absence reason"
+msgstr "Причина відсутності"
 
-#~ msgid "Teacher"
-#~ msgstr "Викладач"
+#: aleksis/apps/chronos/models.py:636
+msgid "Absence reasons"
+msgstr "Причини відсутності"
 
-#~ msgid "Group"
-#~ msgstr "Група"
+#: aleksis/apps/chronos/models.py:657 aleksis/apps/chronos/models.py:889
+#: aleksis/apps/chronos/models.py:943
+msgid "Teacher"
+msgstr "Викладач"
 
-#~ msgid "Start period"
-#~ msgstr "Початок уроків"
+#: aleksis/apps/chronos/models.py:665
+msgid "Group"
+msgstr "Група"
 
-#~ msgid "End period"
-#~ msgstr "Закінчення уроків"
+#: aleksis/apps/chronos/models.py:681 aleksis/apps/chronos/models.py:723
+msgid "Start period"
+msgstr "Початок уроків"
 
-#~ msgid "Unknown absence"
-#~ msgstr "Відсутність без пояснень"
+#: aleksis/apps/chronos/models.py:688 aleksis/apps/chronos/models.py:729
+msgid "End period"
+msgstr "Закінчення уроків"
 
-#~ msgid "Absence"
-#~ msgstr "Відсутність"
+#: aleksis/apps/chronos/models.py:702
+msgid "Unknown absence"
+msgstr "Відсутність без пояснень"
 
-#~ msgid "Absences"
-#~ msgstr "Відсутності"
+#: aleksis/apps/chronos/models.py:707
+msgid "Absence"
+msgstr "Відсутність"
 
-#~ msgid "Date of exam"
-#~ msgstr "Дата іспиту"
+#: aleksis/apps/chronos/models.py:708
+msgid "Absences"
+msgstr "Відсутності"
 
-#~ msgid "Title"
-#~ msgstr "Назва"
+#: aleksis/apps/chronos/models.py:719
+msgid "Date of exam"
+msgstr "Дата іспиту"
 
-#~ msgid "Exam"
-#~ msgstr "Іспит"
+#: aleksis/apps/chronos/models.py:733 aleksis/apps/chronos/models.py:746
+#: aleksis/apps/chronos/models.py:977
+msgid "Title"
+msgstr "Назва"
 
-#~ msgid "Exams"
-#~ msgstr "Іспити"
+#: aleksis/apps/chronos/models.py:739
+msgid "Exam"
+msgstr "Іспит"
 
-#~ msgid "Comments"
-#~ msgstr "Коментарі"
+#: aleksis/apps/chronos/models.py:740
+msgid "Exams"
+msgstr "Іспити"
 
-#~ msgid "Holiday"
-#~ msgstr "Вихідний"
+#: aleksis/apps/chronos/models.py:749
+msgid "Comments"
+msgstr "Коментарі"
 
-#~ msgid "Holidays"
-#~ msgstr "Вихідні"
+#: aleksis/apps/chronos/models.py:789
+msgid "Holidays"
+msgstr "Вихідні"
 
-#~ msgid "Supervision area"
-#~ msgstr "Зона контролю"
+#: aleksis/apps/chronos/models.py:803 aleksis/apps/chronos/models.py:879
+msgid "Supervision area"
+msgstr "Зона контролю"
 
-#~ msgid "Supervision areas"
-#~ msgstr "Зони контролю"
+#: aleksis/apps/chronos/models.py:804
+msgid "Supervision areas"
+msgstr "Зони контролю"
 
-#~ msgid "Time period after break starts"
-#~ msgstr "Навчання після перерви починається"
+#: aleksis/apps/chronos/models.py:816
+msgid "Time period after break starts"
+msgstr "Навчання після перерви починається"
 
-#~ msgid "Time period before break ends"
-#~ msgstr "Навчання перед перервою закінчується"
+#: aleksis/apps/chronos/models.py:824
+msgid "Time period before break ends"
+msgstr "Навчання перед перервою закінчується"
 
-#~ msgid "Break"
-#~ msgstr "Перерва"
+#: aleksis/apps/chronos/models.py:864 aleksis/apps/chronos/models.py:883
+msgid "Break"
+msgstr "Перерва"
 
-#~ msgid "Breaks"
-#~ msgstr "Перерви"
+#: aleksis/apps/chronos/models.py:865
+msgid "Breaks"
+msgstr "Перерви"
 
-#~ msgid "Date"
-#~ msgstr "Дата"
+#: aleksis/apps/chronos/models.py:932
+msgid "Date"
+msgstr "Дата"
 
-#~ msgid "Supervision substitution"
-#~ msgstr "Заміна контролю"
+#: aleksis/apps/chronos/models.py:966
+msgid "Supervision substitution"
+msgstr "Заміна контролю"
 
-#~ msgid "Supervision substitutions"
-#~ msgstr "Заміни контролю"
+#: aleksis/apps/chronos/models.py:967
+msgid "Supervision substitutions"
+msgstr "Заміни контролю"
 
-#~ msgid "Start time period"
-#~ msgstr "Початок навчання"
+#: aleksis/apps/chronos/models.py:985
+msgid "Start time period"
+msgstr "Початок навчання"
 
-#~ msgid "End time period"
-#~ msgstr "Закінчення навчання"
+#: aleksis/apps/chronos/models.py:991
+msgid "End time period"
+msgstr "Закінчення навчання"
 
+#: aleksis/apps/chronos/models.py:1005
 #, python-brace-format
-#~ msgid "Event {pk}"
-#~ msgstr "Подія {pk}"
+msgid "Event {pk}"
+msgstr "Подія {pk}"
 
-#~ msgid "Event"
-#~ msgstr "Подія"
+#: aleksis/apps/chronos/models.py:1103
+msgid "Event"
+msgstr "Подія"
 
-#~ msgid "Events"
-#~ msgstr "Події"
+#: aleksis/apps/chronos/models.py:1104
+msgid "Events"
+msgstr "Події"
 
-#~ msgid "Related exam"
-#~ msgstr "Пов'язані іспити"
+#: aleksis/apps/chronos/models.py:1152
+msgid "Related exam"
+msgstr "Пов'язані іспити"
 
-#~ msgid "Extra lesson"
-#~ msgstr "Додатковий урок"
+#: aleksis/apps/chronos/models.py:1181
+msgid "Extra lesson"
+msgstr "Додатковий урок"
 
-#~ msgid "Extra lessons"
-#~ msgstr "Додаткові уроки"
+#: aleksis/apps/chronos/models.py:1182
+msgid "Extra lessons"
+msgstr "Додаткові уроки"
 
-#~ msgid "Can view all lessons per day"
-#~ msgstr "Може переглядати усі уроки окремого дня"
+#: aleksis/apps/chronos/models.py:1297
+msgid "Can view all lessons per day"
+msgstr "Може бачити усі уроки окремого дня"
 
-#, fuzzy
-#~| msgid "Can view all lessons per day"
-#~ msgid "Can view all supervisions per day"
-#~ msgstr "Може переглядати усі уроки окремого дня"
-
-#~ msgid "Shorten groups in timetable views"
-#~ msgstr "Скорочувати групи в огляді розкладу"
+#: aleksis/apps/chronos/preferences.py:42
+msgid "Shorten groups in timetable views"
+msgstr "Скорочувати групи в огляді розкладу"
 
-#~ msgid "If there are more groups than the set limit, they will be collapsed."
-#~ msgstr "Якщо груп більше за встановлений ліміт, вони будуть згруповані."
+#: aleksis/apps/chronos/preferences.py:43
+msgid "If there are more groups than the set limit, they will be collapsed."
+msgstr "Якщо груп більше за встановлений ліміт, вони будуть згруповані."
 
-#~ msgid "Limit of groups for shortening of groups"
-#~ msgstr "Ліміт груп для скорочення груп"
+#: aleksis/apps/chronos/preferences.py:51
+msgid "Limit of groups for shortening of groups"
+msgstr "Ліміт груп для скорочення груп"
 
-#, fuzzy
-#~| msgid "If an user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-#~ msgid "If a user activates shortening of groups,they will be collapsed if there are more groups than this limit."
-#~ msgstr "Якщо користувач активує скорочення груп, і кількість груп буде більше за ліміт, вони згруповуватимуться."
+#: aleksis/apps/chronos/preferences.py:53
+msgid ""
+"If a user activates shortening of groups,they will be collapsed if there are "
+"more groups than this limit."
+msgstr ""
+"Якщо користувач активує скорочення груп, і кількість груп буде більше за "
+"ліміт, вони групуватимуться."
 
-#~ msgid "How many days in advance users should be notified about timetable changes?"
-#~ msgstr "За скільки днів потрібно інформувати користувачів щодо змін у розкладі?"
+#: aleksis/apps/chronos/preferences.py:118
+msgid ""
+"How many days in advance users should be notified about timetable changes?"
+msgstr ""
+"За скільки днів потрібно інформувати користувачів щодо змін у розкладі?"
 
-#~ msgid "Time for sending notifications about timetable changes"
-#~ msgstr "Час для надсилання сповіщень щодо змін у розкладі"
+#: aleksis/apps/chronos/preferences.py:126
+msgid "Time for sending notifications about timetable changes"
+msgstr "Час для надсилання сповіщень щодо змін у розкладі"
 
-#~ msgid "This is only used for scheduling notifications which doesn't affect the time period configured above. All other notifications affecting the next days are sent immediately."
-#~ msgstr "Це використовується лише для планування сповіщень, які не впливають на навчальний час, встановлений вище. Усі інші сповіщення, які впливають на найближчі дні, надсилаються негайно."
+#: aleksis/apps/chronos/preferences.py:129
+msgid ""
+"This is only used for scheduling notifications which doesn't affect the time "
+"period configured above. All other notifications affecting the next days are "
+"sent immediately."
+msgstr ""
+"Це використовується лише для планування сповіщень, які не впливають на "
+"навчальний час, встановлений вище. Усі інші сповіщення, які впливають на "
+"найближчі дні, надсилаються негайно."
 
-#~ msgid "Send notifications for current timetable changes"
-#~ msgstr "Надіслати сповіщення щодо поточних змін у розкладі"
+#: aleksis/apps/chronos/preferences.py:140
+#: aleksis/apps/chronos/preferences.py:148
+msgid "Send notifications for current timetable changes"
+msgstr "Надіслати сповіщення щодо поточних змін у розкладі"
 
+#: aleksis/apps/chronos/util/notifications.py:48
 #, python-brace-format
-#~ msgid "The {subject} lesson in the {period}. period on {day} has been cancelled."
-#~ msgstr "Урок {subject} на {period} уроці у {day} скасований."
+msgid ""
+"The {subject} lesson in the {period}. period on {day} has been cancelled."
+msgstr "Урок {subject} на {period} уроці у {day} скасований."
 
+#: aleksis/apps/chronos/util/notifications.py:55
 #, python-brace-format
-#~ msgid "The {subject} lesson in the {period}. period on {day} has some current changes."
-#~ msgstr "Урок {subject} на {period} уроці у {day} зараз дещо змінений."
+msgid ""
+"The {subject} lesson in the {period}. period on {day} has some current "
+"changes."
+msgstr "Урок {subject} на {period} уроці у {day} зараз дещо змінений."
 
+#: aleksis/apps/chronos/util/notifications.py:64
 #, python-brace-format
-#~ msgid "The teacher {old} is substituted by {new}."
-#~ msgid_plural "The teachers {old} are substituted by {new}."
-#~ msgstr[0] "Викладач {old} замінений викладачем {new}."
-#~ msgstr[1] "Викладачі {old} замінені викладачами {new}."
-#~ msgstr[2] "Викладачі {old} замінені викладачами {new}."
-#~ msgstr[3] "Викладачі {old} замінені викладачами {new}."
-
+msgid "The teacher {old} is substituted by {new}."
+msgid_plural "The teachers {old} are substituted by {new}."
+msgstr[0] "Викладач {old} замінений викладачем {new}."
+msgstr[1] "Викладачі {old} замінені викладачами {new}."
+msgstr[2] "Викладачі {old} замінені викладачами {new}."
+msgstr[3] "Викладачі {old} замінені викладачами {new}."
+
+#: aleksis/apps/chronos/util/notifications.py:76
 #, python-brace-format
-#~ msgid "The subject is changed to {subject}."
-#~ msgstr "Предмет замінений на {subject}."
+msgid "The subject is changed to {subject}."
+msgstr "Предмет замінений на {subject}."
 
+#: aleksis/apps/chronos/util/notifications.py:82
 #, python-brace-format
-#~ msgid "The lesson is moved from {old} to {new}."
-#~ msgstr "Урок перенесений з {old} до {new}."
+msgid "The lesson is moved from {old} to {new}."
+msgstr "Урок перенесений з {old} до {new}."
 
+#: aleksis/apps/chronos/util/notifications.py:91
 #, python-brace-format
-#~ msgid "There is an additional comment: {comment}."
-#~ msgstr "Маємо додатковий коментар: {comment}."
+msgid "There is an additional comment: {comment}."
+msgstr "Маємо додатковий коментар: {comment}."
 
+#: aleksis/apps/chronos/util/notifications.py:99
 #, python-brace-format
-#~ msgid "There is an event that starts on {date_start}, {period_from}. period and ends on {date_end}, {period_to}. period:"
-#~ msgstr "Маємо подію, яка розпочинається {date_start}, на {period_from}. уроці та закінчується {date_end}, на {period_to}. уроці:"
+msgid ""
+"There is an event that starts on {date_start}, {period_from}. period and "
+"ends on {date_end}, {period_to}. period:"
+msgstr ""
+"Маємо подію, яка розпочинається {date_start}, на {period_from}. уроці та "
+"закінчується {date_end}, на {period_to}. уроці:"
 
+#: aleksis/apps/chronos/util/notifications.py:112
 #, python-brace-format
-#~ msgid "There is an event on {date} from the {period_from}. period to the {period_to}. period:"
-#~ msgstr "Маємо подію на {date} з {period_from}. уроку до кінця {period_to}. уроку:"
+msgid ""
+"There is an event on {date} from the {period_from}. period to the "
+"{period_to}. period:"
+msgstr ""
+"Маємо подію на {date} з {period_from}. уроку до кінця {period_to}. уроку:"
 
+#: aleksis/apps/chronos/util/notifications.py:123
+#: aleksis/apps/chronos/util/notifications.py:143
 #, python-brace-format
-#~ msgid "Groups: {groups}"
-#~ msgstr "Групи: {groups}"
+msgid "Groups: {groups}"
+msgstr "Групи: {groups}"
 
+#: aleksis/apps/chronos/util/notifications.py:125
+#: aleksis/apps/chronos/util/notifications.py:147
 #, python-brace-format
-#~ msgid "Teachers: {teachers}"
-#~ msgstr "Викладачі: {teachers}"
+msgid "Teachers: {teachers}"
+msgstr "Викладачі: {teachers}"
 
+#: aleksis/apps/chronos/util/notifications.py:128
 #, python-brace-format
-#~ msgid "Rooms: {rooms}"
-#~ msgstr "Кімнати: {rooms}"
+msgid "Rooms: {rooms}"
+msgstr "Кімнати: {rooms}"
 
+#: aleksis/apps/chronos/util/notifications.py:135
 #, python-brace-format
-#~ msgid "There is an extra lesson on {date} in the {period}. period:"
-#~ msgstr "Маємо додатковий урок {date} на {period}. уроці:"
+msgid "There is an extra lesson on {date} in the {period}. period:"
+msgstr "Маємо додатковий урок {date} на {period}. уроці:"
 
+#: aleksis/apps/chronos/util/notifications.py:145
 #, python-brace-format
-#~ msgid "Subject: {subject}"
-#~ msgstr "Предмет: {subject}"
+msgid "Subject: {subject}"
+msgstr "Предмет: {subject}"
 
+#: aleksis/apps/chronos/util/notifications.py:149
 #, python-brace-format
-#~ msgid "Room: {room}"
-#~ msgstr "Кімната: {room}"
+msgid "Room: {room}"
+msgstr "Кімната: {room}"
 
+#: aleksis/apps/chronos/util/notifications.py:151
 #, python-brace-format
-#~ msgid "Comment: {comment}."
-#~ msgstr "Коментар: {comment}."
+msgid "Comment: {comment}."
+msgstr "Коментар: {comment}."
 
+#: aleksis/apps/chronos/util/notifications.py:154
 #, python-brace-format
-#~ msgid "The supervision of {old} on {date} between the {period_from}. period and the {period_to}. period in the area {area} is substituted by {new}."
-#~ msgstr "Контроль {old}, {date} між {period_from}. та {period_to}. уроками у зоні {area} замінений на {new}."
+msgid ""
+"The supervision of {old} on {date} between the {period_from}. period and the "
+"{period_to}. period in the area {area} is substituted by {new}."
+msgstr ""
+"Контроль {old}, {date} між {period_from}. та {period_to}. уроками у зоні "
+"{area} замінений на {new}."
+
+#: aleksis/apps/chronos/util/notifications.py:204
+msgid "Timetable"
+msgstr "Розклад"
+
+#: aleksis/apps/chronos/util/notifications.py:205
+msgid "There are current changes to your timetable."
+msgstr "У Вашому розкладі є зміни."
 
-#~ msgid "Timetable"
-#~ msgstr "Розклад"
+#: aleksis/apps/chronos/models.py:788
+msgid "Holiday"
+msgstr "Вихідний"
 
-#~ msgid "There are current changes to your timetable."
-#~ msgstr "У Вашому розкладі є зміни."
+#: aleksis/apps/chronos/models.py:1298
+msgid "Can view all supervisions per day"
+msgstr "Може бачити весь нагляд окремого дня"
 
 #~ msgid "Options for timetables"
 #~ msgstr "Опції для розкладів"
@@ -598,9 +825,6 @@ msgstr ""
 #~ msgid "Substitution"
 #~ msgstr "Заміна"
 
-#~ msgid "Manage substitution"
-#~ msgstr "Керування замінами"
-
 #~ msgid "No teachers timetables available."
 #~ msgstr "Розклади викладачів недоступні."
 
@@ -679,3 +903,6 @@ msgstr ""
 
 #~ msgid "View class register of the current week"
 #~ msgstr "Огляд класного журналу поточного тижня"
+
+#~ msgid "Manage substitution"
+#~ msgstr "Керування замінами"
diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index 07695bb22e7361f3cbfa02b8d5553d2ad6ed5b5d..3fc2ce6dada6aa7a19e182ed46f3b331e33d2f8f 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -15,7 +15,9 @@ class TimetableType(Enum):
 
     GROUP = "group"
     TEACHER = "teacher"
+    PARTICIPANT = "participant"
     ROOM = "room"
+    COURSE = "course"
 
     @classmethod
     def from_string(cls, s: Optional[str]):
@@ -53,6 +55,13 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
         )
         return Q(pk__in=amended)
 
+    def for_teachers(self, teachers: list[Union[int, Person]]) -> "LessonEventQuerySet":
+        """Get all lesson events for a list of persons as teacher (including amends)."""
+        amended = self.filter(Q(amended_by__isnull=False) & (Q(teachers__in=teachers))).values_list(
+            "amended_by__pk", flat=True
+        )
+        return self.filter(Q(teachers__in=teachers) | Q(pk__in=amended)).distinct()
+
     def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet":
         """Get all lesson events the person participates in (including amends)."""
         return self.filter(self.for_participant_q(person)).distinct()
@@ -77,6 +86,17 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
         )
         return Q(pk__in=amended)
 
+    def for_owner_q(self, person: Union[int, Person]) -> Q:
+        """Get all lesson events the person owns any group of (including amends)."""
+        amended = self.filter(Q(amended_by__isnull=False) & Q(groups__owners=person)).values_list(
+            "amended_by__pk", flat=True
+        )
+        return Q(groups__owners=person) | Q(pk__in=amended)
+
+    def for_owner(self, person: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events the person owns any group of (including amends)."""
+        return self.filter(self.for_owner_q(person)).distinct()
+
     def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
         """Get all lesson events for a certain group (including amends/as parent group)."""
         return self.filter(self.for_group_q(group)).distinct()
@@ -210,6 +230,15 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
         """Get all lesson events that are amending other events."""
         return self.filter(self.amending_q())
 
+    @staticmethod
+    def current_changes_q() -> Q:
+        """Get all lesson events that are current changes."""
+        return Q(amends__isnull=False) | Q(current_change=True)
+
+    def current_changes(self) -> "LessonEventQuerySet":
+        """Get all lesson events that are current changes."""
+        return self.filter(self.current_changes_q())
+
 
 class SupervisionEventQuerySet(LessonEventQuerySet):
     pass
diff --git a/aleksis/apps/chronos/migrations/0020_add_global_permissions.py b/aleksis/apps/chronos/migrations/0020_add_global_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fbc5102bd4d4368836ed3a8756b000a8c4ec174
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0020_add_global_permissions.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.0.7 on 2024-09-13 21:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0019_remove_old_models'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='chronosglobalpermissions',
+            options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_all_course_timetables', 'Can view all course timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_substitutions', 'Can view substitutions table'), ('view_all_room_supervisions', 'Can view all room supervisions'), ('view_all_group_supervisions', 'Can view all group supervisions'), ('view_all_person_supervisions', 'Can view all person supervisions'))},
+        ),
+    ]
diff --git a/aleksis/apps/chronos/migrations/0021_lessonevent_current_change.py b/aleksis/apps/chronos/migrations/0021_lessonevent_current_change.py
new file mode 100644
index 0000000000000000000000000000000000000000..f05654958c804f287a54dabb9d4d5707341ae463
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0021_lessonevent_current_change.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.3 on 2024-11-30 10:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0020_add_global_permissions'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='lessonevent',
+            name='current_change',
+            field=models.BooleanField(default=False, verbose_name='Is this a current change?'),
+        ),
+    ]
diff --git a/aleksis/apps/chronos/migrations/0022_add_substitution_global_permission.py b/aleksis/apps/chronos/migrations/0022_add_substitution_global_permission.py
new file mode 100644
index 0000000000000000000000000000000000000000..8244150f1da622dc6b96efe8d34cf5b25756cccd
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0022_add_substitution_global_permission.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.0.6 on 2024-06-07 14:19
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0021_lessonevent_current_change'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='chronosglobalpermissions',
+            options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_lessons_day', 'Can view all lessons per day'), ('view_supervisions_day', 'Can view all supervisions per day'), ('manage_substitutions', 'Can manage all substitutions'))},
+        ),
+    ]
diff --git a/aleksis/apps/chronos/migrations/0020_add_lessoneventalarm.py b/aleksis/apps/chronos/migrations/0023_add_lessoneventalarm.py
similarity index 93%
rename from aleksis/apps/chronos/migrations/0020_add_lessoneventalarm.py
rename to aleksis/apps/chronos/migrations/0023_add_lessoneventalarm.py
index 9963d8b54a8c909e9c6a2836f8d99f82186951b1..e8bfac5985463ba76774f24ba837c60869ec5bf1 100644
--- a/aleksis/apps/chronos/migrations/0020_add_lessoneventalarm.py
+++ b/aleksis/apps/chronos/migrations/0023_add_lessoneventalarm.py
@@ -7,7 +7,7 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('chronos', '0019_remove_old_models'),
+        ('chronos', '0022_add_substitution_global_permission'),
         ('core', '0068_add_calendar_alarm'),
     ]
 
diff --git a/aleksis/apps/chronos/model_extensions.py b/aleksis/apps/chronos/model_extensions.py
index 457ef9ef8d0183bf5ddfa5c6799a7e4d14db20d5..61832c4c5eaf81905ec610104d3409cb5912f558 100644
--- a/aleksis/apps/chronos/model_extensions.py
+++ b/aleksis/apps/chronos/model_extensions.py
@@ -1,14 +1,35 @@
 from django.utils.translation import gettext_lazy as _
 
-from aleksis.core.models import Group, Person
+from aleksis.apps.cursus.models import Course
+from aleksis.core.models import Group, Person, Room
 
-# Dynamically add extra permissions to Group and Person models in core
+# Dynamically add permissions to Group, Person and Room models in core and Course model in cursus
 # Note: requires migrate afterwards
 Group.add_permission(
     "view_group_timetable",
     _("Can view group timetable"),
 )
+Group.add_permission(
+    "manage_group_substitutions",
+    _("Can manage group substitutions"),
+)
 Person.add_permission(
     "view_person_timetable",
     _("Can view person timetable"),
 )
+Group.add_permission(
+    "view_group_supervisions",
+    _("Can view group supervisions"),
+)
+Person.add_permission(
+    "view_person_supervisions",
+    _("Can view person supervisions"),
+)
+Room.add_permission(
+    "view_room_supervisions",
+    _("Can view room supervisions"),
+)
+Course.add_permission(
+    "view_course_timetable",
+    _("Can view course timetable"),
+)
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 95589c4fb155c0cfa24e46235ede803f5969718f..2acfa8d7156eed1f87562cdea574ef014760b005 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -34,6 +34,7 @@ from aleksis.core.mixins import (
 )
 from aleksis.core.models import CalendarAlarm, CalendarEvent, Group, Person, Room
 from aleksis.core.util.core_helpers import get_site_preferences, has_person
+from aleksis.core.util.predicates import check_global_permission
 
 
 class AutomaticPlan(LiveDocument):
@@ -109,7 +110,7 @@ class AutomaticPlan(LiveDocument):
 
                 # Check if the changed object is relevant for the time period of the PDF file
                 if not version.object.amends:
-                    return
+                    continue
 
                 if version.object.datetime_start:
                     date_start = version.object.datetime_start.date()
@@ -146,8 +147,13 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
             ("view_all_room_timetables", _("Can view all room timetables")),
             ("view_all_group_timetables", _("Can view all group timetables")),
             ("view_all_person_timetables", _("Can view all person timetables")),
+            ("view_all_course_timetables", _("Can view all course timetables")),
             ("view_timetable_overview", _("Can view timetable overview")),
             ("view_substitutions", _("Can view substitutions table")),
+            ("view_all_room_supervisions", _("Can view all room supervisions")),
+            ("view_all_group_supervisions", _("Can view all group supervisions")),
+            ("view_all_person_supervisions", _("Can view all person supervisions")),
+            ("manage_substitutions", _("Can manage all substitutions")),
         )
 
 
@@ -210,6 +216,9 @@ class LessonEvent(CalendarEvent):
         blank=True,
     )
 
+    # current_change=True will show this event in substitutions table
+    current_change = models.BooleanField(default=False, verbose_name=_("Is this a current change?"))
+
     @property
     def actual_groups(self: LessonEvent) -> QuerySet[Group]:
         """Get list of the groups of this lesson event."""
@@ -415,7 +424,7 @@ class LessonEvent(CalendarEvent):
             return cls.objects.none()
 
         q = Q()
-
+        prefetch_absences = False
         if params:
             try:
                 obj_id = int(params.get("id", 0))
@@ -423,9 +432,11 @@ class LessonEvent(CalendarEvent):
                 obj_id = None
 
             type_ = params.get("type", None)
+            prefetch_absences = params.get("prefetch_absences", False)
             not_amended = params.get("not_amended", False)
             not_amending = params.get("not_amending", False)
             amending = params.get("amending", False)
+            current_changes = params.get("current_changes", False)
             own = params.get("own", False)
 
             if not_amended:
@@ -437,6 +448,9 @@ class LessonEvent(CalendarEvent):
             if amending:
                 q = q & LessonEventQuerySet.amending_q()
 
+            if current_changes:
+                q = q & LessonEventQuerySet.current_changes_q()
+
             if request and "own" in params:
                 if own:
                     q = q & LessonEventQuerySet.for_person_q(request.user.person)
@@ -444,6 +458,40 @@ class LessonEvent(CalendarEvent):
                     q = q & LessonEventQuerySet.related_to_person_q(request.user.person)
 
             if type_ and obj_id:
+                if request and not (
+                    (
+                        type_ == "GROUP"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_group_timetables"
+                        )
+                    )
+                    or (
+                        type_ == "TEACHER"
+                        or type_ == "PARTICIPANT"
+                        or type_ == "OWNER"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_person_timetables"
+                        )
+                    )
+                    or (
+                        type_ == "ROOM"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_room_timetables"
+                        )
+                    )
+                    or (
+                        type_ == "COURSE"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_course_timetables"
+                        )
+                    )
+                ):
+                    # inline import needed to avoid circular import
+                    from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
+
+                    obj = get_el_by_pk(request, type_.lower(), obj_id)
+                    if not request.user.has_perm("chronos.view_timetable_rule", obj):
+                        return LessonEvent.objects.none()
                 if type_ == "TEACHER":
                     q = q & LessonEventQuerySet.for_teacher_q(obj_id)
                 elif type_ == "PARTICIPANT":
@@ -454,21 +502,130 @@ class LessonEvent(CalendarEvent):
                     q = q & LessonEventQuerySet.for_room_q(obj_id)
                 elif type_ == "COURSE":
                     q = q & LessonEventQuerySet.for_course_q(obj_id)
+                elif type_ == "OWNER":
+                    q = q & LessonEventQuerySet.for_owner_q(obj_id)
 
         elif request:
             q = q & LessonEventQuerySet.for_person_q(request.user.person)
 
+        prefetch_fields = ["groups", "teachers", "rooms", "groups__members"]
+        if prefetch_absences:
+            prefetch_fields.append("teachers__kolego_absences")
+
         objs = super().get_objects(
             request,
             params,
             start_qs=cls.objects.not_instance_of(SupervisionEvent),
             additional_filter=q,
             select_related=["subject", "course"],
-            prefetch_related=["groups", "teachers", "rooms", "groups__members"],
+            prefetch_related=prefetch_fields,
             **kwargs,
         )
         return objs
 
+    @classmethod
+    def get_for_substitution_overview(
+        cls,
+        date_start: datetime,
+        date_end: datetime,
+        request: HttpRequest,
+        obj_type: str | None = None,
+        obj_id: str | None = None,
+        teacher: str | None = None,
+        incomplete: str | None = False,
+    ) -> list:
+        """Get all the amended lessons for an object and a time frame.
+
+        obj_type may be one of TEACHER, GROUP, ROOM, COURSE
+        """
+
+        # 1. Find all LessonEvents for all Lessons of this Group in this date range
+        # which are not themselves amending another lessonEvent
+        event_params = {
+            "not_amending": True,
+            "prefetch_absences": True,
+        }
+
+        if request.user.has_perm("chronos.manage_substitutions"):
+            event_params["all"] = True
+        else:
+            event_params["own"] = False
+
+        if obj_type is not None and obj_id is not None:
+            event_params.update(
+                {
+                    "type": obj_type,
+                    "id": obj_id,
+                }
+            )
+        elif not request.user.has_perm("chronos.manage_substitutions"):
+            event_params.update(
+                {
+                    "type": "OWNER",
+                    "id": request.user.person.id,
+                }
+            )
+
+        event_queryset = LessonEvent.get_objects(request=request, params=event_params)
+
+        if teacher:
+            event_queryset = event_queryset.for_teacher(teacher)
+        else:
+            affected_teachers = Person.objects.filter(
+                Q(kolego_absences__datetime_start__lte=date_end)
+                & Q(kolego_absences__datetime_end__gte=date_start)
+            )
+            event_queryset = event_queryset.for_teachers(affected_teachers)
+
+        events = LessonEvent.get_single_events(
+            start=date_start,
+            end=date_end,
+            request=request,
+            with_reference_object=True,
+            queryset=event_queryset,
+        )
+
+        # 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 dummy one
+
+        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=datetime_end)
+                & Q(kolego_absences__datetime_end__gte=datetime_start)
+            )
+
+            if affected_teachers.exists():
+                existing_substitutions = reference_obj.amended_by.instance_of(cls).filter(
+                    datetime_start=event["DTSTART"].dt,
+                    datetime_end=event["DTEND"].dt,
+                )
+
+                if existing_substitutions.exists():
+                    if incomplete:
+                        continue
+                    substitution = existing_substitutions.first()
+                    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
+
     def save(self, *args, **kwargs):
         adding_status = self._state.adding
 
@@ -614,6 +771,7 @@ class SupervisionEvent(LessonEvent):
             not_amended = params.get("not_amended", False)
             not_amending = params.get("not_amending", False)
             amending = params.get("amending", False)
+            current_changes = params.get("current_changes", False)
 
             if not_amended:
                 q = q & SupervisionEventQuerySet.not_amended_q()
@@ -624,7 +782,36 @@ class SupervisionEvent(LessonEvent):
             if amending:
                 q = q & SupervisionEventQuerySet.amending_q()
 
+            if current_changes:
+                q = q & SupervisionEventQuerySet.current_changes_q()
+
             if type_ and obj_id:
+                if request and not (
+                    (
+                        type_ == "GROUP"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_group_supervisions"
+                        )
+                    )
+                    or (
+                        type_ == "TEACHER"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_person_supervisions"
+                        )
+                    )
+                    or (
+                        type_ == "ROOM"
+                        and check_global_permission(
+                            request.user, "chronos.view_all_room_supervisions"
+                        )
+                    )
+                ):
+                    # inline import needed to avoid circular import
+                    from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
+
+                    obj = get_el_by_pk(request, type_.lower(), obj_id)
+                    if not request.user.has_perm("chronos.view_supervisions_rule", obj):
+                        return SupervisionEvent.objects.none()
                 if type_ == "TEACHER":
                     q = q & SupervisionEventQuerySet.for_teacher_q(obj_id)
                 elif type_ == "GROUP":
diff --git a/aleksis/apps/chronos/preferences.py b/aleksis/apps/chronos/preferences.py
index ec8bf739a4238519ae8dc64ca742dab077a505bc..35068ea34ede3068e7eea4ca9435f7f7afa24bff 100644
--- a/aleksis/apps/chronos/preferences.py
+++ b/aleksis/apps/chronos/preferences.py
@@ -167,4 +167,21 @@ class TimeInAdvanceAlarms(DurationPreference):
     name = "time_in_advance_alarms"
     default = timedelta(hours=24)
     verbose_name = _("How much in advance should lesson event alarms be sent?")
+
+
+@site_preferences_registry.register
+class DaysInCalendar(MultipleChoicePreference):
+    section = chronos
+    name = "days_in_calendar"
+    default = ["1", "2", "3", "4", "5"]
+    verbose_name = _("Days of the week that appear in the timetable")
+    choices = [
+        ("0", _("Sunday")),
+        ("1", _("Monday")),
+        ("2", _("Tuesday")),
+        ("3", _("Wednesday")),
+        ("4", _("Thursday")),
+        ("5", _("Friday")),
+        ("6", _("Saturday")),
+    ]
     required = True
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index c7d0065c6bc2c6180a0fa81188484472e6ed8af7..2f16ba5434c770eb08fd25767b992722a8ca3416 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -6,7 +6,14 @@ from aleksis.core.util.predicates import (
     has_person,
 )
 
-from .util.predicates import has_any_timetable_object, has_timetable_perm
+from .util.predicates import (
+    has_any_group_substitution_perm,
+    has_any_timetable_object,
+    has_group_substitution_perm,
+    has_substitution_perm_by_group,
+    has_supervisions_perm,
+    has_timetable_perm,
+)
 
 # View timetable overview
 view_timetable_overview_predicate = has_person & (
@@ -18,23 +25,56 @@ add_perm("chronos.view_timetable_overview_rule", view_timetable_overview_predica
 view_timetable_predicate = has_person & has_timetable_perm
 add_perm("chronos.view_timetable_rule", view_timetable_predicate)
 
+# View supervisions for group, person or room
+view_supervisions_predicate = has_person & has_supervisions_perm
+add_perm("chronos.view_supervisions_rule", view_supervisions_predicate)
+
+# View substitution management overview page
+view_substitution_overview_predicate = has_person & (
+    has_global_perm("chronos.manage_substitutions") | has_any_group_substitution_perm
+)
+add_perm("chronos.view_substitution_overview_rule", view_substitution_overview_predicate)
+
+# Manage substitutions for a group
+manage_substitutions_for_group_predicate = has_person & (
+    has_global_perm("chronos.manage_substitutions") | has_group_substitution_perm
+)
+add_perm("chronos.manage_substitutions_for_group_rule", manage_substitutions_for_group_predicate)
+
+# Add substitution
+add_substitution_predicate = has_person & (
+    has_global_perm("chronos.manage_substitutions")
+    | has_substitution_perm_by_group
+    | has_global_perm("chronos.add_lessonsubstitution")
+)
+add_perm("chronos.create_substitution_rule", add_substitution_predicate)
 
 # Edit substition
 edit_substitution_predicate = has_person & (
-    has_global_perm("chronos.change_lessonevent") | has_object_perm("chronos.change_lessonevent")
+    has_global_perm("chronos.manage_substitutions")
+    | has_substitution_perm_by_group
+    | has_global_perm("chronos.change_lessonevent")
+    | has_object_perm("chronos.change_lessonevent")
 )
 add_perm("chronos.edit_substitution_rule", edit_substitution_predicate)
 
 # Delete substitution
 delete_substitution_predicate = has_person & (
-    has_global_perm("chronos.delete_lessonevent") | has_object_perm("chronos.delete_lessonevent")
+    has_global_perm("chronos.manage_substitutions")
+    | has_substitution_perm_by_group
+    | has_global_perm("chronos.delete_lessonevent")
+    | has_object_perm("chronos.delete_lessonevent")
 )
 add_perm("chronos.delete_substitution_rule", delete_substitution_predicate)
 
 # View substitutions
-view_substitutions_predicate = has_person & (has_global_perm("chronos.view_substitutions"))
+view_substitutions_predicate = has_person & (
+    has_global_perm("chronos.view_substitutions") | has_global_perm("chronos.manage_substitutions")
+)
 add_perm("chronos.view_substitutions_rule", view_substitutions_predicate)
 
 # View parent menu entry
-view_menu_predicate = has_person & (view_timetable_overview_predicate)
+view_menu_predicate = has_person & (
+    view_timetable_overview_predicate | view_substitutions_predicate
+)
 add_perm("chronos.view_menu_rule", view_menu_predicate)
diff --git a/aleksis/apps/chronos/schema/__init__.py b/aleksis/apps/chronos/schema/__init__.py
index d23eb0c7c8bf056b66f3b7c074f2a73e6b6f1182..5e06c397b2481388c77b23940bdd3256bb0b208a 100644
--- a/aleksis/apps/chronos/schema/__init__.py
+++ b/aleksis/apps/chronos/schema/__init__.py
@@ -1,19 +1,32 @@
+from datetime import datetime
+
+from django.core.exceptions import PermissionDenied
+from django.db.models import Q
+
 import graphene
+import graphene_django_optimizer
 from graphene_django import DjangoObjectType
+from reversion import create_revision, set_comment, set_user
 
+from aleksis.apps.cursus.models import Subject
 from aleksis.core.models import Group, Person, Room
 from aleksis.core.schema.base import (
     BaseBatchCreateMutation,
     BaseBatchDeleteMutation,
     BaseBatchPatchMutation,
+    FilterOrderList,
 )
 from aleksis.core.schema.group import GroupType
 from aleksis.core.schema.person import PersonType
 from aleksis.core.schema.room import RoomType
+from aleksis.core.util.core_helpers import (
+    get_site_preferences,
+    has_person,
+)
 
 from ..models import LessonEvent
 from ..util.build import build_substitutions_list
-from ..util.chronos_helpers import get_groups, get_next_relevant_day, get_rooms, get_teachers
+from ..util.chronos_helpers import get_groups, get_rooms, get_teachers
 
 
 class TimetablePersonType(DjangoObjectType):
@@ -52,6 +65,7 @@ class LessonEventType(DjangoObjectType):
             "teachers",
             "groups",
             "rooms",
+            "course",
             "cancelled",
             "comment",
         )
@@ -60,6 +74,35 @@ class LessonEventType(DjangoObjectType):
         }
 
     amends = graphene.Field(lambda: LessonEventType, required=False)
+    old_id = graphene.ID(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:
+            return 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:
+            return root.amends.rooms
+        return []
 
 
 class AmendLessonBatchCreateMutation(BaseBatchCreateMutation):
@@ -106,6 +149,87 @@ class AmendLessonBatchDeleteMutation(BaseBatchDeleteMutation):
         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 we can't check
+        # different permissions depending on the operation with it.
+        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.create_substitution_rule", amended_lesson_event):
+                obj = LessonEvent.objects.create(
+                    datetime_start=datetime_start,
+                    datetime_end=datetime_end,
+                    amends=amended_lesson_event,
+                    comment=substitution.comment or "",
+                    cancelled=substitution.cancelled or False,
+                )
+                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))
+                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.amends):
+                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"
@@ -148,11 +272,13 @@ class SubstitutionType(graphene.ObjectType):
 
     def resolve_old_groups(root, info):
         le = root["REFERENCE_OBJECT"]
-        return le.amends.groups.all() or le.groups.all()
+        if le.amends and le.amends.groups.all():
+            return le.amends.groups.all()
+        return le.groups.all()
 
     def resolve_new_groups(root, info):
         le = root["REFERENCE_OBJECT"]
-        if le.groups.all() and le.amends.groups.all():
+        if le.groups.all() and le.amends and le.amends.groups.all():
             return le.groups.all()
         else:
             return []
@@ -171,11 +297,13 @@ class SubstitutionType(graphene.ObjectType):
 
     def resolve_old_teachers(root, info):
         le = root["REFERENCE_OBJECT"]
-        return le.amends.teachers.all() or le.teachers.all()
+        if le.amends and le.amends.teachers.all():
+            return le.amends.teachers.all()
+        return le.teachers.all()
 
     def resolve_new_teachers(root, info):
         le = root["REFERENCE_OBJECT"]
-        if le.teachers.all() and le.amends.teachers.all():
+        if le.teachers.all() and le.amends and le.amends.teachers.all():
             return le.teachers.all()
         else:
             return []
@@ -184,30 +312,32 @@ class SubstitutionType(graphene.ObjectType):
         le = root["REFERENCE_OBJECT"]
         if le.name == "supervision":
             return "SUPERVISION"
-        elif not le.amends.subject and not le.subject:
-            return le.amends.title
+        elif not (le.amends and le.amends.subject) and not le.subject:
+            if le.amends:
+                return le.amends.title
+            return le.title
         else:
-            subject = le.amends.subject or le.subject
+            subject = le.amends.subject if le.amends and le.amends.subject else le.subject
             return subject.short_name or subject.name
 
     def resolve_new_subject(root, info):
         le = root["REFERENCE_OBJECT"]
         if le.name == "supervision":
             return None
-        elif not le.amends.subject and not le.subject:
-            return le.title
-        elif le.subject and le.amends.subject:
+        elif le.subject and le.amends and le.amends.subject:
             return le.subject.short_name or le.subject.name
         else:
             return None
 
     def resolve_old_rooms(root, info):
         le = root["REFERENCE_OBJECT"]
-        return le.amends.rooms.all() or le.rooms.all()
+        if le.amends and le.amends.rooms.all():
+            return le.amends.rooms.all()
+        return le.rooms.all()
 
     def resolve_new_rooms(root, info):
         le = root["REFERENCE_OBJECT"]
-        if le.rooms.all() and le.amends.rooms.all():
+        if le.rooms.all() and le.amends and le.amends.rooms.all():
             return le.rooms.all()
         else:
             return []
@@ -216,7 +346,7 @@ class SubstitutionType(graphene.ObjectType):
         return root["REFERENCE_OBJECT"].cancelled
 
     def resolve_notes(root, info):
-        return root["REFERENCE_OBJECT"].title or root["REFERENCE_OBJECT"].comment
+        return root["REFERENCE_OBJECT"].comment
 
 
 class SubstitutionsForDateType(graphene.ObjectType):
@@ -234,19 +364,34 @@ class Query(graphene.ObjectType):
         SubstitutionsForDateType,
         date=graphene.Date(),
     )
+    timetable_days = graphene.List(graphene.Int)
+
+    amended_lessons_from_absences = FilterOrderList(
+        LessonEventType,
+        obj_type=graphene.String(required=False),
+        obj_id=graphene.ID(required=False),
+        date_start=graphene.Date(required=True),
+        date_end=graphene.Date(required=True),
+        teacher=graphene.ID(required=False),
+        incomplete=graphene.Boolean(required=False),
+    )
 
     def resolve_timetable_teachers(self, info, **kwargs):
-        return get_teachers(info.context.user)
+        return graphene_django_optimizer.query(
+            get_teachers(info.context.user, request=info.context), info
+        )
 
     def resolve_timetable_groups(self, info, **kwargs):
-        return get_groups(info.context.user)
+        return graphene_django_optimizer.query(
+            get_groups(info.context.user, request=info.context), info
+        )
 
     def resolve_timetable_rooms(self, info, **kwargs):
-        return get_rooms(info.context.user)
+        return graphene_django_optimizer.query(get_rooms(info.context.user), info)
 
     def resolve_available_timetables(self, info, **kwargs):
         all_timetables = []
-        for group in get_groups(info.context.user):
+        for group in get_groups(info.context.user, request=info.context):
             all_timetables.append(
                 TimetableObjectType(
                     id=group.id,
@@ -256,7 +401,7 @@ class Query(graphene.ObjectType):
                 )
             )
 
-        for teacher in get_teachers(info.context.user):
+        for teacher in get_teachers(info.context.user, request=info.context):
             all_timetables.append(
                 TimetableObjectType(
                     id=teacher.id,
@@ -276,17 +421,65 @@ class Query(graphene.ObjectType):
         return all_timetables
 
     def resolve_substitutions_for_date(root, info, date):
-        substitutions, affected_teachers, affected_groups = build_substitutions_list(
-            get_next_relevant_day(date)
-        )
+        substitutions, affected_teachers, affected_groups = build_substitutions_list(date)
         return SubstitutionsForDateType(
             affected_teachers=affected_teachers,
             affected_groups=affected_groups,
             substitutions=[sub["el"] for sub in substitutions],
         )
 
+    @staticmethod
+    def resolve_timetable_days(root, info, **kwargs):
+        first_day = "default"
+
+        if has_person(info.context):
+            first_day = info.context.user.person.preferences["calendar__first_day_of_the_week"]
+
+        if first_day == "default":
+            first_day = get_site_preferences()["calendar__first_day_of_the_week"]
+
+        first_day = int(first_day)
+
+        days = list(map(str, range(7)))
+        sorted_days = days[first_day:] + days[:first_day]
+
+        allowed_days = get_site_preferences()["chronos__days_in_calendar"]
+
+        return list(map(int, filter(lambda d: d in allowed_days, sorted_days)))
+
+    def resolve_amended_lessons_from_absences(
+        root,
+        info,
+        date_start,
+        date_end,
+        obj_type="GROUP",
+        obj_id=None,
+        teacher=None,
+        incomplete=False,
+        **kwargs,
+    ):
+        datetime_start = datetime.combine(date_start, datetime.min.time())
+        datetime_end = datetime.combine(date_end, datetime.max.time())
+
+        if (
+            obj_id
+            and not info.context.user.has_perm(
+                "chronos.manage_substitutions_for_group_rule", Group.objects.get(id=obj_id)
+            )
+        ) or (
+            not obj_id and not info.context.user.has_perm("chronos.view_substitution_overview_rule")
+        ):
+            raise PermissionDenied()
+
+        return LessonEvent.get_for_substitution_overview(
+            datetime_start, datetime_end, info.context, obj_type, obj_id, teacher, incomplete
+        )
+
 
 class Mutation(graphene.ObjectType):
     create_amend_lessons = AmendLessonBatchCreateMutation.Field()
     patch_amend_lessons = AmendLessonBatchPatchMutation.Field()
+    patch_amend_lessons_with_amends = AmendLessonBatchPatchMutation.Field()
     delete_amend_lessons = AmendLessonBatchDeleteMutation.Field()
+
+    create_or_update_substitutions = SubstitutionBatchCreateOrUpdateMutation.Field()
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/comment.html b/aleksis/apps/chronos/templates/chronos/partials/subs/comment.html
index 694a0ce3a430b25f295c2ed75120ef2e0ed9db84..17cb2f2b3d5a5f9a4b5e38bce2a7b7635c3934d9 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/comment.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/comment.html
@@ -1,5 +1,3 @@
-{% if el.title %}
-  <em>{{ el.title }}</em>
-{% elif el.comment %}
+{% if el.comment %}
   <em>{{ el.comment }}</em>
 {% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
index 033d7b745a36aa3d859ad95b79e705b9ce4cfb87..6d9f79f28736377bf193099702e1d9a08297ee8f 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
@@ -3,10 +3,9 @@
   {% trans "Supervision" %}
 {% elif not el.amends.subject and not el.subject %}
   {% if el.amends.title %}
-    <s>{{ el.amends.title }}</s>
-  {% endif %}
-  {% if el.title %}
-    <s>{{ el.title }}</s>
+    {{ el.amends.title }}
+  {% elif el.title %}
+    {{ el.title }}
   {% endif %}
 {% elif el.cancelled %}
   <s>{% include "chronos/partials/subject.html" with subject=el.amends.subject %}</s>
diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
index 66168ce2496758b6525c98fb31afbd5c686a84b5..e8f0a2cb7a61d2b854125377bd1f9cb05574de10 100644
--- a/aleksis/apps/chronos/util/build.py
+++ b/aleksis/apps/chronos/util/build.py
@@ -4,7 +4,7 @@ from aleksis.apps.chronos.models import LessonEvent, SupervisionEvent
 from aleksis.core.models import Group, Person
 
 
-def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:
+def build_substitutions_list(wanted_day: date) -> tuple[list[dict], list[Person], list[Group]]:
     rows = []
     affected_teachers = set()
     affected_groups = set()
@@ -12,19 +12,21 @@ def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person],
     lesson_events = LessonEvent.get_single_events(
         datetime.combine(wanted_day, time.min),
         datetime.combine(wanted_day, time.max),
-        params={"amending": True},
+        params={"current_changes": True},
         with_reference_object=True,
     )
 
     for lesson_event in lesson_events:
+        ref_object = lesson_event["REFERENCE_OBJECT"]
         affected_teachers.update(lesson_event["REFERENCE_OBJECT"].teachers.all())
-        affected_teachers.update(lesson_event["REFERENCE_OBJECT"].amends.teachers.all())
         affected_groups.update(lesson_event["REFERENCE_OBJECT"].groups.all())
-        affected_groups.update(lesson_event["REFERENCE_OBJECT"].amends.groups.all())
+        if ref_object.amends:
+            affected_teachers.update(ref_object.amends.teachers.all())
+            affected_groups.update(ref_object.amends.groups.all())
 
         row = {
             "type": "substitution",
-            "sort_a": lesson_event["REFERENCE_OBJECT"].group_names,
+            "sort_a": ref_object.group_names,
             "sort_b": str(lesson_event["DTSTART"]),
             "el": lesson_event,
         }
@@ -34,14 +36,15 @@ def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person],
     supervision_events = SupervisionEvent.get_single_events(
         datetime.combine(wanted_day, time.min),
         datetime.combine(wanted_day, time.max),
-        params={"amending": True},
+        params={"current_changes": True},
         with_reference_object=True,
     )
-    print(supervision_events)
 
     for supervision_event in supervision_events:
-        affected_teachers.update(supervision_event["REFERENCE_OBJECT"].teachers.all())
-        affected_teachers.update(supervision_event["REFERENCE_OBJECT"].amends.teachers.all())
+        ref_object = supervision_event["REFERENCE_OBJECT"]
+        affected_teachers.update(ref_object.teachers.all())
+        if ref_object.amends:
+            affected_teachers.update(ref_object.amends.teachers.all())
 
         row = {
             "type": "supervision_substitution",
@@ -54,4 +57,7 @@ def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person],
 
     rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
 
+    affected_teachers = sorted(affected_teachers, key=lambda p: p.short_name or p.last_name)
+    affected_groups = sorted(affected_groups, key=lambda g: g.short_name or g.name)
+
     return rows, affected_teachers, affected_groups
diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
index 961fb01460f8c4fa65504d8726d653d223eeef3b..0adb0bd67b8255b1da528888052a9eeb94f58c94 100644
--- a/aleksis/apps/chronos/util/chronos_helpers.py
+++ b/aleksis/apps/chronos/util/chronos_helpers.py
@@ -2,12 +2,20 @@ from datetime import date, datetime, timedelta
 from typing import TYPE_CHECKING, Optional
 
 from django.db.models import Count, Q
+from django.http import HttpRequest, HttpResponseNotFound
+from django.shortcuts import get_object_or_404
 
 from guardian.shortcuts import get_objects_for_user
 
+from aleksis.apps.cursus.models import Course
 from aleksis.core.models import Announcement, Group, Person, Room
-from aleksis.core.util.core_helpers import get_site_preferences
+from aleksis.core.util.core_helpers import (
+    filter_active_school_term,
+    get_active_school_term,
+    get_site_preferences,
+)
 
+from ..managers import TimetableType
 from .build import build_substitutions_list
 
 if TYPE_CHECKING:
@@ -16,11 +24,39 @@ if TYPE_CHECKING:
     User = get_user_model()  # noqa
 
 
-def get_teachers(user: "User"):
+def get_el_by_pk(
+    request: HttpRequest,
+    type_: str,
+    pk: int,
+    prefetch: bool = False,
+    *args,
+    **kwargs,
+):
+    if type_ == TimetableType.GROUP.value:
+        return get_object_or_404(
+            Group.objects.prefetch_related("owners", "parent_groups") if prefetch else Group,
+            pk=pk,
+        )
+    elif type_ == TimetableType.TEACHER.value or type_ == TimetableType.PARTICIPANT.value:
+        return get_object_or_404(Person, pk=pk)
+    elif type_ == TimetableType.ROOM.value:
+        return get_object_or_404(Room, pk=pk)
+    elif type_ == TimetableType.COURSE.value:
+        return get_object_or_404(Course, pk=pk)
+    else:
+        return HttpResponseNotFound()
+
+
+def get_teachers(user: "User", request=None):
     """Get the teachers whose timetables are allowed to be seen by current user."""
 
+    school_term = get_active_school_term(request)
+
+    q = Q(courses_as_teacher__groups__school_term=school_term) | Q(
+        courses_as_teacher__groups__school_term=None
+    )
     teachers = (
-        Person.objects.annotate(course_count=Count("courses_as_teacher"))
+        Person.objects.annotate(course_count=Count("courses_as_teacher", filter=q))
         .filter(course_count__gt=0)
         .order_by("short_name", "last_name")
     )
@@ -36,10 +72,10 @@ def get_teachers(user: "User"):
     return teachers
 
 
-def get_groups(user: "User"):
+def get_groups(user: "User", request=None):
     """Get the groups whose timetables are allowed to be seen by current user."""
 
-    groups = Group.objects.for_current_school_term_or_all()
+    groups = filter_active_school_term(request, Group.objects.all())
 
     group_types = get_site_preferences()["chronos__group_types_timetables"]
 
diff --git a/aleksis/apps/chronos/util/predicates.py b/aleksis/apps/chronos/util/predicates.py
index 7657ea96becd43be0b92b9ebfda24723b119c44d..370b47f990feb1564ef70e8cb1809030481d7d26 100644
--- a/aleksis/apps/chronos/util/predicates.py
+++ b/aleksis/apps/chronos/util/predicates.py
@@ -3,9 +3,12 @@ from django.db.models import Model
 
 from rules import predicate
 
+from aleksis.apps.cursus.models import Course
 from aleksis.core.models import Group, Person, Room
-from aleksis.core.util.predicates import has_global_perm, has_object_perm
+from aleksis.core.util.core_helpers import queryset_rules_filter
+from aleksis.core.util.predicates import has_any_object, has_global_perm, has_object_perm
 
+from ..models import LessonEvent
 from .chronos_helpers import get_groups, get_rooms, get_teachers
 
 
@@ -23,6 +26,8 @@ def has_timetable_perm(user: User, obj: Model) -> bool:
         return has_person_timetable_perm(user, obj)
     elif isinstance(obj, Room):
         return has_room_timetable_perm(user, obj)
+    elif isinstance(obj, Course):
+        return has_course_timetable_perm(user, obj)
     else:
         return False
 
@@ -44,6 +49,50 @@ def has_group_timetable_perm(user: User, obj: Group) -> bool:
     )
 
 
+@predicate
+def has_substitution_perm_by_group(user: User, obj: LessonEvent) -> bool:
+    """
+    Check if can create/edit substitution based on group.
+
+    Predicate which checks whether the user is allowed
+    to create/edit the requested substitution.
+    """
+    if not obj:
+        return False
+    return (
+        obj.groups.filter(pk__in=user.person.owner_of.values_list("id", flat=True)).exists()
+        or queryset_rules_filter(user, obj.groups.all(), "core.manage_group_substitutions").exists()
+    )
+
+
+@predicate
+def has_group_substitution_perm(user: User, obj: Group) -> bool:
+    """
+    Check if can access/edit substitutions of given group.
+
+    Predicate which checks whether the user is allowed
+    to access/edit the substitutions of the given group.
+    """
+    return (
+        obj in user.person.owner_of.all()
+        or has_global_perm("chronos.view_lessonsubstitution")(user)
+        or has_object_perm("core.manage_group_substitutions")(user, obj)
+    )
+
+
+@predicate
+def has_any_group_substitution_perm(user: User) -> bool:
+    """
+    Check if can create/edit substitutions of any group.
+
+    Predicate which checks whether the user is allowed
+    to create/edit any substitutions of any group.
+    """
+    return user.person.owner_of.exists() or has_any_object(
+        "core.manage_group_substitutions", Group
+    )(user)
+
+
 @predicate
 def has_person_timetable_perm(user: User, obj: Person) -> bool:
     """
@@ -72,6 +121,88 @@ def has_room_timetable_perm(user: User, obj: Room) -> bool:
     )(user, obj)
 
 
+@predicate
+def has_course_timetable_perm(user: User, obj: Course) -> bool:
+    """
+    Check if can access course timetable.
+
+    Predicate which checks whether the user is allowed
+    to access the requested course timetable.
+    """
+    return (
+        user.person in obj.teachers.all()
+        or obj.groups.all().intersection(user.person.member_of.all()).exists()
+        or user.person.primary_group in obj.groups.all()
+        or obj.groups.all().intersection(user.person.owner_of.all()).exists()
+        or has_global_perm("chronos.view_all_course_timetables")(user)
+        or has_object_perm("cursus.view_course_timetable")(user, obj)
+    )
+
+
+@predicate
+def has_supervisions_perm(user: User, obj: Model) -> bool:
+    """
+    Check if can access supervisions of object.
+
+    Predicate which checks whether the user is allowed
+    to access the requested supervisions of the given
+    group, person or room.
+    """
+    if isinstance(obj, Group):
+        return has_group_supervisions_perm(user, obj)
+    elif isinstance(obj, Person):
+        return has_person_supervisions_perm(user, obj)
+    elif isinstance(obj, Room):
+        return has_room_supervisions_perm(user, obj)
+    else:
+        return False
+
+
+@predicate
+def has_group_supervisions_perm(user: User, obj: Group) -> bool:
+    """
+    Check if can access group supervisions.
+
+    Predicate which checks whether the user is allowed
+    to access the requested group supervisions.
+    """
+    return (
+        obj in user.person.member_of.all()
+        or user.person.primary_group == obj
+        or obj in user.person.owner_of.all()
+        or has_global_perm("chronos.view_all_group_supervisions")(user)
+        or has_object_perm("core.view_group_supervisions")(user, obj)
+    )
+
+
+@predicate
+def has_person_supervisions_perm(user: User, obj: Person) -> bool:
+    """
+    Check if can access person supervisions.
+
+    Predicate which checks whether the user is allowed
+    to access the requested person supervisions.
+    """
+    return (
+        user.person == obj
+        or has_global_perm("chronos.view_all_person_supervisions")(user)
+        or has_object_perm("core.view_person_supervisions")(user, obj)
+    )
+
+
+@predicate
+def has_room_supervisions_perm(user: User, obj: Room) -> bool:
+    """
+    Check if can access room supervisions.
+
+    Predicate which checks whether the user is allowed
+    to access the requested room supervisions.
+    """
+    return has_global_perm("chronos.view_all_room_supervisions")(user) or has_object_perm(
+        "core.view_room_supervisions"
+    )(user, obj)
+
+
 @predicate
 def has_any_timetable_object(user: User) -> bool:
     """Predicate which checks whether there are any timetables the user is allowed to access."""
diff --git a/pyproject.toml b/pyproject.toml
index 923710bea23b988bf914f23afba9c2f6f98c0c3e..5b331df48e3053e2eef4f0e7a6d03905146c463b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Chronos"
-version = "4.0.0.dev9"
+version = "4.0.0.dev10"
 packages = [
     { include = "aleksis" }
 ]
@@ -54,6 +54,7 @@ calendarweek = "^0.5.0"
 aleksis-core = "^4.0.0.dev13"
 aleksis-app-resint = "^4.0.0.dev1"
 aleksis-app-cursus = "^0.1.0.dev4"
+aleksis-app-kolego = "^0.1.0.dev0"
 
 
 [tool.poetry.plugins."aleksis.app"]
diff --git a/tox.ini b/tox.ini
index 294e65bc96d4e06262b67e3e6b8a987307b226e4..92f26b55d9ebd6f5b3db425cab8d151bde0c0c69 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 skipsdist = True
 skip_missing_interpreters = true
-envlist = py39,py310,py311
+envlist = py310,py311,py312
 
 [testenv]
 allowlist_externals =