From 22dcf78c2129ec10c79378dce0ff9b0e5ac08d9b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Tue, 13 Feb 2024 21:41:14 +0100
Subject: [PATCH 01/17] WIP: Adapt to refactored CRUD lists

---
 .../components/breaks_and_slots/Break.vue     | 19 ++++----
 .../breaks_and_slots/LesroosterSlot.vue       | 20 ++++-----
 .../components/breaks_and_slots/break.graphql | 32 --------------
 .../components/breaks_and_slots/slot.graphql  | 32 --------------
 .../components/lesson_raster/LessonRaster.vue | 36 +++-------------
 .../components/supervision/Supervision.vue    | 22 +++++-----
 .../supervision/supervision.graphql           | 14 ------
 .../TimeboundCourseConfigCRUDTable.vue        | 23 +++++-----
 .../TimeboundCourseConfigRaster.vue           | 10 ++---
 .../timeboundCourseConfig.graphql             | 24 ++---------
 .../timetableManagement.graphql               | 16 +++----
 .../validity_range/ValidityRange.vue          | 22 ++++------
 .../validity_range/ValidityRangeField.vue     | 13 +++---
 .../validity_range/validityRange.graphql      | 12 ++----
 aleksis/apps/lesrooster/schema/__init__.py    | 43 ++++++-------------
 aleksis/apps/lesrooster/schema/break_slot.py  | 24 -----------
 aleksis/apps/lesrooster/schema/lesson.py      | 32 +-------------
 aleksis/apps/lesrooster/schema/slot.py        | 15 -------
 aleksis/apps/lesrooster/schema/supervision.py | 10 +----
 aleksis/apps/lesrooster/schema/time_grid.py   | 10 +----
 .../schema/timebound_course_config.py         | 15 ++-----
 .../apps/lesrooster/schema/validity_range.py  | 10 +----
 pyproject.toml                                |  2 +-
 23 files changed, 104 insertions(+), 352 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
index b1b6156e..52bf8e28 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
@@ -1,8 +1,7 @@
 <script>
 import {
   breakSlots,
-  createBreakSlot,
-  deleteBreakSlot,
+  createBreakSlots,
   deleteBreakSlots,
   updateBreakSlots,
 } from "./break.graphql";
@@ -39,15 +38,13 @@ export default {
       i18nKey: "lesrooster.break",
       createItemI18nKey: "lesrooster.break.create_item",
       gqlQuery: breakSlots,
-      gqlCreateMutation: createBreakSlot,
+      gqlCreateMutation: createBreakSlots,
       gqlPatchMutation: updateBreakSlots,
-      gqlDeleteMutation: deleteBreakSlot,
-      gqlDeleteMultipleMutation: deleteBreakSlots,
+      gqlDeleteMutation: deleteBreakSlots,
     };
   },
   methods: {
     getCreateData(item) {
-      console.log("in getCreateData", item);
       return {
         ...item,
         period: null,
@@ -55,17 +52,17 @@ export default {
         timeGrid: item.timeGrid.id,
       };
     },
-    getPatchData(items) {
-      console.log("patch items", items);
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
         name: item.name,
         weekday: this.weekdayAsInt(item.weekday),
         period: null,
         timeStart: item.timeStart,
         timeEnd: item.timeEnd,
-        timeGrid: item.timeGrid.id,
-      }));
+        timeGrid: item.timeGrid?.id,
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
index 19189786..cb6f55cb 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
@@ -15,7 +15,6 @@ import TimeGridField from "../validity_range/TimeGridField.vue";
     :gql-create-mutation="gqlCreateMutation"
     :gql-patch-mutation="gqlPatchMutation"
     :gql-delete-mutation="gqlDeleteMutation"
-    :gql-delete-multiple-mutation="gqlDeleteMultipleMutation"
     :default-item="defaultItem"
     :get-create-data="getCreateData"
     :get-patch-data="getPatchData"
@@ -116,8 +115,7 @@ import TimeGridField from "../validity_range/TimeGridField.vue";
 <script>
 import {
   slots,
-  createSlot,
-  deleteSlot,
+  createSlots,
   deleteSlots,
   updateSlots,
 } from "./slot.graphql";
@@ -156,10 +154,9 @@ export default {
       i18nKey: "lesrooster.slot",
       createItemI18nKey: "lesrooster.slot.create_slot",
       gqlQuery: slots,
-      gqlCreateMutation: createSlot,
+      gqlCreateMutation: createSlots,
       gqlPatchMutation: updateSlots,
-      gqlDeleteMutation: deleteSlot,
-      gqlDeleteMultipleMutation: deleteSlots,
+      gqlDeleteMutation: deleteSlots,
       defaultItem: {
         name: "",
         timeStart: "",
@@ -185,24 +182,23 @@ export default {
       return NaN;
     },
     getCreateData(item) {
-      console.log("in getCreateData", item);
       return {
         ...item,
         weekday: this.weekdayAsInt(item.weekday),
         timeGrid: item.timeGrid.id,
       };
     },
-    getPatchData(items) {
-      console.log("patch items", items);
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
         name: item.name,
         weekday: this.weekdayAsInt(item.weekday),
         period: item.period,
         timeStart: item.timeStart,
         timeEnd: item.timeEnd,
-        timeGrid: item.timeGrid.id,
-      }));
+        timeGrid: item.timeGrid?.id,
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
     formatTimeGrid(item) {
       if (!item) return null;
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
index 63c2087d..8a764a73 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
@@ -24,32 +24,6 @@ query breakSlots($orderBy: [String], $filters: JSONString) {
   }
 }
 
-mutation createBreakSlot($input: CreateBreakSlotInput!) {
-  createBreakSlot(input: $input) {
-    item: breakSlot {
-      id
-      name
-      timeGrid {
-        id
-        group {
-          id
-          name
-        }
-        validityRange {
-          id
-          name
-        }
-      }
-      weekday
-      period
-      timeStart
-      timeEnd
-      canEdit
-      canDelete
-    }
-  }
-}
-
 mutation createBreakSlots($input: [BatchCreateBreakSlotInput]!) {
   createBreakSlots(input: $input) {
     items: breakSlots {
@@ -77,12 +51,6 @@ mutation createBreakSlots($input: [BatchCreateBreakSlotInput]!) {
   }
 }
 
-mutation deleteBreakSlot($id: ID!) {
-  deleteBreakSlot(id: $id) {
-    ok
-  }
-}
-
 mutation deleteBreakSlots($ids: [ID]!) {
   deleteBreakSlots(ids: $ids) {
     deletionCount
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
index 269faa53..3c6b2362 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
@@ -23,32 +23,6 @@ query slots($orderBy: [String], $filters: JSONString) {
   }
 }
 
-mutation createSlot($input: CreateSlotInput!) {
-  createSlot(input: $input) {
-    item: slot {
-      id
-      name
-      timeGrid {
-        id
-        group {
-          id
-          name
-        }
-        validityRange {
-          id
-          name
-        }
-      }
-      weekday
-      period
-      timeStart
-      timeEnd
-      canEdit
-      canDelete
-    }
-  }
-}
-
 mutation createSlots($input: [BatchCreateSlotInput]!) {
   createSlots(input: $input) {
     items: slots {
@@ -76,12 +50,6 @@ mutation createSlots($input: [BatchCreateSlotInput]!) {
   }
 }
 
-mutation deleteSlot($id: ID!) {
-  deleteSlot(id: $id) {
-    ok
-  }
-}
-
 mutation deleteSlots($ids: [ID]!) {
   deleteSlots(ids: $ids) {
     deletionCount
diff --git a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
index 02860dc8..bc50cbe0 100644
--- a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
+++ b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
@@ -180,28 +180,12 @@
     <delete-dialog
       :gql-mutation="deleteMutation"
       :gql-query="$apollo.queries.items"
-      v-model="deleteDialog"
-      :item="itemToDelete"
-    >
-      <template #body>
-        {{
-          $t(
-            "lesrooster." + itemToDelete.model.toLowerCase() + ".repr",
-            itemToDelete,
-          )
-        }}
-      </template>
-    </delete-dialog>
-
-    <delete-multiple-dialog
-      :gql-mutation="deleteMultipleMutation"
-      :gql-query="$apollo.queries.items"
       :items="itemsToDelete"
-      v-model="deleteMultipleDialog"
+      v-model="deleteDialog"
     >
       <template #title>
         {{
-          $t("lesrooster.slot.confirm_delete_multiple_slots", {
+          $t("lesrooster.slot.confirm_delete_slots", {
             day: $t("weekdays." + weekdayToDelete),
           })
         }}
@@ -214,7 +198,7 @@
           </li>
         </ul>
       </template>
-    </delete-multiple-dialog>
+    </delete-dialog>
   </div>
 </template>
 
@@ -223,11 +207,9 @@ import {
   carryOverSlots,
   copySlotsFromGrid,
   slots,
-  deleteSlot,
   deleteSlots,
 } from "../breaks_and_slots/slot.graphql";
 import DeleteDialog from "aleksis.core/components/generic/dialogs/DeleteDialog.vue";
-import DeleteMultipleDialog from "aleksis.core/components/generic/dialogs/DeleteMultipleDialog.vue";
 import CopyFromTimeGridMenu from "../validity_range/CopyFromTimeGridMenu.vue";
 import SlotCard from "./SlotCard.vue";
 import SlotCreator from "./SlotCreator.vue";
@@ -240,7 +222,6 @@ export default {
     CopyFromTimeGridMenu,
     SlotCreator,
     DeleteDialog,
-    DeleteMultipleDialog,
     SlotCard,
   },
   apollo: {
@@ -272,11 +253,8 @@ export default {
         main: false,
       },
       gqlQuery: slots,
-      deleteMutation: deleteSlot,
-      deleteMultipleMutation: deleteSlots,
+      deleteMutation: deleteSlots,
       deleteDialog: false,
-      deleteMultipleDialog: false,
-      itemToDelete: null,
       itemsToDelete: [],
       weekdayToDelete: "",
       createBreaks: false,
@@ -426,8 +404,8 @@ export default {
           this.loading[day] = false;
         });
     },
-    deleteSingularSlot(slot) {
-      this.itemToDelete = slot;
+    deleteSlot(slot) {
+      this.itemToDeletes = [slot]
       this.deleteDialog = true;
     },
     deleteSlotsOfDay(weekday) {
@@ -435,7 +413,7 @@ export default {
         (slot) => slot.weekday === weekday,
       );
       this.weekdayToDelete = weekday;
-      this.deleteMultipleDialog = true;
+      this.deleteDialog = true;
     },
     copyFromGrid(existingTimeGrid) {
       if (!this.internalTimeGrid || !this.internalTimeGrid.id) return;
diff --git a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
index 730c1bfc..200e0150 100644
--- a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
+++ b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
@@ -15,7 +15,6 @@ import InlineCRUDList from "aleksis.core/components/generic/InlineCRUDList.vue";
     :gql-create-mutation="gqlCreateMutation"
     :gql-patch-mutation="gqlPatchMutation"
     :gql-delete-mutation="gqlDeleteMutation"
-    :gql-delete-multiple-mutation="gqlDeleteMultipleMutation"
     :default-item="defaultItem"
     :get-create-data="getCreateData"
     :get-patch-data="getPatchData"
@@ -150,8 +149,7 @@ import InlineCRUDList from "aleksis.core/components/generic/InlineCRUDList.vue";
 <script>
 import {
   supervisions,
-  createSupervision,
-  deleteSupervision,
+  createSupervisions,
   deleteSupervisions,
   updateSupervisions,
 } from "./supervision.graphql";
@@ -191,10 +189,9 @@ export default {
       i18nKey: "lesrooster.supervision",
       createItemI18nKey: "lesrooster.supervision.create_supervision",
       gqlQuery: supervisions,
-      gqlCreateMutation: createSupervision,
+      gqlCreateMutation: createSupervisions,
       gqlPatchMutation: updateSupervisions,
-      gqlDeleteMutation: deleteSupervision,
-      gqlDeleteMultipleMutation: deleteSupervisions,
+      gqlDeleteMutation: deleteSupervisions,
       defaultItem: {
         breakSlot: null,
         teachers: [],
@@ -298,15 +295,16 @@ export default {
         recurrence: this.getRRule(item.breakSlot.timeGrid).toString(),
       };
     },
-    getPatchData(items) {
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
-        breakSlot: item.breakSlot.id,
-        rooms: item.rooms.map((r) => r.id),
-        teachers: item.teachers.map((t) => t.id),
+        breakSlot: item.breakSlot?.id,
+        rooms: item.rooms?.map((r) => r.id),
+        teachers: item.teachers?.map((t) => t.id),
         subject: item.subject?.id,
         recurrence: this.getRRule(item.breakSlot.timeGrid).toString(),
-      }));
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql b/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
index 26556dfe..c37ad32d 100644
--- a/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
@@ -47,14 +47,6 @@ query supervisions($orderBy: [String], $filters: JSONString) {
   }
 }
 
-mutation createSupervision($input: CreateSupervisionInput!) {
-  createSupervision(input: $input) {
-    item: supervision {
-      ...supervisionFields
-    }
-  }
-}
-
 mutation createSupervisions($input: [BatchCreateSupervisionInput]!) {
   createSupervisions(input: $input) {
     items: supervisions {
@@ -63,12 +55,6 @@ mutation createSupervisions($input: [BatchCreateSupervisionInput]!) {
   }
 }
 
-mutation deleteSupervision($id: ID!) {
-  deleteSupervision(id: $id) {
-    ok
-  }
-}
-
 mutation deleteSupervisions($ids: [ID]!) {
   deleteSupervisions(ids: $ids) {
     deletionCount
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
index bf93ef62..814b3258 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
@@ -136,8 +136,8 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
 <script>
 import {
   timeboundCourseConfigs,
-  createTimeboundCourseConfig,
-  deleteTimeboundCourseConfig,
+  createTimeboundCourseConfigs,
+  deleteTimeboundCourseConfigs,
   updateTimeboundCourseConfigs,
 } from "./timeboundCourseConfig.graphql";
 
@@ -172,9 +172,9 @@ export default {
       createItemI18nKey:
         "lesrooster.timebound_course_config.create_timebound_course_config",
       gqlQuery: timeboundCourseConfigs,
-      gqlCreateMutation: createTimeboundCourseConfig,
+      gqlCreateMutation: createTimeboundCourseConfigs,
       gqlPatchMutation: updateTimeboundCourseConfigs,
-      gqlDeleteMutation: deleteTimeboundCourseConfig,
+      gqlDeleteMutation: deleteTimeboundCourseConfigs,
       defaultItem: {
         course: {
           id: "",
@@ -192,7 +192,6 @@ export default {
   },
   methods: {
     getCreateData(item) {
-      console.log("in getCreateData", item);
       return {
         ...item,
         course: item.course.id,
@@ -200,15 +199,15 @@ export default {
         validityRange: item.validityRange.id,
       };
     },
-    getPatchData(items) {
-      console.log("patch items", items);
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
-        course: item.course.id,
-        teachers: item.teachers.map((t) => t.id),
-        validityRange: item.validityRange.id,
+        course: item.course?.id,
+        teachers: item.teachers?.map((t) => t.id),
+        validityRange: item.validityRange?.id,
         lessonQuota: item.lessonQuota,
-      }));
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
   },
   apollo: {
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue
index 7a0a700a..79715008 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue
@@ -188,7 +188,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
 <script>
 import {
   subjects,
-  batchCreateTimeboundCourseConfig,
+  createTimeboundCourseConfigs,
   updateTimeboundCourseConfigs,
 } from "./timeboundCourseConfig.graphql";
 
@@ -196,7 +196,7 @@ import { currentValidityRange as gqlCurrentValidityRange } from "../validity_ran
 
 import { gqlGroups, gqlTeachers } from "../helper.graphql";
 
-import { batchCreateCourse } from "aleksis.apps.cursus/components/course.graphql";
+import { createCourses } from "aleksis.apps.cursus/components/course.graphql";
 
 export default {
   name: "TimeboungCourseConfigRaster",
@@ -269,7 +269,7 @@ export default {
         }
       } else {
         if (
-          !course.lrTimeboundCourseConfigs.filter(
+          !course.lrTimeboundCourseConfigs?.filter(
             (c) => c.validityRange.id === this.internalValidityRange?.id,
           ).length
         ) {
@@ -312,11 +312,11 @@ export default {
         },
         {
           data: this.createdCourseConfigs,
-          mutation: batchCreateTimeboundCourseConfig,
+          mutation: createTimeboundCourseConfigs,
         },
         {
           data: this.createdCourses,
-          mutation: batchCreateCourse,
+          mutation: createCourses,
         },
       ]) {
         if (mutationCombination.data.length) {
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
index 9ede745e..044a2be2 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
@@ -55,26 +55,10 @@ query timeboundCourseConfigs($orderBy: [String], $filters: JSONString) {
   }
 }
 
-mutation createTimeboundCourseConfig(
-  $input: CreateTimeboundCourseConfigInput!
-) {
-  createTimeboundCourseConfig(input: $input) {
-    item: timeboundCourseConfig {
-      ...timeboundCourseConfigFields
-      course {
-        ...courseFields
-        subject {
-          ...subjectFields
-        }
-      }
-    }
-  }
-}
-
-mutation batchCreateTimeboundCourseConfig(
+mutation createTimeboundCourseConfigs(
   $input: [BatchCreateTimeboundCourseConfigInput]!
 ) {
-  batchCreateTimeboundCourseConfig(input: $input) {
+  createTimeboundCourseConfigs(input: $input) {
     item: timeboundCourseConfigs {
       ...timeboundCourseConfigFields
       course {
@@ -87,8 +71,8 @@ mutation batchCreateTimeboundCourseConfig(
   }
 }
 
-mutation deleteTimeboundCourseConfig($id: ID!) {
-  deleteTimeboundCourseConfig(id: $id) {
+mutation deleteTimeboundCourseConfigs($ids: [ID]!) {
+  deleteTimeboundCourseConfigs(ids: $ids) {
     ok
   }
 }
diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql b/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
index 970c1c0b..3c6a73a6 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
@@ -138,9 +138,9 @@ query overlayLessons($rooms: [ID]!, $teachers: [ID]!, $timeGrid: ID!) {
   }
 }
 
-mutation createLesson($input: CreateLessonInput!) {
-  createLesson(input: $input) {
-    lesson {
+mutation createLessons($input: [BatchCreateLessonInput]!) {
+  createLessons(input: $input) {
+    items: lessons {
       id
       slotStart {
         id
@@ -217,9 +217,9 @@ mutation moveLesson($id: ID!, $input: PatchLessonInput!) {
   }
 }
 
-mutation updateLesson($input: PatchLessonInput!, $id: ID!) {
-  updateLesson(id: $id, input: $input) {
-    item: lesson {
+mutation updateLessons($input: [BatchPatchLessonInput]!) {
+  updateLessons(input: $input) {
+    items: lessons {
       id
       subject {
         id
@@ -245,8 +245,8 @@ mutation updateLesson($input: PatchLessonInput!, $id: ID!) {
   }
 }
 
-mutation deleteLesson($id: ID!) {
-  deleteLesson(id: $id) {
+mutation deleteLesson($ids: [ID]!) {
+  deleteLessons(ids: $ids) {
     ok
   }
 }
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
index b203b9d7..1dbf398e 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
@@ -21,7 +21,6 @@ import ValidityRangeStatusChip from "./ValidityRangeStatusChip.vue";
       :gql-create-mutation="gqlCreateMutation"
       :gql-patch-mutation="gqlPatchMutation"
       :gql-delete-mutation="gqlDeleteMutation"
-      :gql-delete-multiple-mutation="gqlDeleteMultipleMutation"
       :default-item="defaultItem"
       :get-create-data="getCreateData"
       :get-patch-data="getPatchData"
@@ -210,8 +209,7 @@ import ValidityRangeStatusChip from "./ValidityRangeStatusChip.vue";
 <script>
 import {
   validityRanges,
-  createValidityRange,
-  deleteValidityRange,
+  createValidityRanges,
   deleteValidityRanges,
   updateValidityRanges,
   createTimeGrid,
@@ -253,10 +251,9 @@ export default {
       ],
       i18nKey: "lesrooster.validity_range",
       gqlQuery: validityRanges,
-      gqlCreateMutation: createValidityRange,
+      gqlCreateMutation: createValidityRanges,
       gqlPatchMutation: updateValidityRanges,
-      gqlDeleteMutation: deleteValidityRange,
-      gqlDeleteMultipleMutation: deleteValidityRanges,
+      gqlDeleteMutation: deleteValidityRanges,
       defaultItem: {
         name: "",
         dateStart: "",
@@ -313,22 +310,21 @@ export default {
   },
   methods: {
     getCreateData(item) {
-      console.log("in getCreateData", item);
       return {
         ...item,
         schoolTerm: item.schoolTerm?.id,
       };
     },
-    getPatchData(items) {
-      console.log("patch items", items);
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
         name: item.name,
         dateStart: item.dateStart,
         dateEnd: item.dateEnd,
-        schoolTerm: item.schoolTerm.id,
-        status: item.status.toLowerCase(),
-      }));
+        schoolTerm: item.schoolTerm?.id,
+        status: item.status?.toLowerCase(),
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
     createTimeGridFor(validityRange) {
       this.timeGrids.range = validityRange;
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
index 274df9db..c1aaa47a 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
@@ -59,7 +59,7 @@ import SchoolTermField from "aleksis.core/components/school_term/SchoolTermField
 </template>
 
 <script>
-import { validityRanges, createValidityRange } from "./validityRange.graphql";
+import { validityRanges, createValidityRanges } from "./validityRange.graphql";
 
 export default {
   name: "ValidityRangeField",
@@ -85,7 +85,7 @@ export default {
       ],
       i18nKey: "lesrooster.validity_range",
       gqlQuery: validityRanges,
-      gqlCreateMutation: createValidityRange,
+      gqlCreateMutation: createValidityRanges,
       defaultItem: {
         name: "",
         dateStart: "",
@@ -97,21 +97,20 @@ export default {
   },
   methods: {
     getCreateData(item) {
-      console.log("in getCreateData", item);
       return {
         ...item,
         schoolTerm: item.schoolTerm?.id,
       };
     },
-    getPatchData(items) {
-      console.log("patch items", items);
-      return items.map((item) => ({
+    getPatchData(item) {
+      item = {
         id: item.id,
         name: item.name,
         dateStart: item.dateStart,
         dateEnd: item.dateEnd,
         schoolTerm: item.schoolTerm.id,
-      }));
+      };
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql b/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
index 5c15b259..d698ab15 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
@@ -22,9 +22,9 @@ query validityRanges($orderBy: [String], $filters: JSONString) {
   }
 }
 
-mutation createValidityRange($input: CreateValidityRangeInput!) {
-  createValidityRange(input: $input) {
-    item: validityRange {
+mutation createValidityRanges($input: [BatchCreateValidityRangeInput]!) {
+  createValidityRanges(input: $input) {
+    items: validityRanges {
       id
       name
       status
@@ -48,12 +48,6 @@ mutation createValidityRange($input: CreateValidityRangeInput!) {
   }
 }
 
-mutation deleteValidityRange($id: ID!) {
-  deleteValidityRange(id: $id) {
-    ok
-  }
-}
-
 mutation deleteValidityRanges($ids: [ID]!) {
   deleteValidityRanges(ids: $ids) {
     deletionCount
diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py
index ca317010..a5912290 100644
--- a/aleksis/apps/lesrooster/schema/__init__.py
+++ b/aleksis/apps/lesrooster/schema/__init__.py
@@ -24,16 +24,12 @@ from .break_slot import (
     BreakSlotBatchCreateMutation,
     BreakSlotBatchDeleteMutation,
     BreakSlotBatchPatchMutation,
-    BreakSlotCreateMutation,
-    BreakSlotDeleteMutation,
     BreakSlotType,
 )
 from .lesson import (
+    LessonBatchCreateMutation,
     LessonBatchDeleteMutation,
     LessonBatchPatchMutation,
-    LessonCreateMutation,
-    LessonDeleteMutation,
-    LessonPatchMutation,
     LessonType,
 )
 from .slot import (
@@ -42,36 +38,31 @@ from .slot import (
     SlotBatchCreateMutation,
     SlotBatchDeleteMutation,
     SlotBatchPatchMutation,
-    SlotCreateMutation,
-    SlotDeleteMutation,
     SlotType,
 )
 from .supervision import (
+    SupervisionBatchCreateMutation,
     SupervisionBatchDeleteMutation,
     SupervisionBatchPatchMutation,
-    SupervisionCreateMutation,
-    SupervisionDeleteMutation,
     SupervisionType,
 )
 from .time_grid import (
+    TimeGridBatchCreateMutation,
+    TimeGridBatchDeleteMutation,
     TimeGridBatchDeleteMutation,
-    TimeGridCreateMutation,
-    TimeGridDeleteMutation,
     TimeGridType,
 )
 from .timebound_course_config import (
     LesroosterExtendedSubjectType,
     TimeboundCourseConfigBatchCreateMutation,
+    TimeboundCourseConfigBatchDeleteMutation,
     TimeboundCourseConfigBatchPatchMutation,
-    TimeboundCourseConfigCreateMutation,
-    TimeboundCourseConfigDeleteMutation,
     TimeboundCourseConfigType,
 )
 from .validity_range import (
+    ValidityRangeBatchCreateMutation,
     ValidityRangeBatchDeleteMutation,
     ValidityRangeBatchPatchMutation,
-    ValidityRangeCreateMutation,
-    ValidityRangeDeleteMutation,
     ValidityRangeType,
 )
 
@@ -277,42 +268,32 @@ class Query(graphene.ObjectType):
 
 
 class Mutation(graphene.ObjectType):
-    create_break_slot = BreakSlotCreateMutation.Field()
     create_break_slots = BreakSlotBatchCreateMutation.Field()
-    delete_break_slot = BreakSlotDeleteMutation.Field()
     delete_break_slots = BreakSlotBatchDeleteMutation.Field()
     update_break_slots = BreakSlotBatchPatchMutation.Field()
 
-    create_slot = SlotCreateMutation.Field()
     create_slots = SlotBatchCreateMutation.Field()
-    delete_slot = SlotDeleteMutation.Field()
     delete_slots = SlotBatchDeleteMutation.Field()
     update_slots = SlotBatchPatchMutation.Field()
 
-    batch_create_timebound_course_config = TimeboundCourseConfigBatchCreateMutation.Field()
-    create_timebound_course_config = TimeboundCourseConfigCreateMutation.Field()
-    delete_timebound_course_config = TimeboundCourseConfigDeleteMutation.Field()
+    create_timebound_course_configs = TimeboundCourseConfigBatchCreateMutation.Field()
+    delete_timebound_course_configs = TimeboundCourseConfigBatchDeleteMutation.Field()
     update_timebound_course_configs = TimeboundCourseConfigBatchPatchMutation.Field()
     carry_over_slots = CarryOverSlotsMutation.Field()
     copy_slots_from_grid = CopySlotsFromDifferentTimeGridMutation.Field()
 
-    create_validity_range = ValidityRangeCreateMutation.Field()
-    delete_validity_range = ValidityRangeDeleteMutation.Field()
+    create_validity_ranges = ValidityRangeBatchCreateMutation.Field()
     delete_validity_ranges = ValidityRangeBatchDeleteMutation.Field()
     update_validity_ranges = ValidityRangeBatchPatchMutation.Field()
 
-    create_time_grid = TimeGridCreateMutation.Field()
-    delete_time_grid = TimeGridDeleteMutation.Field()
+    create_time_grids = TimeGridBatchCreateMutation.Field()
     delete_time_grids = TimeGridBatchDeleteMutation.Field()
     update_time_grids = TimeGridBatchDeleteMutation.Field()
 
-    create_lesson = LessonCreateMutation.Field()
-    delete_lesson = LessonDeleteMutation.Field()
+    create_lessons = LessonBatchCreateMutation.Field()
     delete_lessons = LessonBatchDeleteMutation.Field()
-    update_lesson = LessonPatchMutation.Field()
     update_lessons = LessonBatchPatchMutation.Field()
 
-    create_supervision = SupervisionCreateMutation.Field()
-    delete_supervision = SupervisionDeleteMutation.Field()
+    create_supervisions = SupervisionBatchCreateMutation.Field()
     delete_supervisions = SupervisionBatchDeleteMutation.Field()
     update_supervisions = SupervisionBatchPatchMutation.Field()
diff --git a/aleksis/apps/lesrooster/schema/break_slot.py b/aleksis/apps/lesrooster/schema/break_slot.py
index fbc54e41..ed6aa19e 100644
--- a/aleksis/apps/lesrooster/schema/break_slot.py
+++ b/aleksis/apps/lesrooster/schema/break_slot.py
@@ -4,12 +4,10 @@ from graphene_django_cud.mutations import (
     DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
@@ -45,28 +43,6 @@ class BreakSlotType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
         return queryset
 
 
-class BreakSlotCreateMutation(DjangoCreateMutation):
-    class Meta:
-        model = BreakSlot
-        return_field_name = "breakSlot"
-        field_types = {"weekday": graphene.Int()}
-        only_fields = (
-            "id",
-            "time_grid",
-            "name",
-            "weekday",
-            "period",
-            "time_start",
-            "time_end",
-        )
-        permissions = ("lesrooster.create_breakslot_rule",)
-
-
-class BreakSlotDeleteMutation(DeleteMutation):
-    klass = BreakSlot
-    permission_required = "lesrooster.delete_breakslot_rule"
-
-
 class BreakSlotBatchCreateMutation(PermissionBatchPatchMixin, DjangoBatchCreateMutation):
     class Meta:
         model = BreakSlot
diff --git a/aleksis/apps/lesrooster/schema/lesson.py b/aleksis/apps/lesrooster/schema/lesson.py
index de013bc0..e9c87c77 100644
--- a/aleksis/apps/lesrooster/schema/lesson.py
+++ b/aleksis/apps/lesrooster/schema/lesson.py
@@ -1,16 +1,14 @@
 import graphene
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
+    DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
-    DjangoPatchMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 from recurrence import Recurrence, deserialize, serialize
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     OptimisticResponseTypeMixin,
     PermissionBatchDeleteMixin,
@@ -47,7 +45,7 @@ class LessonType(
         return serialize(root.recurrence)
 
 
-class LessonCreateMutation(DjangoCreateMutation):
+class LessonBatchCreateMutation(DjangoBatchCreateMutation):
     class Meta:
         model = Lesson
         only_fields = (
@@ -68,11 +66,6 @@ class LessonCreateMutation(DjangoCreateMutation):
         return deserialize(value)
 
 
-class LessonDeleteMutation(DeleteMutation):
-    klass = Lesson
-    permission_required = "lesrooster.delete_lesson_rule"
-
-
 class LessonBatchDeleteMutation(PermissionBatchDeleteMixin, DjangoBatchDeleteMutation):
     class Meta:
         model = Lesson
@@ -98,24 +91,3 @@ class LessonBatchPatchMutation(PermissionBatchPatchMixin, DjangoBatchPatchMutati
     @classmethod
     def handle_recurrence(cls, value: str, name, info) -> Recurrence:
         return deserialize(value)
-
-
-class LessonPatchMutation(PermissionPatchMixin, DjangoPatchMutation):
-    class Meta:
-        model = Lesson
-        only_fields = (
-            "id",
-            "course",
-            "slot_start",
-            "slot_end",
-            "rooms",
-            "teachers",
-            "subject",
-            "recurrence",
-        )
-        field_types = {"recurrence": graphene.String()}
-        permissions = ("lesrooster.edit_lesson_rule",)
-
-    @classmethod
-    def handle_recurrence(cls, value: str, name, info) -> Recurrence:
-        return deserialize(value)
diff --git a/aleksis/apps/lesrooster/schema/slot.py b/aleksis/apps/lesrooster/schema/slot.py
index 6c2de6a5..73f135a9 100644
--- a/aleksis/apps/lesrooster/schema/slot.py
+++ b/aleksis/apps/lesrooster/schema/slot.py
@@ -6,12 +6,10 @@ from graphene_django_cud.mutations import (
     DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
@@ -50,19 +48,6 @@ class SlotType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
         return root.get_real_instance_class().__name__
 
 
-class SlotCreateMutation(DjangoCreateMutation):
-    class Meta:
-        model = Slot
-        field_types = {"weekday": graphene.Int()}
-        only_fields = ("id", "time_grid", "name", "weekday", "period", "time_start", "time_end")
-        permissions = ("lesrooster.create_slot_rule",)
-
-
-class SlotDeleteMutation(DeleteMutation):
-    klass = Slot
-    permission_required = "lesrooster.delete_slot"
-
-
 class SlotBatchCreateMutation(PermissionBatchPatchMixin, DjangoBatchCreateMutation):
     class Meta:
         model = Slot
diff --git a/aleksis/apps/lesrooster/schema/supervision.py b/aleksis/apps/lesrooster/schema/supervision.py
index e028cf86..80603a12 100644
--- a/aleksis/apps/lesrooster/schema/supervision.py
+++ b/aleksis/apps/lesrooster/schema/supervision.py
@@ -1,15 +1,14 @@
 import graphene
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
+    DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 from recurrence import Recurrence, deserialize, serialize
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
@@ -53,7 +52,7 @@ class SupervisionType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType)
         return serialize(root.recurrence)
 
 
-class SupervisionCreateMutation(DjangoCreateMutation):
+class SupervisionBatchCreateMutation(DjangoBatchCreateMutation):
     class Meta:
         model = Supervision
         field_types = {"recurrence": graphene.String()}
@@ -72,11 +71,6 @@ class SupervisionCreateMutation(DjangoCreateMutation):
         return deserialize(value)
 
 
-class SupervisionDeleteMutation(DeleteMutation):
-    klass = Supervision
-    permission_required = "lesrooster.delete_supervision_rule"
-
-
 class SupervisionBatchDeleteMutation(PermissionBatchDeleteMixin, DjangoBatchDeleteMutation):
     class Meta:
         model = Supervision
diff --git a/aleksis/apps/lesrooster/schema/time_grid.py b/aleksis/apps/lesrooster/schema/time_grid.py
index 906b16ce..a823920d 100644
--- a/aleksis/apps/lesrooster/schema/time_grid.py
+++ b/aleksis/apps/lesrooster/schema/time_grid.py
@@ -1,13 +1,12 @@
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
+    DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
@@ -40,7 +39,7 @@ class TimeGridType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
         return queryset
 
 
-class TimeGridCreateMutation(DjangoCreateMutation):
+class TimeGridBatchCreateMutation(DjangoBatchCreateMutation):
     class Meta:
         model = TimeGrid
         permissions = ("lesrooster.create_timegrid_rule",)
@@ -51,11 +50,6 @@ class TimeGridCreateMutation(DjangoCreateMutation):
         )
 
 
-class TimeGridDeleteMutation(DeleteMutation):
-    klass = TimeGrid
-    permission_required = "lesrooster.delete_timegrid_rule"
-
-
 class TimeGridBatchDeleteMutation(PermissionBatchDeleteMixin, DjangoBatchDeleteMutation):
     class Meta:
         model = TimeGrid
diff --git a/aleksis/apps/lesrooster/schema/timebound_course_config.py b/aleksis/apps/lesrooster/schema/timebound_course_config.py
index 978260ba..705f08ec 100644
--- a/aleksis/apps/lesrooster/schema/timebound_course_config.py
+++ b/aleksis/apps/lesrooster/schema/timebound_course_config.py
@@ -2,15 +2,14 @@ import graphene
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
     DjangoBatchCreateMutation,
+    DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 
 from aleksis.apps.cursus.models import Course, Subject
 from aleksis.apps.cursus.schema import CourseInterface, CourseType, SubjectType
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchPatchMixin,
     PermissionsTypeMixin,
@@ -86,23 +85,17 @@ class LesroosterExtendedSubjectType(SubjectType):
     courses = graphene.List(LesroosterExtendedCourseType)
 
 
-class TimeboundCourseConfigCreateMutation(DjangoCreateMutation):
+class TimeboundCourseConfigBatchCreateMutation(DjangoBatchCreateMutation):
     class Meta:
         model = TimeboundCourseConfig
         fields = ("id", "course", "validity_range", "lesson_quota", "teachers")
         permissions = ("lesrooster.create_timeboundcourseconfig_rule",)
 
 
-class TimeboundCourseConfigBatchCreateMutation(DjangoBatchCreateMutation):
+class TimeboundCourseConfigBatchDeleteMutation(DjangoBatchDeleteMutation):
     class Meta:
         model = TimeboundCourseConfig
-        fields = ("id", "course", "validity_range", "lesson_quota", "teachers")
-        permissions = ("lesrooster.create_timeboundcourseconfig_rule",)
-
-
-class TimeboundCourseConfigDeleteMutation(DeleteMutation):
-    klass = TimeboundCourseConfig
-    permission_required = "lesrooster.delete_timeboundcourseconfig_rule"
+        permission_required = "lesrooster.delete_timeboundcourseconfig_rule"
 
 
 class TimeboundCourseConfigBatchPatchMutation(PermissionBatchPatchMixin, DjangoBatchPatchMutation):
diff --git a/aleksis/apps/lesrooster/schema/validity_range.py b/aleksis/apps/lesrooster/schema/validity_range.py
index 040fc277..a3238585 100644
--- a/aleksis/apps/lesrooster/schema/validity_range.py
+++ b/aleksis/apps/lesrooster/schema/validity_range.py
@@ -1,14 +1,13 @@
 import graphene
 from graphene_django.types import DjangoObjectType
 from graphene_django_cud.mutations import (
+    DjangoBatchCreateMutation,
     DjangoBatchDeleteMutation,
     DjangoBatchPatchMutation,
-    DjangoCreateMutation,
 )
 from guardian.shortcuts import get_objects_for_user
 
 from aleksis.core.schema.base import (
-    DeleteMutation,
     DjangoFilterMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
@@ -40,7 +39,7 @@ class ValidityRangeType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp
         return queryset
 
 
-class ValidityRangeCreateMutation(DjangoCreateMutation):
+class ValidityRangeBatchCreateMutation(DjangoBatchCreateMutation):
     class Meta:
         model = ValidityRange
         permissions = ("lesrooster.create_validity_range_rule",)
@@ -56,11 +55,6 @@ class ValidityRangeCreateMutation(DjangoCreateMutation):
         field_types = {"status": graphene.String()}
 
 
-class ValidityRangeDeleteMutation(DeleteMutation):
-    klass = ValidityRange
-    permission_required = "lesrooster.delete_validityrange_rule"
-
-
 class ValidityRangeBatchDeleteMutation(PermissionBatchDeleteMixin, DjangoBatchDeleteMutation):
     class Meta:
         model = ValidityRange
diff --git a/pyproject.toml b/pyproject.toml
index 5266c94c..48885bcb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,7 +34,7 @@ url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple"
 priority = "supplemental"
 [tool.poetry.dependencies]
 python = "^3.10"
-AlekSIS-Core = "^4.0.0.dev2"
+AlekSIS-Core = "^4.0.0.dev3"
 AlekSIS-App-Chronos = "^4.0.0.dev1"
 AlekSIS-App-Cursus = "^0.1.dev0"
 django-recurrence = "^1.11.1"
-- 
GitLab


From 3f3e1918a9f10fdfa645a29d205206b84f6bf0fe Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Thu, 15 Feb 2024 21:00:22 +0100
Subject: [PATCH 02/17] Hide period field for break slots

---
 .../frontend/components/lesson_raster/SlotCreator.vue           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/lesson_raster/SlotCreator.vue b/aleksis/apps/lesrooster/frontend/components/lesson_raster/SlotCreator.vue
index bbf5d06b..f567120e 100644
--- a/aleksis/apps/lesrooster/frontend/components/lesson_raster/SlotCreator.vue
+++ b/aleksis/apps/lesrooster/frontend/components/lesson_raster/SlotCreator.vue
@@ -126,7 +126,7 @@ export default defineComponent({
     </template>
 
     <template #content>
-      <div aria-required="true">
+      <div v-if="!breaks" aria-required="true">
         <positive-small-integer-field
           v-model="slots.period"
           :label="$t('lesrooster.slot.period')"
-- 
GitLab


From 56feacf1238a9892c3cf966f195c929f53b6498c Mon Sep 17 00:00:00 2001
From: Michael Bauer <michael-bauer@posteo.de>
Date: Fri, 16 Feb 2024 13:03:46 +0100
Subject: [PATCH 03/17] Fix LessonRaster

---
 .../frontend/components/lesson_raster/LessonRaster.vue    | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
index bc50cbe0..989d5f49 100644
--- a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
+++ b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
@@ -171,15 +171,15 @@
       :disabled="
         $apollo.queries.items.loading || loading.main || loading[slot.weekday]
       "
-      @click:delete="deleteSingularSlot"
+      @click:delete="deleteSlot"
       @click:copy="copySingularSlotTodDay($event.item, $event.weekday)"
       :weekdays="weekdays"
       :id="'#slot-' + slot.id"
     />
 
     <delete-dialog
-      :gql-mutation="deleteMutation"
-      :gql-query="$apollo.queries.items"
+      :gql-delete-mutation="deleteMutation"
+      :affected-query="$apollo.queries.items"
       :items="itemsToDelete"
       v-model="deleteDialog"
     >
@@ -405,7 +405,7 @@ export default {
         });
     },
     deleteSlot(slot) {
-      this.itemToDeletes = [slot]
+      this.itemsToDelete = [slot]
       this.deleteDialog = true;
     },
     deleteSlotsOfDay(weekday) {
-- 
GitLab


From daf781e5cce5df62e24554f12fd27965ae70d5d7 Mon Sep 17 00:00:00 2001
From: Michael Bauer <michael-bauer@posteo.de>
Date: Fri, 16 Feb 2024 13:21:51 +0100
Subject: [PATCH 04/17] Fix TimeBoundCourseConfig

---
 .../timebound_course_config/TimeboundCourseConfigCRUDTable.vue  | 2 +-
 .../timebound_course_config/timeboundCourseConfig.graphql       | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
index 814b3258..dfac53ef 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
@@ -207,7 +207,7 @@ export default {
         validityRange: item.validityRange?.id,
         lessonQuota: item.lessonQuota,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => !(value === undefined)));
     },
   },
   apollo: {
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
index 044a2be2..6b1e3b0a 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
@@ -80,7 +80,7 @@ mutation deleteTimeboundCourseConfigs($ids: [ID]!) {
 mutation updateTimeboundCourseConfigs(
   $input: [BatchPatchTimeboundCourseConfigInput]!
 ) {
-  batchMutation: updateTimeboundCourseConfigs(input: $input) {
+  updateTimeboundCourseConfigs(input: $input) {
     items: timeboundCourseConfigs {
       ...timeboundCourseConfigFields
       course {
-- 
GitLab


From 0c3f7e4977534754c6553f5a1d474a82c7b2d601 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 16 Feb 2024 20:05:57 +0100
Subject: [PATCH 05/17] Fix things related to new CRUD lists and mutations

---
 .../components/breaks_and_slots/Break.vue     |  4 +-
 .../breaks_and_slots/LesroosterSlot.vue       |  5 +-
 .../components/breaks_and_slots/break.graphql |  2 +-
 .../components/breaks_and_slots/slot.graphql  |  2 +-
 .../components/supervision/Supervision.vue    |  6 +-
 .../supervision/supervision.graphql           |  2 +-
 .../TimeboundCourseConfigCRUDTable.vue        |  2 +-
 .../timeboundCourseConfig.graphql             |  2 +-
 .../TimetableManagement.vue                   | 84 ++++++++++---------
 .../timetableManagement.graphql               | 10 +--
 .../validity_range/TimeGridField.vue          |  8 +-
 .../validity_range/ValidityRange.vue          | 10 +--
 .../validity_range/ValidityRangeField.vue     |  2 +-
 .../validity_range/validityRange.graphql      | 14 ++--
 14 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
index 52bf8e28..4ece76f3 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
@@ -56,13 +56,13 @@ export default {
       item = {
         id: item.id,
         name: item.name,
-        weekday: this.weekdayAsInt(item.weekday),
+        weekday: item.weekday ? this.weekdayAsInt(item.weekday) : undefined,
         period: null,
         timeStart: item.timeStart,
         timeEnd: item.timeEnd,
         timeGrid: item.timeGrid?.id,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
index cb6f55cb..833ee1d3 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
@@ -192,13 +192,14 @@ export default {
       item = {
         id: item.id,
         name: item.name,
-        weekday: this.weekdayAsInt(item.weekday),
+        weekday: item.weekday ? this.weekdayAsInt(item.weekday) : undefined,
         period: item.period,
         timeStart: item.timeStart,
         timeEnd: item.timeEnd,
         timeGrid: item.timeGrid?.id,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+	    console.trace(item);
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
     formatTimeGrid(item) {
       if (!item) return null;
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
index 8a764a73..324d35b3 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/break.graphql
@@ -58,7 +58,7 @@ mutation deleteBreakSlots($ids: [ID]!) {
 }
 
 mutation updateBreakSlots($input: [BatchPatchBreakSlotInput]!) {
-  batchMutation: updateBreakSlots(input: $input) {
+  updateBreakSlots(input: $input) {
     items: breakSlots {
       id
       name
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
index 3c6b2362..f9b8f08e 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/slot.graphql
@@ -57,7 +57,7 @@ mutation deleteSlots($ids: [ID]!) {
 }
 
 mutation updateSlots($input: [BatchPatchSlotInput]!) {
-  batchMutation: updateSlots(input: $input) {
+  updateSlots(input: $input) {
     items: slots {
       id
       name
diff --git a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
index 200e0150..e4825810 100644
--- a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
+++ b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
@@ -159,7 +159,7 @@ import { rooms } from "aleksis.core/components/room/room.graphql";
 import { breakSlots } from "../breaks_and_slots/break.graphql";
 import {
   subjects,
-  createSubject,
+  createSubjects,
 } from "aleksis.apps.cursus/components/subject.graphql";
 
 import { RRule } from "rrule";
@@ -199,7 +199,7 @@ export default {
       },
       subject: {
         gqlQuery: subjects,
-        gqlCreateMutation: createSubject,
+        gqlCreateMutation: createSubjects,
         transformCreateData(item) {
           return { ...item, parent: item.parent?.id };
         },
@@ -304,7 +304,7 @@ export default {
         subject: item.subject?.id,
         recurrence: this.getRRule(item.breakSlot.timeGrid).toString(),
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql b/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
index c37ad32d..12cda730 100644
--- a/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/supervision/supervision.graphql
@@ -62,7 +62,7 @@ mutation deleteSupervisions($ids: [ID]!) {
 }
 
 mutation updateSupervisions($input: [BatchPatchSupervisionInput]!) {
-  batchMutation: updateSupervisions(input: $input) {
+  updateSupervisions(input: $input) {
     items: supervisions {
       ...supervisionFields
     }
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
index dfac53ef..048f37c0 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
@@ -207,7 +207,7 @@ export default {
         validityRange: item.validityRange?.id,
         lessonQuota: item.lessonQuota,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => !(value === undefined)));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
   },
   apollo: {
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
index 6b1e3b0a..885c6d33 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
@@ -73,7 +73,7 @@ mutation createTimeboundCourseConfigs(
 
 mutation deleteTimeboundCourseConfigs($ids: [ID]!) {
   deleteTimeboundCourseConfigs(ids: $ids) {
-    ok
+    deletionCount
   }
 }
 
diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 92283488..cde02b2b 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -2,13 +2,13 @@
 import { defineComponent } from "vue";
 import {
   courses,
-  createLesson,
-  deleteLesson,
+  createLessons,
+  deleteLessons,
   gqlGroups,
   lessonObjects,
   moveLesson,
   overlayLessons,
-  updateLesson,
+  updateLessons,
 } from "./timetableManagement.graphql";
 import { gqlTeachers } from "../helper.graphql";
 import { timeGrids } from "../validity_range/validityRange.graphql";
@@ -56,9 +56,9 @@ export default defineComponent({
       courseSearch: null,
       lessonsUsed: {},
       lessonQuotaTotal: 0,
-      deleteMutation: deleteLesson,
+      deleteMutation: deleteLessons,
       deleteDialog: false,
-      itemToDelete: null,
+      itemsToDelete: [],
       selectedObject: null,
       selectedObjectType: null,
       selectedObjectTitle: "",
@@ -90,7 +90,7 @@ export default defineComponent({
             value: "rooms",
           },
         ],
-        mutation: updateLesson,
+        mutation: updateLessons,
       },
       draggedItem: null,
       overlayLessons: [],
@@ -385,15 +385,15 @@ export default defineComponent({
           .mutate({
             mutation: moveLesson,
             variables: {
-              id: eventData.data.id,
               input: {
+                id: eventData.data.id,
                 slotStart: newStartSlotId,
                 slotEnd: newEndSlotId,
               },
             },
             optimisticResponse: {
-              updateLesson: {
-                lesson: {
+              updateLessons: {
+                lessons: {
                   ...eventData.data,
                   slotStart: newStartSlot,
                   slotEnd: newEndSlot,
@@ -406,7 +406,7 @@ export default defineComponent({
               store,
               {
                 data: {
-                  updateLesson: { lesson },
+                  updateLessons: { lessons },
                 },
               },
             ) {
@@ -424,14 +424,16 @@ export default defineComponent({
                 return;
               }
 
-              const index = storedData.lessonObjects.findIndex(
-                (lessonObject) => lessonObject.id === lesson.id,
-              );
-              storedData.lessonObjects[index].slotStart = lesson.slotStart;
-              storedData.lessonObjects[index].slotEnd = lesson.slotEnd;
+              lessons.forEach((lesson) => {
+                const index = storedData.lessonObjects.findIndex(
+                  (lessonObject) => lessonObject.id === lesson.id,
+                );
+                storedData.lessonObjects[index].slotStart = lesson.slotStart;
+                storedData.lessonObjects[index].slotEnd = lesson.slotEnd;
 
-              // Write data back to the cache
-              store.writeQuery({ ...query, data: storedData });
+                // Write data back to the cache
+                store.writeQuery({ ...query, data: storedData });
+              });
             },
           })
           .then(() => {
@@ -454,7 +456,7 @@ export default defineComponent({
         const recurrenceString = rule.toString();
         this.$apollo
           .mutate({
-            mutation: createLesson,
+            mutation: createLessons,
             variables: {
               input: {
                 slotStart: newStartSlotId,
@@ -468,8 +470,8 @@ export default defineComponent({
               },
             },
             optimisticResponse: {
-              createLesson: {
-                lesson: {
+              createLessons: {
+                lessons: {
                   id: "temporary-lesson-id-" + crypto.randomUUID(),
                   slotStart: newStartSlot,
                   slotEnd: newEndSlot,
@@ -491,7 +493,7 @@ export default defineComponent({
               store,
               {
                 data: {
-                  createLesson: { lesson },
+                  createLessons: { lessons },
                 },
               },
             ) {
@@ -509,7 +511,7 @@ export default defineComponent({
                 return;
               }
 
-              storedData.lessonObjects.push(lesson);
+              storedData.lessonObjects.concat(lessons);
 
               // Write data back to the cache
               store.writeQuery({ ...query, data: storedData });
@@ -558,15 +560,15 @@ export default defineComponent({
         .mutate({
           mutation: moveLesson,
           variables: {
-            id: lesson.id,
             input: {
+              id: lesson.id,
               slotStart: slotStart.id,
               slotEnd: slotEnd.id,
             },
           },
           optimisticResponse: {
-            updateLesson: {
-              lesson: {
+            updateLessons: {
+              lessons: {
                 ...lesson,
                 slotStart: slotStart,
                 slotEnd: slotEnd,
@@ -579,7 +581,7 @@ export default defineComponent({
             store,
             {
               data: {
-                updateLesson: { lesson },
+                updateLessons: { lessons },
               },
             },
           ) {
@@ -597,14 +599,16 @@ export default defineComponent({
               return;
             }
 
-            const index = storedData.lessonObjects.findIndex(
-              (lessonObject) => lessonObject.id === lesson.id,
-            );
-            storedData.lessonObjects[index].slotStart = lesson.slotStart;
-            storedData.lessonObjects[index].slotEnd = lesson.slotEnd;
+            lessons.forEach((lesson) => {
+              const index = storedData.lessonObjects.findIndex(
+                (lessonObject) => lessonObject.id === lesson.id,
+              );
+              storedData.lessonObjects[index].slotStart = lesson.slotStart;
+              storedData.lessonObjects[index].slotEnd = lesson.slotEnd;
 
-            // Write data back to the cache
-            store.writeQuery({ ...query, data: storedData });
+              // Write data back to the cache
+              store.writeQuery({ ...query, data: storedData });
+            });
           },
         })
         .then(() => {
@@ -647,7 +651,7 @@ export default defineComponent({
       this.changeLessonSlots(lesson, lesson.slotStart, slotEnd);
     },
     deleteLesson(lesson) {
-      this.itemToDelete = lesson;
+      this.itemsToDelete = [lesson];
       this.deleteDialog = true;
     },
     teacherClick(teacher) {
@@ -1083,13 +1087,17 @@ export default defineComponent({
     </mobile-fullscreen-dialog>
 
     <delete-dialog
-      :gql-mutation="deleteMutation"
-      :gql-query="$apollo.queries.lessonObjects"
+      :gql-delete-mutation="deleteMutation"
+      :affected-query="$apollo.queries.lessonObjects"
       v-model="deleteDialog"
-      :item="itemToDelete"
+      :items="itemsToDelete"
     >
       <template #body>
-        {{ itemToDelete.subject?.name || itemToDelete.course.subject.name }}
+        <ul class="text-body-1">
+          <li v-for="item in itemsToDelete" :key="'delete-' + item.id">
+            {{ item.subject?.name || item.course.subject.name }}
+          </li>
+        </ul>
       </template>
     </delete-dialog>
 
diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql b/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
index 3c6a73a6..8f29aa75 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/timetableManagement.graphql
@@ -198,9 +198,9 @@ mutation createLessons($input: [BatchCreateLessonInput]!) {
   }
 }
 
-mutation moveLesson($id: ID!, $input: PatchLessonInput!) {
-  updateLesson(id: $id, input: $input) {
-    lesson {
+mutation moveLesson($input: [BatchPatchLessonInput]!) {
+  updateLessons(input: $input) {
+    lessons {
       id
       slotStart {
         id
@@ -245,8 +245,8 @@ mutation updateLessons($input: [BatchPatchLessonInput]!) {
   }
 }
 
-mutation deleteLesson($ids: [ID]!) {
+mutation deleteLessons($ids: [ID]!) {
   deleteLessons(ids: $ids) {
-    ok
+    deletionCount
   }
 }
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/TimeGridField.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/TimeGridField.vue
index 03352820..69246ca8 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/TimeGridField.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/TimeGridField.vue
@@ -5,7 +5,7 @@ import ValidityRangeField from "./ValidityRangeField.vue";
 
 <script>
 import { defineComponent } from "vue";
-import { timeGrids, createTimeGrid } from "./validityRange.graphql";
+import { timeGrids, createTimeGrids } from "./validityRange.graphql";
 import { gqlGroups } from "../helper.graphql";
 
 export default defineComponent({
@@ -38,7 +38,7 @@ export default defineComponent({
       ],
       i18nKey: "lesrooster.validity_range.time_grid",
       gqlQuery: timeGrids,
-      gqlCreateMutation: createTimeGrid,
+      gqlCreateMutation: createTimeGrids,
       defaultItem: {
         isGeneric: false,
         group: null,
@@ -63,7 +63,7 @@ export default defineComponent({
         (group) =>
           !this.$refs.field.items.some(
             (timeGrid) =>
-              timeGrid.validityRange.id === itemModel.validityRange.id &&
+              timeGrid.validityRange.id === itemModel.validityRange?.id &&
               timeGrid.group !== null &&
               timeGrid.group.id === group.id,
           ),
@@ -75,7 +75,7 @@ export default defineComponent({
       // Is there a timeGrid that has the same validityRange as we and no group?
       return this.$refs.field.items.some(
         (timeGrid) =>
-          timeGrid.validityRange.id === itemModel.validityRange.id &&
+          timeGrid.validityRange.id === itemModel.validityRange?.id &&
           timeGrid.group === null,
       );
     },
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
index 1dbf398e..a5f0b326 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
@@ -212,8 +212,8 @@ import {
   createValidityRanges,
   deleteValidityRanges,
   updateValidityRanges,
-  createTimeGrid,
-  deleteTimeGrid,
+  createTimeGrids,
+  deleteTimeGrids,
 } from "./validityRange.graphql";
 import { gqlGroups } from "../helper.graphql";
 
@@ -265,13 +265,13 @@ export default {
         open: false,
         deleteOpen: false,
         deleteItem: null,
-        deleteMutation: deleteTimeGrid,
+        deleteMutation: deleteTimeGrids,
         range: null,
         object: {
           isGeneric: false,
           group: null,
         },
-        mutation: createTimeGrid,
+        mutation: createTimeGrids,
         fields: [
           {
             text: this.$t(
@@ -324,7 +324,7 @@ export default {
         schoolTerm: item.schoolTerm?.id,
         status: item.status?.toLowerCase(),
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
     createTimeGridFor(validityRange) {
       this.timeGrids.range = validityRange;
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
index c1aaa47a..da6cfcb4 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
@@ -110,7 +110,7 @@ export default {
         dateEnd: item.dateEnd,
         schoolTerm: item.schoolTerm.id,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value === undefined));
+      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql b/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
index d698ab15..7124e149 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/validityRange.graphql
@@ -55,7 +55,7 @@ mutation deleteValidityRanges($ids: [ID]!) {
 }
 
 mutation updateValidityRanges($input: [BatchPatchValidityRangeInput]!) {
-  batchMutation: updateValidityRanges(input: $input) {
+  utation: updateValidityRanges(input: $input) {
     items: validityRanges {
       id
       name
@@ -89,9 +89,9 @@ query currentValidityRange {
   }
 }
 
-mutation createTimeGrid($input: CreateTimeGridInput!) {
-  createTimeGrid(input: $input) {
-    item: timeGrid {
+mutation createTimeGrids($input: [BatchCreateTimeGridInput]!) {
+  createTimeGrids(input: $input) {
+    items: timeGrids {
       id
       group {
         id
@@ -105,9 +105,9 @@ mutation createTimeGrid($input: CreateTimeGridInput!) {
   }
 }
 
-mutation deleteTimeGrid($id: ID!) {
-  deleteTimeGrid(id: $id) {
-    ok
+mutation deleteTimeGrids($ids: [ID]!) {
+  deleteTimeGrids(id: $ids) {
+    deletionCount
   }
 }
 
-- 
GitLab


From 9c9a8c6ec389d3c89af4be15e3c402f8a80639b2 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Sat, 17 Feb 2024 14:21:39 +0100
Subject: [PATCH 06/17] Fix permission check in timebound course config field
 in subjects query

---
 aleksis/apps/lesrooster/schema/timebound_course_config.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/lesrooster/schema/timebound_course_config.py b/aleksis/apps/lesrooster/schema/timebound_course_config.py
index 705f08ec..b0c1feaa 100644
--- a/aleksis/apps/lesrooster/schema/timebound_course_config.py
+++ b/aleksis/apps/lesrooster/schema/timebound_course_config.py
@@ -70,12 +70,13 @@ class LesroosterExtendedCourseType(CourseType):
 
     @staticmethod
     def resolve_lr_timebound_course_configs(root, info, **kwargs):
-        if not info.context.user.has_perm("lesrostter.view_timeboundcourseconfig_rule"):
+        if not info.context.user.has_perm("lesrooster.view_timeboundcourseconfig_rule"):
             return get_objects_for_user(
                 info.context.user,
                 "lesrooster.view_timeboundcourseconfig",
                 root.lr_timebound_course_configs.all(),
             )
+        return root.lr_timebound_course_configs.all()
 
 
 class LesroosterExtendedSubjectType(SubjectType):
-- 
GitLab


From 312a8d718b76c7b32e8cd4b4e0b1a83f51c384b0 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sat, 17 Feb 2024 21:47:52 +0100
Subject: [PATCH 07/17] Implement the changes of using batch mutations in
 optimistic response (previously forgotten)

---
 .../TimetableManagement.vue                   | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index cde02b2b..7d641c59 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -393,13 +393,13 @@ export default defineComponent({
             },
             optimisticResponse: {
               updateLessons: {
-                lessons: {
+                lessons: [{
                   ...eventData.data,
                   slotStart: newStartSlot,
                   slotEnd: newEndSlot,
                   isOptimistic: true,
-                },
-                __typename: "LessonPatchMutation",
+                }],
+                __typename: "LessonBatchPatchMutation",
               },
             },
             update(
@@ -471,7 +471,7 @@ export default defineComponent({
             },
             optimisticResponse: {
               createLessons: {
-                lessons: {
+                items: [{
                   id: "temporary-lesson-id-" + crypto.randomUUID(),
                   slotStart: newStartSlot,
                   slotEnd: newEndSlot,
@@ -485,15 +485,15 @@ export default defineComponent({
                   canDelete: true,
                   recurrence: recurrenceString,
                   __typename: "LessonType",
-                },
-                __typename: "LessonCreateMutation",
+                }],
+                __typename: "LessonBatchCreateMutation",
               },
             },
             update(
               store,
               {
                 data: {
-                  createLessons: { lessons },
+                  createLessons: { items },
                 },
               },
             ) {
@@ -511,7 +511,7 @@ export default defineComponent({
                 return;
               }
 
-              storedData.lessonObjects.concat(lessons);
+              items.forEach(lesson => storedData.lessonObjects.push(lesson));
 
               // Write data back to the cache
               store.writeQuery({ ...query, data: storedData });
@@ -568,13 +568,13 @@ export default defineComponent({
           },
           optimisticResponse: {
             updateLessons: {
-              lessons: {
+              lessons: [{
                 ...lesson,
                 slotStart: slotStart,
                 slotEnd: slotEnd,
                 isOptimistic: true,
-              },
-              __typename: "LessonPatchMutation",
+              }],
+              __typename: "LessonBatchPatchMutation",
             },
           },
           update(
-- 
GitLab


From 8d15a7ca00ce2b7e95dba2f69b1b2089173e9057 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sat, 17 Feb 2024 21:48:19 +0100
Subject: [PATCH 08/17] Fix vue errors regarding keys of rooms

---
 .../timetable_management/TimetableOverlayCard.vue        | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableOverlayCard.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableOverlayCard.vue
index 8d05ecb6..92de1150 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableOverlayCard.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableOverlayCard.vue
@@ -45,9 +45,10 @@ export default {
       );
     },
     rooms() {
-      return this.lesson.rooms.filter(
+      // dragged item may be a course which doesn't have a field rooms
+      return this.lesson.rooms?.filter(
         (lessonRoom) =>
-          !!this.draggedItem.rooms.find((room) => room.id === lessonRoom.id),
+          !!this.draggedItem.rooms?.find((room) => room.id === lessonRoom.id),
       );
     },
     teachers() {
@@ -68,7 +69,7 @@ export default {
       v-for="room in rooms"
       icon="mdi-home-off-outline"
       color="warning"
-      :key="room.id"
+      :key="'room-' + room.id"
     >
       <colored-short-name-chip class="short" :item="room" :elevation="0" />
     </blocking-card>
@@ -76,7 +77,7 @@ export default {
       v-for="teacher in teachers"
       icon="mdi-account-off-outline"
       color="warning"
-      :key="teacher.id"
+      :key="'teacher-' + teacher.id"
     >
       <colored-short-name-chip class="short" :item="teacher" :elevation="0" />
     </blocking-card>
-- 
GitLab


From 919915ba47323aa080fa53c69ae5abd2b624524a Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sun, 18 Feb 2024 19:46:35 +0100
Subject: [PATCH 09/17] Stop miniplans from not showing up due to duplicate
 keys

---
 .../components/timetable_management/TimetableManagement.vue    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 7d641c59..5230d19e 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -64,6 +64,7 @@ export default defineComponent({
       selectedObjectTitle: "",
       selectedObjectDialogOpen: false,
       selectedObjectDialogTab: null,
+      timeGrids: [],
       groups: [],
       selectedGroup: null,
       lessonEdit: {
@@ -1067,7 +1068,7 @@ export default defineComponent({
         </v-card-title>
         <v-card-text>
           <v-tabs-items v-model="selectedObjectDialogTab">
-            <v-tab-item v-for="timeGrid in timeGrids" :key="timeGrid.id">
+            <v-tab-item v-for="timeGrid in timeGrids" :key="'tabItem-' + timeGrid.id">
               <teacher-time-table
                 v-if="internalTimeGrid && selectedObjectType === 'teacher'"
                 :teacher-id="selectedObject"
-- 
GitLab


From 44e0fed71cc0e49093c01de1f239d441085ea2b3 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sun, 18 Feb 2024 19:47:08 +0100
Subject: [PATCH 10/17] Make Timetable creation fullwidth

---
 aleksis/apps/lesrooster/frontend/index.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/aleksis/apps/lesrooster/frontend/index.js b/aleksis/apps/lesrooster/frontend/index.js
index 1a82aec7..5f072d56 100644
--- a/aleksis/apps/lesrooster/frontend/index.js
+++ b/aleksis/apps/lesrooster/frontend/index.js
@@ -58,6 +58,7 @@ export default {
         toolbarTitle: "lesrooster.timetable_management.menu_title",
         icon: "mdi-magnet",
         permission: "lesrooster.plan_timetables_rule",
+        fullWidth: true,
       },
       children: [
         {
@@ -67,7 +68,10 @@ export default {
           name: "lesrooster.timetable_management",
           props: true,
           meta: {
+            titleKey: "lesrooster.timetable_management.menu_title",
+            toolbarTitle: "lesrooster.timetable_management.menu_title",
             permission: "lesrooster.plan_timetables_rule",
+            fullWidth: true,
           },
         },
       ],
-- 
GitLab


From a06aebafd56dd28f62e018d550296e9b0cf80101 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Tue, 20 Feb 2024 11:43:31 +0100
Subject: [PATCH 11/17] Auto-select first tab

---
 .../components/timetable_management/TimetableManagement.vue     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 5230d19e..9e3f70f1 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -63,7 +63,7 @@ export default defineComponent({
       selectedObjectType: null,
       selectedObjectTitle: "",
       selectedObjectDialogOpen: false,
-      selectedObjectDialogTab: null,
+      selectedObjectDialogTab: 0,
       timeGrids: [],
       groups: [],
       selectedGroup: null,
-- 
GitLab


From e36b16fb19e60114a8185794999fa955e1836ca4 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Tue, 20 Feb 2024 11:43:49 +0100
Subject: [PATCH 12/17] Make all iteration keys unique

---
 .../components/timetable_management/TimetableManagement.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 9e3f70f1..536ed145 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -872,7 +872,7 @@ export default defineComponent({
         <div id="periods">
           <period-card
             v-for="(period, index) in periods"
-            :key="period"
+            :key="'period-' + period"
             :period="period"
             :weekdays="weekdays"
             :time-ranges="
@@ -974,7 +974,7 @@ export default defineComponent({
               :periods="periods"
               :weekdays="weekdays"
               :lesson="overlayLesson"
-              :key="overlayLesson.id"
+              :key="'overlay-' + overlayLesson.id"
             />
           </template>
         </drag-grid>
@@ -1061,7 +1061,7 @@ export default defineComponent({
             v-if="timeGrids && timeGrids.length > 1"
             class="width-max-content"
           >
-            <v-tab v-for="timeGrid in timeGrids" :key="timeGrid.id">
+            <v-tab v-for="timeGrid in timeGrids" :key="'tabSelector-' + timeGrid.id">
               {{ formatTimeGrid(timeGrid) }}
             </v-tab>
           </v-tabs>
-- 
GitLab


From 8931e0b678584520a8fe1c36570658bd74ce4266 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Tue, 20 Feb 2024 11:52:02 +0100
Subject: [PATCH 13/17] Prevent duplicate entries in miniplans and overlays

---
 aleksis/apps/lesrooster/schema/__init__.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py
index a5912290..44c4167a 100644
--- a/aleksis/apps/lesrooster/schema/__init__.py
+++ b/aleksis/apps/lesrooster/schema/__init__.py
@@ -225,7 +225,7 @@ class Query(graphene.ObjectType):
             Q(teachers=teacher) | Q(course__teachers=teacher),
             slot_start__time_grid_id=time_grid,
             slot_end__time_grid_id=time_grid,
-        )
+        ).distinct()
 
     @staticmethod
     def resolve_lesson_objects_for_room(root, info, room, time_grid):
@@ -236,7 +236,7 @@ class Query(graphene.ObjectType):
             rooms=room,
             slot_start__time_grid_id=time_grid,
             slot_end__time_grid_id=time_grid,
-        )
+        ).distinct()
 
     @staticmethod
     def resolve_lessons_objects_for_rooms_or_teachers(
@@ -253,7 +253,7 @@ class Query(graphene.ObjectType):
             Q(rooms__in=rooms) | Q(teachers__in=teachers) | Q(course__teachers__in=teachers),
             slot_start__time_grid_id=time_grid,
             slot_end__time_grid_id=time_grid,
-        )
+        ).distinct()
 
     @staticmethod
     def resolve_groups_by_time_grid(root, info, time_grid=None, **kwargs):
-- 
GitLab


From 71fbf4e0dfde7e810b021b92b67bbb59602e848f Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Fri, 23 Feb 2024 16:53:43 +0100
Subject: [PATCH 14/17] Send ID when editing lesson

---
 .../components/timetable_management/TimetableManagement.vue | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 536ed145..3d808708 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -710,6 +710,11 @@ export default defineComponent({
       const index = storedData.lessonObjects.findIndex(
         (lessonObject) => lessonObject.id === lesson.id,
       );
+
+      if (index === -1) {
+        return;
+      }
+
       storedData.lessonObjects[index].subject = lesson.subject;
       storedData.lessonObjects[index].teachers = lesson.teachers;
       storedData.lessonObjects[index].rooms = lesson.rooms;
@@ -729,6 +734,7 @@ export default defineComponent({
     },
     lessonEditGetPatchData(lesson) {
       return {
+        id: lesson.id,
         subject: lesson.subject.id,
         teachers: lesson.teachers.map((teacher) => teacher.id),
         rooms: lesson.rooms.map((room) => room.id),
-- 
GitLab


From 186edb338023767627762bd0e9b3a7cc492cc911 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Fri, 23 Feb 2024 16:58:08 +0100
Subject: [PATCH 15/17] Skip queries until group is loaded fully

---
 .../components/timetable_management/TimetableManagement.vue    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 3d808708..443b5b53 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -234,7 +234,8 @@ export default defineComponent({
   },
   computed: {
     readyForQueries() {
-      return this.internalTimeGrid !== null && this.selectedGroup !== null;
+      // Non-typesafe check to also handle undefined
+      return this.internalTimeGrid != null && this.selectedGroup != null && this.selectedGroup.id != null;
     },
     lessons() {
       return this.lessonObjects
-- 
GitLab


From dc21fc185ea320b665d6b5699687cfe65a73ac36 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sat, 24 Feb 2024 12:47:42 +0100
Subject: [PATCH 16/17] Bump version of AlekSIS-Core

---
 pyproject.toml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 48885bcb..b451c0d2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Lesrooster"
-version = "0.1"
+version = "0.1.0.dev0"
 packages = [
     { include = "aleksis" }
 ]
@@ -32,9 +32,10 @@ priority = "primary"
 name = "gitlab"
 url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple"
 priority = "supplemental"
+
 [tool.poetry.dependencies]
 python = "^3.10"
-AlekSIS-Core = "^4.0.0.dev3"
+AlekSIS-Core = "^4.0.0.dev4"
 AlekSIS-App-Chronos = "^4.0.0.dev1"
 AlekSIS-App-Cursus = "^0.1.dev0"
 django-recurrence = "^1.11.1"
-- 
GitLab


From 18364fba6f5554ea52b5f4248a7ace73aa827320 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sat, 24 Feb 2024 14:27:19 +0100
Subject: [PATCH 17/17] Reformat

---
 .../components/breaks_and_slots/Break.vue     |  4 +-
 .../breaks_and_slots/LesroosterSlot.vue       | 13 ++--
 .../components/lesson_raster/LessonRaster.vue |  2 +-
 .../components/supervision/Supervision.vue    |  4 +-
 .../TimeboundCourseConfigCRUDTable.vue        |  4 +-
 .../TimetableManagement.vue                   | 78 +++++++++++--------
 .../validity_range/ValidityRange.vue          |  4 +-
 .../validity_range/ValidityRangeField.vue     |  4 +-
 aleksis/apps/lesrooster/schema/__init__.py    |  1 -
 aleksis/apps/lesrooster/schema/lesson.py      |  1 -
 10 files changed, 68 insertions(+), 47 deletions(-)

diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
index 4ece76f3..8c39f208 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/Break.vue
@@ -62,7 +62,9 @@ export default {
         timeEnd: item.timeEnd,
         timeGrid: item.timeGrid?.id,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
index 833ee1d3..83da9f69 100644
--- a/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
+++ b/aleksis/apps/lesrooster/frontend/components/breaks_and_slots/LesroosterSlot.vue
@@ -113,12 +113,7 @@ import TimeGridField from "../validity_range/TimeGridField.vue";
 </template>
 
 <script>
-import {
-  slots,
-  createSlots,
-  deleteSlots,
-  updateSlots,
-} from "./slot.graphql";
+import { slots, createSlots, deleteSlots, updateSlots } from "./slot.graphql";
 
 export default {
   name: "LesroosterSlot",
@@ -198,8 +193,10 @@ export default {
         timeEnd: item.timeEnd,
         timeGrid: item.timeGrid?.id,
       };
-	    console.trace(item);
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      console.trace(item);
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
     formatTimeGrid(item) {
       if (!item) return null;
diff --git a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
index 989d5f49..bc0205fe 100644
--- a/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
+++ b/aleksis/apps/lesrooster/frontend/components/lesson_raster/LessonRaster.vue
@@ -405,7 +405,7 @@ export default {
         });
     },
     deleteSlot(slot) {
-      this.itemsToDelete = [slot]
+      this.itemsToDelete = [slot];
       this.deleteDialog = true;
     },
     deleteSlotsOfDay(weekday) {
diff --git a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
index e4825810..2a25319d 100644
--- a/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
+++ b/aleksis/apps/lesrooster/frontend/components/supervision/Supervision.vue
@@ -304,7 +304,9 @@ export default {
         subject: item.subject?.id,
         recurrence: this.getRRule(item.breakSlot.timeGrid).toString(),
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
index 048f37c0..927935ec 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigCRUDTable.vue
@@ -207,7 +207,9 @@ export default {
         validityRange: item.validityRange?.id,
         lessonQuota: item.lessonQuota,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
   },
   apollo: {
diff --git a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
index 443b5b53..1fbc0d46 100644
--- a/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timetable_management/TimetableManagement.vue
@@ -235,7 +235,11 @@ export default defineComponent({
   computed: {
     readyForQueries() {
       // Non-typesafe check to also handle undefined
-      return this.internalTimeGrid != null && this.selectedGroup != null && this.selectedGroup.id != null;
+      return (
+        this.internalTimeGrid != null &&
+        this.selectedGroup != null &&
+        this.selectedGroup.id != null
+      );
     },
     lessons() {
       return this.lessonObjects
@@ -395,12 +399,14 @@ export default defineComponent({
             },
             optimisticResponse: {
               updateLessons: {
-                lessons: [{
-                  ...eventData.data,
-                  slotStart: newStartSlot,
-                  slotEnd: newEndSlot,
-                  isOptimistic: true,
-                }],
+                lessons: [
+                  {
+                    ...eventData.data,
+                    slotStart: newStartSlot,
+                    slotEnd: newEndSlot,
+                    isOptimistic: true,
+                  },
+                ],
                 __typename: "LessonBatchPatchMutation",
               },
             },
@@ -473,21 +479,23 @@ export default defineComponent({
             },
             optimisticResponse: {
               createLessons: {
-                items: [{
-                  id: "temporary-lesson-id-" + crypto.randomUUID(),
-                  slotStart: newStartSlot,
-                  slotEnd: newEndSlot,
-                  subject: eventData.data.subject,
-                  teachers: eventData.data.teachers,
-                  // rooms: eventData.data.rooms,
-                  rooms: [],
-                  course: eventData.data,
-                  isOptimistic: true,
-                  canEdit: true,
-                  canDelete: true,
-                  recurrence: recurrenceString,
-                  __typename: "LessonType",
-                }],
+                items: [
+                  {
+                    id: "temporary-lesson-id-" + crypto.randomUUID(),
+                    slotStart: newStartSlot,
+                    slotEnd: newEndSlot,
+                    subject: eventData.data.subject,
+                    teachers: eventData.data.teachers,
+                    // rooms: eventData.data.rooms,
+                    rooms: [],
+                    course: eventData.data,
+                    isOptimistic: true,
+                    canEdit: true,
+                    canDelete: true,
+                    recurrence: recurrenceString,
+                    __typename: "LessonType",
+                  },
+                ],
                 __typename: "LessonBatchCreateMutation",
               },
             },
@@ -513,7 +521,7 @@ export default defineComponent({
                 return;
               }
 
-              items.forEach(lesson => storedData.lessonObjects.push(lesson));
+              items.forEach((lesson) => storedData.lessonObjects.push(lesson));
 
               // Write data back to the cache
               store.writeQuery({ ...query, data: storedData });
@@ -570,12 +578,14 @@ export default defineComponent({
           },
           optimisticResponse: {
             updateLessons: {
-              lessons: [{
-                ...lesson,
-                slotStart: slotStart,
-                slotEnd: slotEnd,
-                isOptimistic: true,
-              }],
+              lessons: [
+                {
+                  ...lesson,
+                  slotStart: slotStart,
+                  slotEnd: slotEnd,
+                  isOptimistic: true,
+                },
+              ],
               __typename: "LessonBatchPatchMutation",
             },
           },
@@ -1068,14 +1078,20 @@ export default defineComponent({
             v-if="timeGrids && timeGrids.length > 1"
             class="width-max-content"
           >
-            <v-tab v-for="timeGrid in timeGrids" :key="'tabSelector-' + timeGrid.id">
+            <v-tab
+              v-for="timeGrid in timeGrids"
+              :key="'tabSelector-' + timeGrid.id"
+            >
               {{ formatTimeGrid(timeGrid) }}
             </v-tab>
           </v-tabs>
         </v-card-title>
         <v-card-text>
           <v-tabs-items v-model="selectedObjectDialogTab">
-            <v-tab-item v-for="timeGrid in timeGrids" :key="'tabItem-' + timeGrid.id">
+            <v-tab-item
+              v-for="timeGrid in timeGrids"
+              :key="'tabItem-' + timeGrid.id"
+            >
               <teacher-time-table
                 v-if="internalTimeGrid && selectedObjectType === 'teacher'"
                 :teacher-id="selectedObject"
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
index a5f0b326..52a81562 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRange.vue
@@ -324,7 +324,9 @@ export default {
         schoolTerm: item.schoolTerm?.id,
         status: item.status?.toLowerCase(),
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
     createTimeGridFor(validityRange) {
       this.timeGrids.range = validityRange;
diff --git a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
index da6cfcb4..72ee6326 100644
--- a/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
+++ b/aleksis/apps/lesrooster/frontend/components/validity_range/ValidityRangeField.vue
@@ -110,7 +110,9 @@ export default {
         dateEnd: item.dateEnd,
         schoolTerm: item.schoolTerm.id,
       };
-      return Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== undefined));
+      return Object.fromEntries(
+        Object.entries(item).filter(([key, value]) => value !== undefined),
+      );
     },
   },
 };
diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py
index 44c4167a..d27e9288 100644
--- a/aleksis/apps/lesrooster/schema/__init__.py
+++ b/aleksis/apps/lesrooster/schema/__init__.py
@@ -49,7 +49,6 @@ from .supervision import (
 from .time_grid import (
     TimeGridBatchCreateMutation,
     TimeGridBatchDeleteMutation,
-    TimeGridBatchDeleteMutation,
     TimeGridType,
 )
 from .timebound_course_config import (
diff --git a/aleksis/apps/lesrooster/schema/lesson.py b/aleksis/apps/lesrooster/schema/lesson.py
index e9c87c77..a3ceb68e 100644
--- a/aleksis/apps/lesrooster/schema/lesson.py
+++ b/aleksis/apps/lesrooster/schema/lesson.py
@@ -13,7 +13,6 @@ from aleksis.core.schema.base import (
     OptimisticResponseTypeMixin,
     PermissionBatchDeleteMixin,
     PermissionBatchPatchMixin,
-    PermissionPatchMixin,
     PermissionsTypeMixin,
 )
 
-- 
GitLab