/** * Distribute seats for several courses in a course set using the given * user priorities. * * @param CourseSet $courseSet The course set containing the courses * that seats shall be distributed for. * @see CourseSet */ private function distributeByPriorities($courseSet) { Log::DEBUG('start seat distribution for course set: ' . $courseSet->getId()); $limited_admission = $courseSet->getAdmissionRule('LimitedAdmission'); //all users with their priorities $claiming_users = AdmissionPriority::getPriorities($courseSet->getId()); //all users which have bonus/malus $factored_users = $courseSet->getUserFactorList(); //all users with their max number of courses $max_seats_users = array_combine(array_keys($claiming_users), array_map(function ($u) use($limited_admission) { return $limited_admission->getMaxNumberForUser($u); }, array_keys($claiming_users))); //unlucky users get a bonus for the next round $bonus_users = array(); //users / courses für later waitlist distribution $waiting_users = array(); //number of already distributed seats for users $distributed_users = array(); $prio_mapper = function ($users, $course_id) use($claiming_users) { $mapper = function ($u) use($course_id) { return isset($u[$course_id]) ? $u[$course_id] : null; }; return array_filter(array_map($mapper, array_intersect_key($claiming_users, array_flip($users)))); }; //sort courses by highest count of prio 1 applicants $stats = AdmissionPriority::getPrioritiesStats($courseSet->getId()); $courses = array_map(function ($a) { return $a['h']; }, $stats); arsort($courses, SORT_NUMERIC); $max_prio = AdmissionPriority::getPrioritiesMax($courseSet->getId()); //count already manually distributed places $distributed_users = $this->countParticipatingUsers(array_keys($courses), array_keys($claiming_users)); Log::DEBUG('already distributed users: ' . print_r($distributed_users, 1)); //walk through all prios with all courses foreach (range(1, $max_prio) as $current_prio) { foreach (array_keys($courses) as $course_id) { $current_claiming = array(); $course = Course::find($course_id); $free_seats = $course->getFreeSeats(); //find users with current prio for this course, if they still need a place foreach ($claiming_users as $user_id => $prio_courses) { if ($prio_courses[$course_id] == $current_prio && $distributed_users[$user_id] < $max_seats_users[$user_id]) { //exclude participants if (!$course->getParticipantStatus($user_id)) { $current_claiming[$user_id] = 1; if (isset($factored_users[$user_id])) { $current_claiming[$user_id] *= $factored_users[$user_id]; } } else { Log::DEBUG(sprintf('user %s is already %s in course %s, ignoring', $user_id, $course->getParticipantStatus($user_id), $course->id)); } } } //give maximum bonus to users which were unlucky before foreach (array_keys($current_claiming) as $user_id) { if ($bonus_users[$user_id] > 0) { $current_claiming[$user_id] = $bonus_users[$user_id] * count($current_claiming) + 1; $bonus_users[$user_id]--; } } Log::DEBUG(sprintf('distribute %s seats on %s claiming with prio %s in course %s', $free_seats, count($current_claiming), $current_prio, $course->id)); Log::DEBUG('users to distribute: ' . print_r($current_claiming, 1)); $current_claiming = $this->rollTheDice($current_claiming); Log::DEBUG('the die is cast: ' . print_r($current_claiming, 1)); $chosen_ones = array_slice(array_keys($current_claiming), 0, $free_seats); Log::DEBUG('chosen ones: ' . print_r($chosen_ones, 1)); $this->addUsersToCourse($chosen_ones, $course, $prio_mapper($chosen_ones, $course->id)); foreach ($chosen_ones as $one) { $distributed_users[$one]++; } if ($free_seats < count($current_claiming)) { $remaining_ones = array_slice(array_keys($current_claiming), $free_seats); foreach ($remaining_ones as $one) { $bonus_users[$one]++; $waiting_users[$current_prio][$course_id][] = $one; } } } } //distribute to waitlists if applicable Log::DEBUG('waiting list: ' . print_r($waiting_users, 1)); foreach ($waiting_users as $current_prio => $current_prio_waiting_courses) { foreach ($current_prio_waiting_courses as $course_id => $users) { $users = array_filter($users, function ($user_id) use($distributed_users, $max_seats_users) { return $distributed_users[$user_id] < $max_seats_users[$user_id]; }); $course = Course::find($course_id); Log::DEBUG(sprintf('distribute waitlist of %s with prio %s in course %s', count($users), $current_prio, $course->id)); if (!$course->admission_disable_waitlist) { if ($course->admission_waitlist_max) { $free_seats_waitlist = $course->admission_waitlist_max - $course->getNumWaiting(); $free_seats_waitlist = $free_seats_waitlist < 0 ? 0 : $free_seats_waitlist; } else { $free_seats_waitlist = count($users); } $waiting_list_ones = array_slice($users, 0, $free_seats_waitlist); Log::DEBUG('waiting list ones: ' . print_r($waiting_list_ones, 1)); $this->addUsersToWaitlist($waiting_list_ones, $course, $prio_mapper($waiting_list_ones, $course->id)); foreach ($waiting_list_ones as $one) { $distributed_users[$one]++; } } else { $free_seats_waitlist = 0; } if ($free_seats_waitlist < count($users)) { $remaining_ones = array_slice($users, $free_seats_waitlist); Log::DEBUG('remaining ones: ' . print_r($remaining_ones, 1)); $this->notifyRemainingUsers($remaining_ones, $course, $prio_mapper($remaining_ones, $course->id)); } } } }
function save_courseset_action($cs_id) { $cs = new CourseSet($cs_id); if ($cs->isUserAllowedToEdit($this->user_id)) { $this->instant_course_set_view = true; $response = $this->relay('admission/courseset/save/' . $cs->getId()); $this->body = $response->body; if ($response->headers['Location']) { $this->redirect($response->headers['Location']); } } else { throw new Trails_Exception(403); } }
/** * Gets the list of applicants for the courses belonging to this course set. * * @param String $set_id course set ID * @param String $csv export users to file */ public function applications_list_action($set_id, $csv = null) { if (Request::isXhr()) { $this->response->add_header('X-Title', _('Liste der Anmeldungen')); } $courseset = new CourseSet($set_id); $applicants = AdmissionPriority::getPriorities($set_id); $users = User::findMany(array_keys($applicants), 'ORDER BY Nachname'); $courses = SimpleCollection::createFromArray(Course::findMany($courseset->getCourses())); $captions = array(_("Nachname"), _("Vorname"), _("Nutzername"), _("Veranstaltung"), _("Nummer"), _("Priorität")); $data = array(); foreach ($users as $user) { $row = array(); $app_courses = $applicants[$user->id]; asort($app_courses); foreach ($app_courses as $course_id => $prio) { $row = array(); $row[] = $user->nachname; $row[] = $user->vorname; $row[] = $user->username; $row[] = $courses->findOneBy('id', $course_id)->name; $row[] = $courses->findOneBy('id', $course_id)->veranstaltungsnummer; $row[] = $prio; if ($csv) { $row[] = $user->email; } $data[] = $row; } } if ($csv) { $tmpname = md5(uniqid('tmp')); $captions[] = _("Email"); if (array_to_csv($data, $GLOBALS['TMP_PATH'] . '/' . $tmpname, $captions)) { $this->redirect(GetDownloadLink($tmpname, 'Anmeldungen_' . $courseset->getName() . '.csv', 4, 'force')); return; } } $this->captions = $captions; $this->data = $data; $this->set_id = $courseset->getId(); }
public function execute($last_result, $parameters = array()) { $verbose = $parameters['verbose']; $sets = DbManager::get()->fetchFirst("SELECT DISTINCT cr.set_id FROM courseset_rule cr INNER JOIN coursesets USING(set_id)\n WHERE type = 'ParticipantRestrictedAdmission' AND algorithm_run = 0"); if (count($sets)) { if ($verbose) { echo date('r') . ' - Starting seat distribution ' . chr(10); $old_logger = Log::get()->getHandler(); $old_log_level = Log::get()->getLogLevel(); @mkdir($GLOBALS['TMP_PATH'] . '/seat_distribution_logs'); $logfile = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs/' . date('Y-m-d-H-i') . '_seat_distribution.log'; if (is_dir($GLOBALS['TMP_PATH'] . '/seat_distribution_logs')) { Log::get()->setHandler($logfile); Log::get()->setLogLevel(Log::DEBUG); echo 'logging to ' . $logfile . chr(10); } else { echo 'could not create directory ' . $GLOBALS['TMP_PATH'] . '/seat_distribution_logs' . chr(10); } } foreach ($sets as $set_id) { $courseset = new CourseSet($set_id); if ($courseset->isSeatDistributionEnabled() && !$courseset->hasAlgorithmRun() && $courseset->getSeatDistributionTime() < time()) { if ($verbose) { echo ++$i . ' ' . $courseset->getId() . ' : ' . $courseset->getName() . chr(10); $applicants = AdmissionPriority::getPriorities($set_id); $courses = SimpleCollection::createFromArray(Course::findMany($courseset->getCourses()))->toGroupedArray('seminar_id', words('name veranstaltungsnummer')); $captions = array(_("Nachname"), _("Vorname"), _("Nutzername"), _('Nutzer-ID'), _('Veranstaltung-ID'), _("Veranstaltung"), _("Nummer"), _("Priorität")); $data = array(); $users = User::findEachMany(function ($user) use($courses, $applicants, &$data) { $app_courses = $applicants[$user->id]; asort($app_courses); foreach ($app_courses as $course_id => $prio) { $row = array(); $row[] = $user->nachname; $row[] = $user->vorname; $row[] = $user->username; $row[] = $user->id; $row[] = $course_id; $row[] = $courses[$course_id]['name']; $row[] = $courses[$course_id]['veranstaltungsnummer']; $row[] = $prio; $data[] = $row; } }, array_keys($applicants), 'ORDER BY Nachname'); $applicants_file = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs/applicants_' . $set_id . '.csv'; if (array_to_csv($data, $applicants_file, $captions)) { echo 'applicants written to ' . $applicants_file . chr(10); } } $courseset->distributeSeats(); } } if ($verbose) { Log::get()->setHandler($old_logger); Log::get()->setLogLevel($old_log_level); } } else { if ($verbose) { echo date('r') . ' - Nothing to do' . chr(10); } } }