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 c6d572a1ba6b0d00ae11f088d8b135eb0be9053e..32fa24b2d472c4e6021c63019f41fc1f206e0070 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue
@@ -14,7 +14,8 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
       disable-pagination
       hide-default-footer
       :headers="headers"
-      :items="tableItems"
+      :items="items"
+      :loading="$apollo.queries.subjects.loading"
     >
       <template #top>
         <v-row>
@@ -33,11 +34,36 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
               item-value="id"
               return-object
               :disabled="$apollo.queries.groupsForPlanning.loading"
-              :label="$t('lesrooster.timebound_course_config.groups')"
-              :loading="$apollo.queries.groupsForPlanning.loading"
+              :label="
+                $t('lesrooster.timebound_course_config.filters.groups.label')
+              "
+              :loading="$apollo.queries.grouptemplatesForPlanning.loading"
               v-model="selectedGroups"
               class="mr-4"
-            />
+            >
+              <template #selection="{ item, index }">
+                <div v-if="selectedGroups.length === groupsForPlanning.length">
+                  <span v-if="index === 0" class="grey--text">
+                    {{
+                      $t(
+                        "lesrooster.timebound_course_config.filters.groups.all_groups",
+                      )
+                    }}
+                  </span>
+                </div>
+                <div v-else>
+                  <span v-if="index === 0">{{ item.shortName }}</span>
+                  <span v-if="index === 1" class="grey--text text-caption">
+                    {{
+                      $t(
+                        "lesrooster.timebound_course_config.filters.groups.other_groups",
+                        { groupCount: selectedGroups.length - 1 },
+                      )
+                    }}
+                  </span>
+                </div>
+              </template>
+            </v-autocomplete>
           </v-col>
 
           <v-col
@@ -48,26 +74,34 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
             <validity-range-field
               outlined
               filled
-              label="Select Validity Range"
+              :label="
+                $t('lesrooster.timebound_course_config.filters.validity_range')
+              "
               hide-details
               v-model="internalValidityRange"
               :loading="$apollo.queries.currentValidityRange.loading"
             />
           </v-col>
 
-          <v-spacer />
-
           <v-col
-            cols="8"
-            lg="3"
+            cols="6"
+            lg="2"
             class="d-flex justify-space-between flex-wrap align-center"
           >
-            <secondary-action-button
-              i18n-key="actions.copy_last_configuration"
-              block
-              class="mr-4"
-            />
+            <v-switch
+              v-model="includeChildGroups"
+              inset
+              :label="
+                $t(
+                  'lesrooster.timebound_course_config.filters.include_child_groups',
+                )
+              "
+              :loading="$apollo.queries.subjects.loading"
+            ></v-switch>
           </v-col>
+
+          <v-spacer />
+
           <v-col
             cols="4"
             lg="1"
@@ -79,6 +113,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
                 !createdCourseConfigs.length &&
                 !createdCourses.length
               "
+              :loading="loading"
               @click="save"
             />
           </v-col>
@@ -100,12 +135,13 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
               v-for="(course, index) in value"
               :key="index"
               no-gutters
-              class="mt-2"
+              class="my-1"
             >
-              <v-col cols="6">
+              <v-col cols="4">
                 <positive-small-integer-field
                   dense
-                  filled
+                  outlined
+                  hide-details
                   class="mx-1"
                   :disabled="loading"
                   :value="
@@ -122,14 +158,16 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue";
                   "
                 />
               </v-col>
-              <v-col cols="6">
+              <v-col cols="8">
                 <v-autocomplete
-                  counter
                   dense
-                  filled
+                  outlined
                   multiple
+                  chips
+                  small-chips
+                  hide-details
                   :items="getTeacherList(item.subject.teachers)"
-                  item-text="fullName"
+                  item-text="shortName"
                   item-value="id"
                   class="mx-1"
                   :disabled="loading"
@@ -230,11 +268,14 @@ export default {
       subjects: [],
       editedCourseConfigs: [],
       createdCourseConfigs: [],
-      newCourses: [],
       createdCourses: [],
       currentCourse: null,
       currentSubject: null,
       loading: false,
+      includeChildGroups: true,
+      selectedGroupHeaders: [],
+      items: [],
+      groupCombinationsSet: new Set(),
     };
   },
   methods: {
@@ -243,15 +284,7 @@ export default {
     },
     getCurrentCourseConfig(course) {
       if (course.lrTimeboundCourseConfigs?.length) {
-        let currentCourseConfigs = course.lrTimeboundCourseConfigs.filter(
-          (timeboundConfig) =>
-            timeboundConfig.validityRange.id === this.internalValidityRange.id,
-        );
-        if (currentCourseConfigs.length) {
-          return currentCourseConfigs[0];
-        } else {
-          return null;
-        }
+        return course.lrTimeboundCourseConfigs[0];
       } else {
         return null;
       }
@@ -267,7 +300,6 @@ export default {
           this.createdCourses.push({
             subject: subject.id,
             groups: JSON.parse(header.value),
-            name: `${header.text}-${subject.name}`,
             schoolTerm: this.internalValidityRange.schoolTerm.id,
             ...newValue,
           });
@@ -275,11 +307,7 @@ export default {
           Object.assign(existingCreatedCourse, newValue);
         }
       } else {
-        if (
-          !course.lrTimeboundCourseConfigs?.filter(
-            (c) => c.validityRange.id === this.internalValidityRange?.id,
-          ).length
-        ) {
+        if (!course.lrTimeboundCourseConfigs?.length) {
           let existingCreatedCourseConfig = this.createdCourseConfigs.find(
             (c) =>
               c.course === course.id &&
@@ -363,66 +391,91 @@ export default {
       ];
     },
     addCourse(subject, groups) {
-      let courseSubjectGroup = this.newCourses.find(
-        (courseSubject) => courseSubject.subject === subject,
+      this.$set(
+        this.items.find((i) => i.subject.id === subject),
+        groups,
+        [{ teachers: [], newCourse: true }],
       );
-      if (courseSubjectGroup) {
-        if (courseSubjectGroup.groupCombinations) {
-          this.$set(courseSubjectGroup.groupCombinations, groups, [
-            { teachers: [], newCourse: true },
-          ]);
-        } else {
-          courseSubjectGroup.groupCombinations = {
-            [groups]: [{ teachers: [], newCourse: true }],
-          };
-        }
-      } else {
-        this.newCourses.push({
-          subject: subject,
-          groupCombinations: { [groups]: [{ teachers: [], newCourse: true }] },
+    },
+    generateTableItems(subjects) {
+      return subjects.map((subject) => {
+        let { courses, ...reducedSubject } = subject;
+        let groupCombinations = {};
+
+        courses.forEach((course) => {
+          const ownGroupIDs = course.groups.map((group) => group.id);
+          let groupIDs;
+
+          if (
+            this.includeChildGroups &&
+            ownGroupIDs.some((groupID) => !this.groupIDSet.has(groupID))
+          ) {
+            groupIDs = JSON.stringify(
+              course.groups
+                .flatMap((group) =>
+                  group.parentGroups.map((parentGroup) => parentGroup.id),
+                )
+                .sort(),
+            );
+          } else {
+            groupIDs = JSON.stringify(ownGroupIDs.sort());
+          }
+
+          if (!groupCombinations[groupIDs]) {
+            groupCombinations[groupIDs] = [];
+            if (course.groups.length > 1) {
+              this.groupCombinationsSet.add(groupIDs);
+            }
+            groupCombinations[groupIDs].push({
+              ...course,
+            });
+          } else if (
+            !groupCombinations[groupIDs].some((c) => c.id === course.id)
+          ) {
+            groupCombinations[groupIDs].push({
+              ...course,
+            });
+          }
         });
-      }
+
+        return {
+          subject: reducedSubject,
+          ...Object.fromEntries(
+            this.groupHeaders.map((header) => [header.value, []]),
+          ),
+          ...groupCombinations,
+        };
+      });
     },
   },
   computed: {
-    groupIDList() {
-      return this.selectedGroups.map((group) => group.id);
+    groupIDSet() {
+      return new Set(this.selectedGroups.map((group) => group.id));
     },
-    subjectGroupCombinations() {
-      return [].concat.apply(
-        [],
-        this.items.map((subject) => Object.keys(subject.groupCombinations)),
-      );
+    groupCombinationHeaders() {
+      return this.subjectGroupCombinations.map((combination) => {
+        let parsedCombination = JSON.parse(combination);
+        return {
+          text: parsedCombination
+            .map(
+              (groupID) =>
+                this.selectedGroups.find((group) => group.id === groupID)
+                  ?.shortName ||
+                this.selectedGroups.find((group) => group.id === groupID)
+                  ?.shortName,
+            )
+            .join(", "),
+          value: combination,
+        };
+      });
     },
     groupHeaders() {
-      return this.selectedGroups
-        .map((group) => ({
-          text: group.shortName,
-          value: JSON.stringify([group.id]),
-        }))
-        .concat(
-          this.subjectGroupCombinations.map((combination) => {
-            let parsedCombination = JSON.parse(combination);
-            return {
-              text: parsedCombination
-                .map(
-                  (groupID) =>
-                    this.groups.find((group) => group.id === groupID).shortName,
-                )
-                .join(", "),
-              value: combination,
-            };
-          }),
-        )
-        .filter(
-          (obj, index, self) =>
-            index === self.findIndex((o) => o.value === obj.value),
-        );
+      return [...this.selectedGroupHeaders, ...this.groupCombinationHeaders];
     },
     headers() {
       let groupHeadersWithWidth = this.groupHeaders.map((header) => ({
         ...header,
-        width: `${Math.max(95 / this.groupHeaders.length, 15)}vw`,
+        width: "15vw",
       }));
       return [
         {
@@ -432,52 +485,16 @@ export default {
         },
       ].concat(groupHeadersWithWidth);
     },
-    items() {
-      return this.subjects.map((subject) => {
-        let groupCombinations = {};
-
-        subject.courses.forEach((course) => {
-          let groupIds = JSON.stringify(
-            course.groups.map((group) => group.id).sort(),
-          );
-
-          if (!groupCombinations[groupIds]) {
-            groupCombinations[groupIds] = [];
-          }
-
-          if (!groupCombinations[groupIds].find((c) => c.id === course.id)) {
-            groupCombinations[groupIds].push({
-              ...course,
-            });
-          }
-        });
-
-        subject = {
-          ...subject,
-          groupCombinations: { ...groupCombinations },
-          newCourses: {
-            ...this.newCourses.find(
-              (courseSubject) => courseSubject.subject === subject.id,
-            )?.groupCombinations,
-          },
-        };
-
-        return subject;
-      });
+    subjectGroupCombinations() {
+      return Array.from(this.groupCombinationsSet);
     },
-    tableItems() {
-      return this.items.map((subject) => {
-        // eslint-disable-next-line no-unused-vars
-        let { courses, groupCombinations, ...reducedSubject } = subject;
-        return {
-          subject: reducedSubject,
-          ...Object.fromEntries(
-            this.groupHeaders.map((header) => [header.value, []]),
-          ),
-          ...subject.groupCombinations,
-          ...subject.newCourses,
-        };
-      });
+  },
+  watch: {
+    selectedGroups(newValue) {
+      this.selectedGroupHeaders = newValue.map((group) => ({
+        text: group.shortName,
+        value: JSON.stringify([group.id]),
+      }));
     },
   },
   apollo: {
@@ -488,27 +505,33 @@ export default {
         this.internalValidityRange = data.currentValidityRange;
       },
     },
-    groups: {
-      query: gqlGroups,
-    },
     groupsForPlanning: {
       query: gqlGroupsForPlanning,
       result({ data }) {
         if (!data) return;
-        console.log(data.groups);
         this.selectedGroups = data.groupsForPlanning;
       },
     },
     subjects: {
       query: subjects,
       skip() {
-        return !this.groupIDList.length;
+        return (
+          !this.groupIDSet.size ||
+          !this.internalValidityRange ||
+          !this.persons.length
+        );
       },
       variables() {
         return {
-          groups: this.groupIDList,
+          groups: Array.from(this.groupIDSet),
+          includeChildGroups: this.includeChildGroups,
+          validityRange: this.internalValidityRange.id,
         };
       },
+      result({ data }) {
+        if (!data) return;
+        this.items = this.generateTableItems(data.subjects);
+      },
     },
     persons: {
       query: gqlTeachers,
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 c70792897146cc84e6afb74ac070d92ab9d0dfd7..b5a2887b3ba595aaeb3b4a8027f63e9afc8ef4bb 100644
--- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
+++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql
@@ -23,6 +23,11 @@ fragment courseFields on LesroosterExtendedCourseType {
     id
     name
     shortName
+    parentGroups {
+      id
+      name
+      shortName
+    }
   }
   lessonQuota
 }
@@ -93,11 +98,19 @@ mutation updateTimeboundCourseConfigs(
   }
 }
 
-query subjects($orderBy: [String], $filters: JSONString, $groups: [ID]) {
+query subjects(
+  $orderBy: [String]
+  $filters: JSONString
+  $groups: [ID]
+  $includeChildGroups: Boolean!
+  $validityRange: ID!
+) {
   subjects: lesroosterExtendedSubjects(
     orderBy: $orderBy
     filters: $filters
     groups: $groups
+    includeChildGroups: $includeChildGroups
+    validityRange: $validityRange
   ) {
     ...subjectFields
     courses {
diff --git a/aleksis/apps/lesrooster/frontend/messages/de.json b/aleksis/apps/lesrooster/frontend/messages/de.json
index da44980d0e3f51e4ceccff6fb30c04ee4cb04835..4ffab852e92641de74f11567ccc0100d752e84e6 100644
--- a/aleksis/apps/lesrooster/frontend/messages/de.json
+++ b/aleksis/apps/lesrooster/frontend/messages/de.json
@@ -23,6 +23,31 @@
       "title": "Pause",
       "title_plural": "Pausen"
     },
+    "timebound_course_config": {
+      "crud_table_menu_title": "Kurskonfigurationen",
+      "raster_menu_title": "Kurse planen",
+      "title": "Kurskonfiguration",
+      "title_plural": "Kurskonfigurationen",
+      "lesson_quota": "Stundenpensum",
+      "course": "Kurs",
+      "teachers": "Lehrkräfte",
+      "teachers_for": "Lehrkräfte für",
+      "subject_teachers": "Fachlehrkräfte",
+      "all_teachers": "Alle Lehrkräfte",
+      "no_course_selected": "Kein Kurs ausgewählt",
+      "create_timebound_course_config": "Kurskonfiguration erstellen",
+      "subject": "Fach",
+      "filters": {
+        "include_child_groups": "Kurse von Kindgruppen einbeziehen",
+        "validity_range": "Gültigkeitszeitraum",
+        "groups": {
+          "label": "Gruppen",
+          "other_groups": " (+{groupCount} andere)",
+          "all_groups": "Alle ausgewählt"
+        }
+      }
+    },
+    },
     "lesson_raster": {
       "menu_title": "Stundenraster"
     },
diff --git a/aleksis/apps/lesrooster/frontend/messages/en.json b/aleksis/apps/lesrooster/frontend/messages/en.json
index 9a73841fd9d09a333e582359d3f4f60dfb8b5b18..5b6469f99911ebae10e1b9feb2625891fbcd7bac 100644
--- a/aleksis/apps/lesrooster/frontend/messages/en.json
+++ b/aleksis/apps/lesrooster/frontend/messages/en.json
@@ -78,16 +78,24 @@
       "raster_menu_title": "Plan courses",
       "title": "Timebound course config",
       "title_plural": "Timebound course configs",
-      "lesson_quota": "Scheduled lesson quota",
+      "lesson_quota": "Lessons",
       "course": "Course",
-      "groups": "Groups",
       "teachers": "Teachers",
       "teachers_for": "Teachers for",
       "subject_teachers": "Teachers for this subject",
       "all_teachers": "All teachers",
       "no_course_selected": "No course selected",
       "create_timebound_course_config": "Create timebound course config",
-      "subject": "Subject"
+      "subject": "Subject",
+      "filters": {
+        "include_child_groups": "Include courses from child groups",
+        "validity_range": "Validity range",
+        "groups": {
+          "label": "Groups",
+          "other_groups": " (+{groupCount} others)",
+          "all_groups": "All selected"
+        }
+      }
     },
     "lesson_raster": {
       "menu_title": "Lesson Raster"
diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py
index e8bd683f26defa9e0e20c491e0b6a7040decf7ad..51713021712c572feb2d47ee71e8fee0a5e6d013 100644
--- a/aleksis/apps/lesrooster/schema/__init__.py
+++ b/aleksis/apps/lesrooster/schema/__init__.py
@@ -111,7 +111,10 @@ class Query(graphene.ObjectType):
     current_validity_range = graphene.Field(ValidityRangeType)
 
     lesrooster_extended_subjects = FilterOrderList(
-        LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID)
+        LesroosterExtendedSubjectType,
+        groups=graphene.List(graphene.ID),
+        include_child_groups=graphene.Boolean(required=True),
+        validity_range=graphene.ID(required=True),
     )
 
     groups_by_time_grid = graphene.List(GroupType, time_grid=graphene.ID(required=True))
@@ -154,18 +157,29 @@ class Query(graphene.ObjectType):
         return Supervision.objects.all()
 
     @staticmethod
-    def resolve_lesrooster_extended_subjects(root, info, groups):
-        subjects = Subject.objects.all().prefetch_related(
+    def resolve_lesrooster_extended_subjects(
+        root, info, groups, include_child_groups, validity_range
+    ):
+        if include_child_groups:
+            courses = Course.objects.filter(
+                Q(groups__in=groups) | Q(groups__parent_groups__in=groups)
+            )
+        else:
+            courses = Course.objects.filter(groups__in=groups)
+        course_configs = TimeboundCourseConfig.objects.filter(validity_range=validity_range)
+        courses = get_objects_for_user(
+            info.context.user, "cursus.view_course", courses
+        ).prefetch_related(Prefetch("lr_timebound_course_configs", queryset=course_configs))
+        subjects = get_objects_for_user(
+            info.context.user, "cursus.view_subject", Subject.objects.all()
+        )
+
+        return subjects.prefetch_related(
             Prefetch(
                 "courses",
-                queryset=get_objects_for_user(
-                    info.context.user, "cursus.view_course", Course.objects.all()
-                ).filter(groups__in=groups),
+                queryset=courses,
             )
         )
-        if not info.context.user.has_perm("lesrooster.view_subject_rule"):
-            return get_objects_for_user(info.context.user, "cursus.view_subject", subjects)
-        return subjects
 
     @staticmethod
     def resolve_current_validity_range(root, info):
diff --git a/aleksis/apps/lesrooster/schema/timebound_course_config.py b/aleksis/apps/lesrooster/schema/timebound_course_config.py
index 1d1e07a5e05e6ea2d139d3eb8def7b248d6700e3..87426363328ce1c3d8b981a5f20649df32d64094 100644
--- a/aleksis/apps/lesrooster/schema/timebound_course_config.py
+++ b/aleksis/apps/lesrooster/schema/timebound_course_config.py
@@ -134,7 +134,8 @@ class CourseBatchCreateForSchoolTermMutation(graphene.Mutation):
             teachers = Person.objects.filter(pk__in=course_input.teachers)
 
             course = Course.objects.create(
-                name=course_input.name,
+                name=f"""{''.join(groups.values_list('short_name', flat=True)
+                .order_by('short_name'))}-{subject.name}""",
                 subject=subject,
                 lesson_quota=course_input.lesson_quota or None,
             )