diff --git a/classes/booking_manager.php b/classes/booking_manager.php index 6f36d35..1fdd26f 100644 --- a/classes/booking_manager.php +++ b/classes/booking_manager.php @@ -175,6 +175,9 @@ public function validate($timenow = null): array { $timenow = time(); } + $userrows = []; + $usersessions = []; + // Break into rows and validate the multiple interdependant fields together. foreach ($this->get_iterator() as $index => $entry) { $row = $index + 1; @@ -244,6 +247,20 @@ public function validate($timenow = null): array { $sessioncapacitycache[$session->id]['capacity']--; $sessioncapacitycache[$session->id]['rows'][] = $row; } + + if (isset($userid) && $session && $this->facetoface->signuptype != MOD_FACETOFACE_SIGNUP_MULTIPLE) { + if ($currusersessions = facetoface_get_user_submissions($this->f, $userid)) { + foreach ($currusersessions as $currusersession) { + if ($currusersession->sessionid != $session->id) { + $errors[] = [ + $row, + new lang_string('error:addalreadysignedupattendee', 'mod_facetoface', $entry->email), + ]; + break; + } + } + } + } } // Check user enrolment into the course. @@ -276,6 +293,35 @@ public function validate($timenow = null): array { new lang_string('error:invalidstatusspecified', 'mod_facetoface', $entry->status), ]; } + + if (isset($userid)) { + if (!isset($userrows[$userid])) { + $userrows[$userid] = []; + } + if (!isset($usersessions[$userid])) { + $usersessions[$userid] = []; + } + + $userrows[$userid][$row] = $entry->email; + + if ($session && !in_array($session->id, $usersessions[$userid])) { + $usersessions[$userid][] = $session->id; + } + } + } + + if ($this->facetoface->signuptype != MOD_FACETOFACE_SIGNUP_MULTIPLE) { + foreach ($usersessions as $userid => $sessions) { + if (count($sessions) > 1) { + $rows = $userrows[$userid]; + foreach ($rows as $row => $email) { + $errors[] = [ + $row, + new lang_string('error:multipleusersessions', 'mod_facetoface', $email), + ]; + } + } + } } // For all sessions that went over capacity, report it. diff --git a/lang/en/facetoface.php b/lang/en/facetoface.php index 9cdd69e..c8891eb 100644 --- a/lang/en/facetoface.php +++ b/lang/en/facetoface.php @@ -153,6 +153,7 @@ $string['error:sessionoverbooked'] = 'Session ID {$a->session} overbooked by {$a->amount} person(s).'; $string['error:sessiondoesnotexist'] = 'Session ID {$a} does not exist'; $string['error:userdoesnotexist'] = 'User {$a} does not exist'; +$string['error:multipleusersessions'] = 'User {$a} has more than one session'; $string['error:multipleusersmatched'] = 'Multiple users matched to identifier {$a}'; $string['error:addalreadysignedupattendee'] = '{$a} is already signed-up for this Face-to-Face activity.'; $string['error:addattendee'] = 'Could not add {$a} to the session.'; diff --git a/tests/upload_test.php b/tests/upload_test.php index a475849..fbe0618 100644 --- a/tests/upload_test.php +++ b/tests/upload_test.php @@ -138,7 +138,9 @@ public function test_user_validation() { // Generate users. $user = $this->getDataGenerator()->create_user(); - $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student'); $this->setCurrentTimeStart(); $now = time(); @@ -156,6 +158,20 @@ public function test_user_validation() { ], ]); + $secondsession = $generator->create_session([ + 'facetoface' => $facetoface->id, + 'capacity' => '3', + 'allowoverbook' => '0', + 'details' => 'xyz', + 'duration' => '1.5', // One and half hours. + 'normalcost' => '111', + 'discountcost' => '11', + 'allowcancellations' => '0', + 'sessiondates' => [ + ['timestart' => $now + 4 * DAYSECS, 'timefinish' => $now + 5 * DAYSECS], + ], + ]); + $remotesession = $generator->create_session([ 'facetoface' => $anotherfacetoface->id, 'capacity' => '3', @@ -170,6 +186,9 @@ public function test_user_validation() { ], ]); + facetoface_user_signup($session, $facetoface, $course, '', + MDL_F2F_TEXT, MDL_F2F_STATUS_BOOKED, $student3->id); + $bm = new booking_manager($facetoface->id); $records = [ @@ -191,7 +210,7 @@ public function test_user_validation() { ], // Test student who is enrolled into the course (no issues). (object) [ - 'email' => $student->email, + 'email' => $student1->email, 'session' => $session->id, 'status' => '', 'notificationtype' => '', @@ -199,15 +218,23 @@ public function test_user_validation() { ], // Test invalid options. (object) [ - 'email' => $student->email, + 'email' => $student2->email, 'session' => $session->id, 'status' => 'helloworld', 'notificationtype' => 'phone', 'discountcode' => '', ], + // Test multiple rows for the same student when f2f signup type is single. + (object) [ + 'email' => $student2->email, + 'session' => $secondsession->id, + 'status' => '', + 'notificationtype' => '', + 'discountcode' => '', + ], // Test permissions (e.g. user not able to upload/process for a f2f activity loaded). (object) [ - 'email' => $student->email, + 'email' => $student2->email, 'session' => $remotesession->id, 'status' => '', 'notificationtype' => '', @@ -215,12 +242,20 @@ public function test_user_validation() { ], // Test email does not match case-wise (in the default case sensitive mode). (object) [ - 'email' => strtoupper($student->email), + 'email' => strtoupper($student2->email), 'session' => $session->id, 'status' => '', 'notificationtype' => '', 'discountcode' => '', ], + // Test student already in a session when f2f signup type is single. + (object) [ + 'email' => $student3->email, + 'session' => $secondsession->id, + 'status' => '', + 'notificationtype' => '', + 'discountcode' => '', + ], ]; $bm->load_from_array($records); @@ -275,6 +310,15 @@ public function test_user_validation() { $this->check_row_validation_error_exists( $errors, 5, + new lang_string('error:multipleusersessions', 'mod_facetoface', $student2->email) + ), + 'Expecting multiple sessions error for user when f2f signup type is single.' + ); + + $this->assertTrue( + $this->check_row_validation_error_exists( + $errors, + 6, new lang_string('error:tryingtoupdatesessionfromanothermodule', 'mod_facetoface', (object) [ 'session' => $remotesession->id, 'f' => $facetoface->id, @@ -282,14 +326,24 @@ public function test_user_validation() { ), 'Expecting permission check conflict due to session->facetoface + facetoface id mismatcherror.' ); + $this->assertTrue( $this->check_row_validation_error_exists( $errors, - 6, - new lang_string('error:userdoesnotexist', 'mod_facetoface', strtoupper($student->email)) + 7, + new lang_string('error:userdoesnotexist', 'mod_facetoface', strtoupper($student2->email)) ), 'Expecting user to not exist because email does not match case-wise.' ); + + $this->assertTrue( + $this->check_row_validation_error_exists( + $errors, + 8, + new lang_string('error:addalreadysignedupattendee', 'mod_facetoface', $student3->email) + ), + 'Expecting user already signed up to another session error.' + ); } /** @@ -758,4 +812,73 @@ public function test_email_suppression(string $status, bool $shouldsuppress) { $this->assertNotEmpty($emails); } } + + /** + * Test upload processing multiple sessions with signup type set to multiple. + */ + public function test_processing_signup_multiple() { + /** @var \mod_facetoface_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_facetoface'); + + $course = $this->getDataGenerator()->create_course(); + // Facetoface with signuptype set to multiple. + $facetoface = $generator->create_instance(['course' => $course->id, 'signuptype' => MOD_FACETOFACE_SIGNUP_MULTIPLE]); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $now = time(); + $record = [ + 'facetoface' => $facetoface->id, + 'capacity' => '3', + 'allowoverbook' => '0', + 'details' => 'xyz', + 'duration' => '1.5', // One and half hours. + 'normalcost' => '111', + 'discountcost' => '11', + 'allowcancellations' => '0', + 'sessiondates' => [ + ['timestart' => $now + 3 * DAYSECS, 'timefinish' => $now + 4 * DAYSECS], + ], + ]; + $session1 = $generator->create_session($record); + $session2 = $generator->create_session($record); + $session3 = $generator->create_session($record); + + // Signup student to session3 to test already signed up validating. + facetoface_user_signup($session3, $facetoface, $course, '', + MDL_F2F_TEXT, MDL_F2F_STATUS_BOOKED, $student->id); + + $bm = new booking_manager($facetoface->id); + + $records = [ + // Test user can be added to session 1. + (object) [ + 'email' => $student->email, + 'session' => $session1->id, + 'status' => '', + 'notificationtype' => '', + 'discountcode' => '', + ], + // Test user can be added to session 2. + (object) [ + 'email' => $student->email, + 'session' => $session2->id, + 'status' => '', + 'notificationtype' => '', + 'discountcode' => '', + ], + ]; + + $bm->load_from_array($records); + $errors = $bm->validate(); + + $this->assertEmpty($errors); + $this->assertTrue($bm->process()); + + $sessions = facetoface_get_user_submissions($facetoface->id, $student->id); + $this->assertCount(3, $sessions); + $sessionids = array_column($sessions, 'sessionid'); + $this->assertContains($session1->id, $sessionids); + $this->assertContains($session2->id, $sessionids); + $this->assertContains($session3->id, $sessionids); + } } diff --git a/version.php b/version.php index 4b57c01..8a255ae 100644 --- a/version.php +++ b/version.php @@ -30,8 +30,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023101907; -$plugin->release = 2023101907; +$plugin->version = 2023101908; +$plugin->release = 2023101908; $plugin->requires = 2022031500; // Requires 4.0. $plugin->component = 'mod_facetoface'; $plugin->maturity = MATURITY_STABLE;