diff --git a/config/default.schema.json b/config/default.schema.json index 671afe11fa..669fad5355 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -597,6 +597,11 @@ "type": "boolean", "default": false, "description": "Enables the new class list view" + }, + "FEATURE_GROUPS_IN_COURSE_ENABLED": { + "type": "boolean", + "default": false, + "description": "Enables to get groups of type class in courses" } }, "allOf": [ diff --git a/config/global.js b/config/global.js index 0b47c56e7d..738efbfe1a 100644 --- a/config/global.js +++ b/config/global.js @@ -40,6 +40,7 @@ const { FEATURE_ALERTS_ON_HOMEPAGE_ENABLED, FEATURE_BUTTONS_ON_LOGINPAGE_ENABLED, FEATURE_SHOW_NEW_CLASS_VIEW_ENABLED, + FEATURE_GROUPS_IN_COURSE_ENABLED, } = process.env; const exp = { @@ -82,6 +83,7 @@ const exp = { FEATURE_ALERTS_ON_HOMEPAGE_ENABLED, FEATURE_BUTTONS_ON_LOGINPAGE_ENABLED, FEATURE_SHOW_NEW_CLASS_VIEW_ENABLED, + FEATURE_GROUPS_IN_COURSE_ENABLED, }; // eslint-disable-next-line no-console diff --git a/controllers/courses.js b/controllers/courses.js index 871b95e9ae..91ab460b7b 100644 --- a/controllers/courses.js +++ b/controllers/courses.js @@ -17,6 +17,7 @@ const { logger, formatError } = require('../helpers'); const timesHelper = require('../helpers/timesHelper'); const OPTIONAL_COURSE_FEATURES = ['messenger', 'videoconference']; +const FEATURE_GROUPS_IN_COURSE_ENABLED = Configuration.get('FEATURE_GROUPS_IN_COURSE_ENABLED'); const router = express.Router(); const { HOST } = require('../config/global'); @@ -27,7 +28,8 @@ const getSelectOptions = (req, service, query) => api(req).get(`/${service}`, { }).then((data) => data.data); const markSelected = (options, values = []) => options.map((option) => { - option.selected = values.includes(option._id); + const optionId = option.id !== undefined ? option.id : option._id; + option.selected = values.includes(optionId); return option; }); @@ -106,6 +108,8 @@ const deleteEventsForCourse = (req, res, courseId) => { return Promise.resolve(true); }; +let groupIds = []; +let classIds = []; const editCourseHandler = (req, res, next) => { let coursePromise; let action; @@ -124,16 +128,23 @@ const editCourseHandler = (req, res, next) => { action += `?redirectUrl=${req.query.redirectUrl}`; } - const classesPromise = api(req) - .get('/classes', { - qs: { - schoolId: res.locals.currentSchool, - $populate: ['year'], - $limit: -1, - $sort: { year: -1, displayName: 1 }, - }, - }); - // .then(data => data.data); needed when pagination is not disabled + let classesAndGroupsPromise; + let classesPromise; + if (FEATURE_GROUPS_IN_COURSE_ENABLED) { + classesAndGroupsPromise = api(req, { version: 'v3' }) + .get('/groups/class'); + } else { + classesPromise = api(req) + .get('/classes', { + qs: { + schoolId: res.locals.currentSchool, + $populate: ['year'], + $limit: -1, + $sort: { year: -1, displayName: 1 }, + }, + }); + } + const teachersPromise = getSelectOptions(req, 'users', { roles: ['teacher'], $limit: false, @@ -153,15 +164,23 @@ const editCourseHandler = (req, res, next) => { Promise.all([ coursePromise, - classesPromise, + FEATURE_GROUPS_IN_COURSE_ENABLED ? classesAndGroupsPromise : classesPromise, teachersPromise, studentsPromise, scopePermissions, - ]).then(([course, _classes, _teachers, _students, _scopePermissions]) => { + ]).then(([course, _classesAndGroups, _teachers, _students, _scopePermissions]) => { // these 3 might not change anything because hooks allow just ownSchool results by now, but to be sure: - const classes = _classes.filter( - (c) => c.schoolId === res.locals.currentSchool, - ).sort(); + let classesAndGroups = []; + if (FEATURE_GROUPS_IN_COURSE_ENABLED) { + classesAndGroups = _classesAndGroups.data; + groupIds = _classesAndGroups.data.filter((g) => g.type === 'group').map((group) => group.id); + classIds = _classesAndGroups.data.filter((c) => c.type === 'class').map((clazz) => clazz.id); + } else { + classesAndGroups = _classesAndGroups.filter( + (c) => c.schoolId === res.locals.currentSchool, + ).sort(); + } + const teachers = _teachers.filter( (t) => t.schoolId === res.locals.currentSchool, ); @@ -233,6 +252,8 @@ const editCourseHandler = (req, res, next) => { ? s.filter(({ selected }) => selected) : s ); + const classAndGroupIds = [...(course.classIds || []), ...(course.groupIds || [])]; + if (req.params.courseId) { if (!_scopePermissions.includes('COURSE_EDIT')) return next(new Error(res.$t('global.text.403'))); return res.render('courses/edit-course', { @@ -243,7 +264,7 @@ const editCourseHandler = (req, res, next) => { closeLabel: res.$t('global.button.cancel'), course, colors, - classes: markSelected(classes, course.classIds), + classesAndGroups: markSelected(classesAndGroups, classAndGroupIds), teachers: markSelected( teachers, course.teacherIds, @@ -265,7 +286,7 @@ const editCourseHandler = (req, res, next) => { closeLabel: res.$t('global.button.cancel'), course, colors, - classes: markSelected(classes, course.classIds), + classesAndGroups: markSelected(classesAndGroups, classAndGroupIds), teachers: markSelected( teachers, course.teacherIds, @@ -510,6 +531,17 @@ router.post('/', (req, res, next) => { delete req.body[feature]; }); + req.body.groupIds = []; + if (FEATURE_GROUPS_IN_COURSE_ENABLED) { + req.body.groupIds = (req.body.classIds ?? []) + .filter((id) => groupIds.includes(id)); + + if (groupIds.length > 0) { + req.body.classIds = (req.body.classIds ?? []) + .filter((id) => classIds.includes(id)); + } + } + api(req) .post('/courses/', { json: req.body, // TODO: sanitize @@ -738,6 +770,9 @@ router.patch('/:courseId', async (req, res, next) => { if (!req.body.classIds) { req.body.classIds = []; } + if (!req.body.groupIds) { + req.body.groupIds = []; + } if (!req.body.userIds) { req.body.userIds = []; } @@ -745,6 +780,13 @@ router.patch('/:courseId', async (req, res, next) => { req.body.substitutionIds = []; } + if (FEATURE_GROUPS_IN_COURSE_ENABLED) { + req.body.groupIds = req.body.classIds + .filter((id) => groupIds.includes(id)); + req.body.classIds = req.body.classIds + .filter((id) => classIds.includes(id)); + } + const startDate = timesHelper.dateStringToMoment(req.body.startDate); const untilDate = timesHelper.dateStringToMoment(req.body.untilDate); diff --git a/views/courses/create-course.hbs b/views/courses/create-course.hbs index c49294e86d..9d55d424ac 100644 --- a/views/courses/create-course.hbs +++ b/views/courses/create-course.hbs @@ -184,13 +184,30 @@ diff --git a/views/courses/edit-course.hbs b/views/courses/edit-course.hbs index b9b024bca5..ac2e1f0f8b 100644 --- a/views/courses/edit-course.hbs +++ b/views/courses/edit-course.hbs @@ -167,18 +167,29 @@