From 1201e3d0f93420f76dcd917211121f2d8b79d05f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Noack?= <noack@data-quest.de>
Date: Fri, 18 Oct 2024 11:18:49 +0000
Subject: [PATCH] Resolve #3697 "Geplante Teilnehmendenzahl als Pflichtangabe
 bei der Anlage von LV"

Closes #3697

Merge request studip/studip!2566
---
 app/controllers/admin/sem_classes.php         |  1 +
 app/views/admin/sem_classes/details.php       |  6 ++++-
 .../course/wizard/steps/basicdata/index.php   |  6 +++++
 .../6.0.23_tic3967_turnout_mandatory.php      | 25 +++++++++++++++++++
 lib/classes/SemClass.php                      |  2 ++
 .../coursewizardsteps/BasicDataWizardStep.php | 14 ++++++++++-
 .../javascripts/bootstrap/course_wizard.js    |  9 +++++++
 .../assets/javascripts/lib/admin_sem_class.js |  3 ++-
 8 files changed, 63 insertions(+), 3 deletions(-)
 create mode 100644 db/migrations/6.0.23_tic3967_turnout_mandatory.php

diff --git a/app/controllers/admin/sem_classes.php b/app/controllers/admin/sem_classes.php
index 7993c4b308d..3dc9f269b46 100644
--- a/app/controllers/admin/sem_classes.php
+++ b/app/controllers/admin/sem_classes.php
@@ -126,6 +126,7 @@ class Admin_SemClassesController extends AuthenticatedController
         $sem_class->set('show_raumzeit', Request::int("show_raumzeit"));
         $sem_class->set('is_group', Request::int("is_group"));
         $sem_class->set('unlimited_forbidden', Request::bool('unlimited_forbidden'));
+        $sem_class->set('admission_turnout_mandatory', Request::bool('admission_turnout_mandatory'));
         $sem_class->store();
         foreach (array_keys($sem_class->getModules()) as $module_name) {
             if ($sem_class->isModuleMandatory($module_name) && !$old_data_sem_class->isModuleMandatory($module_name)) {
diff --git a/app/views/admin/sem_classes/details.php b/app/views/admin/sem_classes/details.php
index 54f4f903353..530f553a511 100644
--- a/app/views/admin/sem_classes/details.php
+++ b/app/views/admin/sem_classes/details.php
@@ -205,11 +205,15 @@
             <?= _('Unbegrenzte Laufzeit verbieten') ?>
         </label>
 
+        <label>
+            <input type="checkbox" id="admission_turnout_mandatory" value="1" <?= $sem_class['admission_turnout_mandatory'] ?  'checked' : '' ?>>
+            <?= _('Geplante Teilnehmendenzahl muss angegeben werden') ?>
+        </label>
+
         <label>
             <?= _('Kurzer Beschreibungstext zum Anlegen einer Veranstaltung') ?>
             <textarea id="create_description" maxlength="200" style="width: 100%"><?= htmlReady($sem_class['create_description']) ?></textarea>
         </label>
-
     </fieldset>
 
     <fieldset class="attribute_table">
diff --git a/app/views/course/wizard/steps/basicdata/index.php b/app/views/course/wizard/steps/basicdata/index.php
index 372d587821d..1ec8a37888f 100644
--- a/app/views/course/wizard/steps/basicdata/index.php
+++ b/app/views/course/wizard/steps/basicdata/index.php
@@ -56,6 +56,12 @@
     <input type="text" name="number" id="wizard-number" size="20" maxlength="99" value="<?= htmlReady($values['number'] ?? '') ?>"
      <? if ($course_number_format) : ?>pattern="<?= htmlReady($course_number_format) ?>" <? endif ?>/>
 </section>
+<section data-mandatory="<?=htmlready(json_encode($admission_turnout_mandatory_types))?>" <?=in_array($values['coursetype'],$admission_turnout_mandatory_types) ? '' : 'style="display: none"' ?>>
+    <label for="wizard-maxmembers" class="required">
+        <?= _('max. Teilnehmendenzahl') ?>
+    </label>
+    <input type="number" name="maxmembers" id="wizard-maxmember" min="0" value="<?= htmlReady($values['maxmembers'] ?? '') ?>"/>
+</section>
 <section>
     <label for="wizard-description">
         <?= _('Beschreibung') ?>
diff --git a/db/migrations/6.0.23_tic3967_turnout_mandatory.php b/db/migrations/6.0.23_tic3967_turnout_mandatory.php
new file mode 100644
index 00000000000..be1bfff551a
--- /dev/null
+++ b/db/migrations/6.0.23_tic3967_turnout_mandatory.php
@@ -0,0 +1,25 @@
+<?php
+
+final class Tic3967TurnoutMandatory extends Migration
+{
+    public function description()
+    {
+        return 'adds option to make admission turnout mandatory';
+    }
+
+    public function up()
+    {
+        DBManager::get()->exec("
+            ALTER TABLE `sem_classes` ADD `admission_turnout_mandatory` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `is_group`
+        ");
+        $cache = StudipCacheFactory::getCache();
+        $cache->expire('DB_SEM_CLASSES_ARRAY');
+    }
+
+    public function down()
+    {
+        DBManager::get()->exec("ALTER TABLE `sem_classes` DROP `admission_turnout_mandatory`");
+        $cache = StudipCacheFactory::getCache();
+        $cache->expire('DB_SEM_CLASSES_ARRAY');
+    }
+}
diff --git a/lib/classes/SemClass.php b/lib/classes/SemClass.php
index 2a038e20787..e541d8c371f 100644
--- a/lib/classes/SemClass.php
+++ b/lib/classes/SemClass.php
@@ -386,6 +386,7 @@ class SemClass implements ArrayAccess
                 "show_raumzeit = :show_raumzeit, " .
                 "is_group = :is_group, " .
                 "unlimited_forbidden = :unlimited_forbidden, " .
+                "admission_turnout_mandatory = :admission_turnout_mandatory," .
                 "chdate = UNIX_TIMESTAMP() " .
             "WHERE id = :id ".
         "");
@@ -430,6 +431,7 @@ class SemClass implements ArrayAccess
             'show_raumzeit' => (int) $this->data['show_raumzeit'],
             'is_group' => (int) $this->data['is_group'],
             'unlimited_forbidden' => (int) $this->data['unlimited_forbidden'],
+            'admission_turnout_mandatory' => (int) $this->data['admission_turnout_mandatory'],
         ]);
     }
 
diff --git a/lib/classes/coursewizardsteps/BasicDataWizardStep.php b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
index 53c8c5026ec..022f9338488 100644
--- a/lib/classes/coursewizardsteps/BasicDataWizardStep.php
+++ b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
@@ -48,6 +48,7 @@ class BasicDataWizardStep implements CourseWizardStep
         $values = $values[__CLASS__] ?? [];
         // Get all available course types and their categories.
         $typestruct = [];
+        $admission_turnout_mandatory_types = [];
         foreach (SemType::getTypes() as $type) {
             $class = $type->getClass();
             // Creates a studygroup.
@@ -62,10 +63,14 @@ class BasicDataWizardStep implements CourseWizardStep
             } else {
                 if (!$class['course_creation_forbidden'] && !$class['studygroup_mode']) {
                     $typestruct[$class['name']][] = $type;
+                    if ($class['admission_turnout_mandatory']) {
+                        $admission_turnout_mandatory_types[] = $type['id'];
+                    }
                 }
             }
         }
         $tpl->set_attribute('types', $typestruct);
+        $tpl->set_attribute('admission_turnout_mandatory_types', $admission_turnout_mandatory_types);
         // Select a default type if none is given.
         if (empty($values['coursetype'])) {
             if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && Request::isXhr()) {
@@ -376,6 +381,11 @@ class BasicDataWizardStep implements CourseWizardStep
                 $errors[] = _('Sie müssen die Nutzungsbedingungen akzeptieren.');
             }
         }
+        $sem_type = new SemType((int)$values['coursetype']);
+        $sem_class = $sem_type->getClass();
+        if ($sem_class['admission_turnout_mandatory'] && !$values['maxmembers']) {
+            $errors[] = _('Sie müssen die maximale Teilnehmendenzahl angeben');
+        }
         if ($errors) {
             $ok = false;
             PageLayout::postError(_('Bitte beheben Sie erst folgende Fehler, bevor Sie fortfahren:'), $errors);
@@ -422,6 +432,7 @@ class BasicDataWizardStep implements CourseWizardStep
         $course->admission_prelim = $semclass['admission_prelim_default'];
         $course->lesezugriff = $semclass['default_read_level'] ?: 1;
         $course->schreibzugriff = $semclass['default_write_level'] ?: 1;
+        $course->admission_turnout = $values['maxmembers'];
 
         // Studygroups: access and description.
         if (in_array($values['coursetype'], studygroup_sem_types())) {
@@ -523,7 +534,8 @@ class BasicDataWizardStep implements CourseWizardStep
             'institute' => $course->institut_id,
             'description' => $course->beschreibung,
             'description_i18n' => is_object($course->beschreibung) ?
-                $course->beschreibung->toArray() : $course->beschreibung
+                $course->beschreibung->toArray() : $course->beschreibung,
+            'maxmembers' => $course->getSemClass()->offsetGet('admission_turnout_mandatory') ? $course->admission_turnout : '',
         ];
         $lecturers = $course->members->findBy('status', 'dozent')->pluck('user_id');
         $data['lecturers'] = array_flip($lecturers);
diff --git a/resources/assets/javascripts/bootstrap/course_wizard.js b/resources/assets/javascripts/bootstrap/course_wizard.js
index ef85e96661f..a053af50f0e 100644
--- a/resources/assets/javascripts/bootstrap/course_wizard.js
+++ b/resources/assets/javascripts/bootstrap/course_wizard.js
@@ -11,4 +11,13 @@ STUDIP.ready(function() {
             $(this).closest('section,footer').css('order')
         );
     });
+    $('#wizard-coursetype').on('change', function() {
+        let semtype = $(this).val();
+        let mandatory_types = $('#wizard-maxmember').parent('section').data('mandatory');
+        if (mandatory_types.includes(semtype)) {
+            $('#wizard-maxmember').parent('section').show();
+        } else {
+            $('#wizard-maxmember').parent('section').hide();
+        }
+    });
 });
diff --git a/resources/assets/javascripts/lib/admin_sem_class.js b/resources/assets/javascripts/lib/admin_sem_class.js
index 5ac95324681..10f698c636b 100644
--- a/resources/assets/javascripts/lib/admin_sem_class.js
+++ b/resources/assets/javascripts/lib/admin_sem_class.js
@@ -66,7 +66,8 @@ const admin_sem_class = {
                 admission_type_default: jQuery('#admission_type_default').val(),
                 show_raumzeit: jQuery('#show_raumzeit').is(':checked') ? 1 : 0,
                 is_group: jQuery('#is_group').is(':checked') ? 1 : 0,
-                unlimited_forbidden: jQuery('#unlimited_forbidden').is(':checked') ? 1 : 0
+                unlimited_forbidden: jQuery('#unlimited_forbidden').is(':checked') ? 1 : 0,
+                admission_turnout_mandatory: jQuery('#admission_turnout_mandatory').is(':checked') ? 1 : 0
             },
             type: 'POST',
             dataType: 'json',
-- 
GitLab