diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml
index 6903de17..0747b0af 100644
--- a/.github/workflows/moodle-ci.yml
+++ b/.github/workflows/moodle-ci.yml
@@ -128,7 +128,7 @@ jobs:
- name: Initialise moodle-plugin-ci
run: |
- composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
+ composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4
echo $(cd ci/bin; pwd) >> $GITHUB_PATH
echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
sudo locale-gen en_AU.UTF-8
diff --git a/classes/completion/custom_completion.php b/classes/completion/custom_completion.php
new file mode 100644
index 00000000..e477c750
--- /dev/null
+++ b/classes/completion/custom_completion.php
@@ -0,0 +1,107 @@
+.
+
+declare(strict_types=1);
+
+namespace mod_ratingallocate\completion;
+
+use context_module;
+use core_completion\activity_custom_completion;
+
+/**
+ * Activity custom completion subclass for the ratingallocate activity.
+ *
+ * Class for defining the custom completion rules of ratingallocate and fetching the completion statuses
+ * of the custom completion rules for a given ratingallocate instance and a user.
+ *
+ * @package mod_ratingallocate
+ * @copyright Irina Hoppe Uni Münster
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class custom_completion extends activity_custom_completion {
+
+ /**
+ * Fetches the completion state for a given completion rule.
+ *
+ * @param string $rule The completion rule.
+ * @return int The completion state.
+ * @throws \moodle_exception
+ */
+ public function get_state(string $rule): int {
+ global $DB;
+ $status = false;
+ $this->validate_rule($rule);
+
+ $userid = $this->userid;
+ $instance = $this->cm->instance;
+
+ if (!$DB->get_record('ratingallocate', ['id' => $instance])) {
+ throw new \moodle_exception('Unable to find ratingallocate instance with id ' . $instance);
+ }
+
+ if ($rule == 'completionvote') {
+ $sql = "SELECT * FROM {ratingallocate_ratings} r INNER JOIN {ratingallocate_choices} c on r.choiceid=c.id " .
+ "WHERE r.userid= :userid AND c.ratingallocateid= :ratingallocateid AND c.active=1";
+ $votesofuser = $DB->get_records_sql($sql, ['userid' => $userid, 'ratingallocateid' => $instance]);
+ $status = count($votesofuser) > 0;
+ } else if ($rule == 'completionallocation') {
+ $sql = "SELECT * FROM {ratingallocate_allocations} a INNER JOIN {ratingallocate_choices} c
+ ON a.choiceid = c.id WHERE userid= :userid AND a.ratingallocateid= :ratingallocateid AND c.active=1";
+ $allocationsofuser = $DB->get_records_sql($sql, ['userid' => $userid, 'ratingallocateid' => $instance]);
+ $status = count($allocationsofuser) > 0;
+ }
+
+ return $status ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
+ }
+
+ /**
+ * Fetch the list of custom completion rules that this module defines.
+ *
+ * @return array
+ */
+ public static function get_defined_custom_rules(): array {
+ return [
+ 'completionvote',
+ 'completionallocation',
+ ];
+ }
+
+ /**
+ * Returns an associative array of the descriptions of custom completion rules.
+ *
+ * @return array
+ */
+ public function get_custom_rule_descriptions(): array {
+ return [
+ 'completionvote' => get_string('completionvote_desc', RATINGALLOCATE_MOD_NAME),
+ 'completionallocation' => get_string('completionallocation_desc', RATINGALLOCATE_MOD_NAME),
+ ];
+ }
+
+ /**
+ * Returns an array of all completion rules, in the order they should be displayed to users.
+ *
+ * @return array
+ */
+ public function get_sort_order(): array {
+ return [
+ 'completionview',
+ 'completionvote',
+ 'completionallocation',
+ ];
+ }
+}
+
diff --git a/db/install.xml b/db/install.xml
index 5433f236..3ca1d6f0 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -23,6 +23,8 @@
+
+
diff --git a/db/upgrade.php b/db/upgrade.php
index c6885680..777594ca 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -218,5 +218,24 @@ function xmldb_ratingallocate_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2023050900, 'ratingallocate');
}
+ if ($oldversion < 2024080900) {
+
+ // Define completionrules fields to be added to ratingallocate.
+ $table = new xmldb_table('ratingallocate');
+ $votefield = new xmldb_field('completionvote', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, '0');
+ $allocationfield = new xmldb_field('completionallocation', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, '0');
+
+ // Conditionally launch add field notification_send.
+ if (!$dbman->field_exists($table, $votefield)) {
+ $dbman->add_field($table, $votefield);
+ }
+ if (!$dbman->field_exists($table, $allocationfield)) {
+ $dbman->add_field($table, $allocationfield);
+ }
+
+ // Ratingallocate savepoint reached.
+ upgrade_mod_savepoint(true, 2024080900, 'ratingallocate');
+ }
+
return true;
}
diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php
index 24cbddf5..a9551325 100644
--- a/lang/en/ratingallocate.php
+++ b/lang/en/ratingallocate.php
@@ -304,6 +304,13 @@
$string['err_required'] = 'You need to provide a value for this field.';
$string['err_minimum'] = 'The minimum value for this field is {$a}.';
$string['err_maximum'] = 'The maximum value for this field is {$a}.';
+
+$string['completionvote'] = "Vote in the activity";
+$string['completionallocation'] = "Been allocated to a choice";
+$string['completionvote_help'] = "To complete this activity, users have to submit a vote.";
+$string['completionallocation_help'] = "To complete this activity, users have to be allocated to a choice.";
+$string['completionvote_desc'] = "Vote";
+$string['completionallocation_desc'] = "Be allocated";
//
//
$string['show_choices_header'] = 'List of all choices';
diff --git a/lib.php b/lib.php
index 8008cb25..6f8a56ea 100644
--- a/lib.php
+++ b/lib.php
@@ -70,6 +70,8 @@ function ratingallocate_supports($feature) {
return true;
case FEATURE_COMPLETION_TRACKS_VIEWS:
return true;
+ case FEATURE_COMPLETION_HAS_RULES:
+ return true;
default :
return null;
}
@@ -760,12 +762,14 @@ function ratingallocate_reset_course_form_defaults($course) {
* @param stdClass $coursemodule The coursemodule object (record).
* @return cached_cm_info An object on information that the courses
* will know about (most noticeably, an icon).
+ * @throws dml_exception
*/
function ratingallocate_get_coursemodule_info($coursemodule) {
global $DB;
$dbparams = ['id' => $coursemodule->instance];
- if (! $ratingallocate = $DB->get_record('ratingallocate', $dbparams)) {
+ $fields = 'id, name, intro, introformat, accesstimestart, accesstimestop, completionvote, completionallocation';
+ if (!$ratingallocate = $DB->get_record(RATINGALLOCATE_MOD_NAME, $dbparams, $fields)) {
return false;
}
@@ -774,7 +778,13 @@ function ratingallocate_get_coursemodule_info($coursemodule) {
if ($coursemodule->showdescription) {
// Convert intro to html. Do not filter cached version, filters run at display time.
- $result->content = format_module_intro('ratingallocate', $ratingallocate, $coursemodule->id, false);
+ $result->content = format_module_intro(RATINGALLOCATE_MOD_NAME, $ratingallocate, $coursemodule->id, false);
+ }
+
+ // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
+ if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
+ $result->customdata['customcompletionrules']['completionvote'] = $ratingallocate->completionvote;
+ $result->customdata['customcompletionrules']['completionallocation'] = $ratingallocate->completionallocation;
}
// Populate some other values that can be used in calendar or on dashboard.
@@ -787,3 +797,37 @@ function ratingallocate_get_coursemodule_info($coursemodule) {
return $result;
}
+
+/**
+ * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
+ *
+ * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
+ * @return array $descriptions the array of descriptions for the custom rules.
+ */
+function mod_ratingallocate_get_completion_active_rule_descriptions($cm) {
+ // Values will be present in cm_info, and we assume these are up to date.
+ if (empty($cm->customdata['customcompletionrules']) || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
+ return [];
+ }
+
+ $descriptions = [];
+
+ foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
+ switch ($key) {
+ case 'completionvote':
+ if ($val == 1) {
+ $descriptions[] = get_string('copletionvotedesc', RATINGALLOCATE_MOD_NAME);
+ }
+ break;
+ case 'completionallocation':
+ if ($val == 1) {
+ $descriptions[] = get_string('copletionallocationdesc', RATINGALLOCATE_MOD_NAME);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $descriptions;
+}
diff --git a/locallib.php b/locallib.php
index fc9f82fe..dea65099 100644
--- a/locallib.php
+++ b/locallib.php
@@ -187,6 +187,7 @@ class ratingallocate {
/**
* Returns all users enrolled in the course the ratingallocate is in, who were able to access the activity
+ * @return Array of user records
* @throws moodle_exception
*/
public function get_raters_in_course(): array {
@@ -303,6 +304,13 @@ private function process_action_start_distribution() {
\core\output\notification::NOTIFY_SUCCESS);
}
}
+ $raters = $this->get_raters_in_course();
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($raters as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $rater->id);
+ }
+ }
redirect(new moodle_url('/mod/ratingallocate/view.php',
['id' => $this->coursemodule->id]));
return;
@@ -645,6 +653,14 @@ private function process_action_delete_choice() {
$DB->delete_records(this_db\ratingallocate_ch_gengroups::TABLE, ['choiceid' => $choiceid]);
$DB->delete_records(this_db\ratingallocate_choices::TABLE, ['id' => $choiceid]);
+ $raters = $this->get_raters_in_course();
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($raters as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $rater->id);
+ }
+ }
+
redirect(new moodle_url('/mod/ratingallocate/view.php',
['id' => $this->coursemodule->id, 'action' => ACTION_SHOW_CHOICES]),
get_string('choice_deleted_notification', RATINGALLOCATE_MOD_NAME,
@@ -727,6 +743,13 @@ private function process_action_manual_allocation() {
['id' => $this->coursemodule->id, 'action' => ACTION_MANUAL_ALLOCATION]));
}
}
+ $raters = $this->get_raters_in_course();
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($raters as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $rater->id);
+ }
+ }
}
$output .= $OUTPUT->heading(get_string('manual_allocation', RATINGALLOCATE_MOD_NAME), 2);
@@ -1020,6 +1043,13 @@ public function distribute_users_without_choice(string $distributionalgorithm):
// At this point we tried to assign all the users. It is possible that users remain undistributed, though.
$transaction->allow_commit();
+
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($possibleusers as $userid) {
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $userid);
+ }
+ }
}
/**
@@ -1195,6 +1225,9 @@ private function process_default() {
}
+ $completion = new completion_info($this->course);
+ $completion->set_module_viewed($this->coursemodule);
+
// Logging.
$event = \mod_ratingallocate\event\ratingallocate_viewed::create_simple(
context_module::instance($this->coursemodule->id), $this->ratingallocateid);
@@ -1402,6 +1435,15 @@ public function distrubute_choices() {
$distributor = new solver_edmonds_karp();
$timestart = microtime(true);
$distributor->distribute_users($this);
+
+ $completion = new completion_info($this->course);
+ $raters = $this->get_raters_in_course();
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($raters as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $rater->id);
+ }
+
+ }
$timeneeded = (microtime(true) - $timestart);
// Set algorithm status to finished.
@@ -1611,8 +1653,16 @@ public function get_allocations() {
* Removes all allocations for choices in $ratingallocateid
*/
public function clear_all_allocations() {
- $this->db->delete_records('ratingallocate_allocations', ['ratingallocateid' => intval
- ($this->ratingallocateid)]);
+ $this->db->delete_records('ratingallocate_allocations', ['ratingallocateid' => intval($this->ratingallocateid)]);
+ $raters = $this->get_raters_in_course();
+
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($raters as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $rater->id);
+ }
+ }
+
}
/**
@@ -1751,7 +1801,7 @@ public function get_users_with_ratings() {
* Deletes all ratings in this ratingallocate
*/
public function delete_all_ratings() {
- global $DB;
+ global $DB, $USER;
$transaction = $DB->start_delegated_transaction();
@@ -1772,6 +1822,11 @@ public function delete_all_ratings() {
$transaction->allow_commit();
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $USER->id);
+ }
+
// Logging.
$event = \mod_ratingallocate\event\all_ratings_deleted::create_simple(
context_module::instance($this->coursemodule->id), $this->ratingallocateid);
@@ -1806,6 +1861,11 @@ public function delete_ratings_of_user($userid) {
$transaction->allow_commit();
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $userid);
+ }
+
// Logging.
$event = \mod_ratingallocate\event\rating_deleted::create_simple(
context_module::instance($this->coursemodule->id), $this->ratingallocateid);
@@ -1862,7 +1922,10 @@ public function save_ratings_to_db($userid, array $data) {
$transaction->allow_commit();
$completion = new completion_info($this->course);
- $completion->set_module_viewed($this->coursemodule);
+ if ($completion->is_enabled()) {
+ $completion->set_module_viewed($this->coursemodule, $userid);
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $userid);
+ }
// Logging.
$event = \mod_ratingallocate\event\rating_saved::create_simple(
@@ -2000,6 +2063,14 @@ public function save_manual_allocation_form($allocdata, $userdata) {
$event->trigger();
$transaction->allow_commit();
+
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ foreach ($allusers as $rater) {
+ $completion->update_state($this->coursemodule, COMPLETION_UNKNOWN, $rater->id);
+ }
+ }
+
} catch (Exception $e) {
if (isset($transaction)) {
$transaction->rollback($e);
@@ -2054,6 +2125,10 @@ public function remove_allocation($choiceid, $userid) {
'choiceid' => $choiceid,
'userid' => $userid,
]);
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $userid);
+ }
return true;
}
@@ -2067,6 +2142,10 @@ public function remove_allocations($userid) {
'userid' => $userid,
'ratingallocateid' => $this->ratingallocateid,
]);
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ $completion->update_state($this->coursemodule, COMPLETION_INCOMPLETE, $userid);
+ }
}
/**
@@ -2081,6 +2160,10 @@ public function add_allocation($choiceid, $userid) {
'userid' => $userid,
'ratingallocateid' => $this->ratingallocateid,
]);
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled($this->coursemodule)) {
+ $completion->update_state($this->coursemodule, COMPLETION_COMPLETE, $userid);
+ }
return true;
}
diff --git a/mod_form.php b/mod_form.php
index 0879f737..42510f15 100644
--- a/mod_form.php
+++ b/mod_form.php
@@ -231,7 +231,7 @@ private function add_settings_field($stratfieldid, array $value, $strategyid, Mo
* @throws coding_exception
*/
public function definition_after_data() {
- parent::definition_after_data();
+
$mform = &$this->_form;
$data = $this->current;
@@ -272,8 +272,12 @@ public function definition_after_data() {
}
$mform->removeElement($strategyplaceholder);
}
+
+ // Call parent function after, in order to have completiontracking working properly.
+ parent::definition_after_data();
}
+
/**
* Checks that accesstimestart is before accesstimestop
*/
@@ -324,4 +328,88 @@ public function validation($data, $files) {
private function get_settingsfield_identifier($strategy, $key) {
return self::STRATEGY_OPTIONS . '[' . $strategy . '][' . $key . ']';
}
+
+ /**
+ * Add elements for setting the custom completion rules.
+ *
+ * @return array List of added element names.
+ */
+ public function add_completion_rules() {
+ $mform = $this->_form;
+
+ $mform->addElement('advcheckbox', $this->get_suffixed_name('vote'), ' ', get_string('completionvote', RATINGALLOCATE_MOD_NAME));
+ $mform->addElement('advcheckbox', $this->get_suffixed_name('allocation'), ' ', get_string('completionallocation', RATINGALLOCATE_MOD_NAME));
+
+ // Set default to not checked.
+ $mform->setDefault($this->get_suffixed_name('vote'), 0);
+ $mform->setDefault($this->get_suffixed_name('allocation'), 0);
+
+ // Add help buttons.
+ $mform->addHelpButton($this->get_suffixed_name('vote'), 'completionvote', RATINGALLOCATE_MOD_NAME);
+ $mform->addHelpButton($this->get_suffixed_name('allocation'), 'completionallocation', RATINGALLOCATE_MOD_NAME);
+
+ return [$this->get_suffixed_name('vote'), $this->get_suffixed_name('allocation')];
+ }
+
+ /**
+ * Returns the suffixed name for custom completion elements.
+ *
+ * @param string $fieldname
+ * @return string
+ */
+ protected function get_suffixed_name(string $fieldname): string {
+ // Counterintuitively don't use function get_suffix(), since data isn't saved correctly in DB otherwise.
+ return 'completion' . $fieldname;
+ }
+
+ /**
+ * Called during validaiton to see wether some activitiy-specific completion rules are selected.
+ *
+ * @param array $data Input data not yet validated.
+ * @return bool True if one or more rules are enabled, false if none are.
+ */
+ public function completion_rule_enabled($data) {
+ return ($data[$this->get_suffixed_name('vote')] == 1 || $data[$this->get_suffixed_name('allocation')] == 1);
+ }
+
+ /**
+ * Allows module to modify data returned by get_moduleinfo_data() or prepare_new_moduleinfo_data() before calling set_data().
+ * This method is also called in the bulk activity completion form.
+ * Only available on moodleform_mod.
+ *
+ * @param $defaultvalues
+ * @return void
+ */
+ public function data_preprocessing(&$defaultvalues) {
+ if (empty($defaultvalues[$this->get_suffixed_name('vote')])) {
+ $defaultvalues[$this->get_suffixed_name('vote')] = 0;
+ }
+ if (empty($defaultvalues[$this->get_suffixed_name('allocation')])) {
+ $defaultvalues[$this->get_suffixed_name('allocation')] = 0;
+ }
+ }
+
+ /**
+ * Allows module to modify the data returned by form get_data().
+ * This method is also called in the bulk activity completion form.
+ *
+ * Only available on moodleform_mod.
+ *
+ * @param stdClass $data the form data to be modified.
+ */
+ public function data_postprocessing($data) {
+ parent::data_postprocessing($data);
+ // Turn off completion settings if the checkboxes aren't ticked.
+ if (!empty($data->completionunlocked)) {
+ $completion = $data->completion;
+ $autocompletion = !empty($completion) && $completion == COMPLETION_TRACKING_AUTOMATIC;
+ if (empty($data->{$this->get_suffixed_name('vote')}) || !$autocompletion) {
+ $data->{$this->get_suffixed_name('vote')} = 0;
+ }
+ if (empty($data->{$this->get_suffixed_name('allocation')}) || !$autocompletion) {
+ $data->{$this->get_suffixed_name('allocation')} = 0;
+ }
+ }
+ }
+
}
diff --git a/tests/behat/completion_condition_allocation.feature b/tests/behat/completion_condition_allocation.feature
new file mode 100644
index 00000000..ab83b5a1
--- /dev/null
+++ b/tests/behat/completion_condition_allocation.feature
@@ -0,0 +1,48 @@
+@mod @mod_ratingallocate @core_completion
+Feature: Set a ratingallocate activity marked as completed when a user has been allocated
+ In order to ensure a student has been allocated
+ As a teacher
+ I need to set the ratingallocate to complete when the student has an allocation
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ And the following "activities" exist:
+ | activity | course | idnumber | name | completion | completionallocation | accesstimestart | accesstimestop |
+ | ratingallocate | C1 | ra1 | My Fair Allocation | 2 | 1 | ##2 days ago## | ##yesterday## |
+ And the following choices exist:
+ | title | explanation | maxsize | ratingallocate |
+ | C1 | Test | 1 | My Fair Allocation |
+ | C2 | Test | 0 | My Fair Allocation |
+ And the following ratings exist:
+ | choice | user | rating |
+ | C1 | student1 | 1 |
+ | C1 | student2 | 0 |
+ | C2 | student1 | 0 |
+ | C2 | student2 | 0 |
+ And I log in as "teacher1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > View" page
+ And I run the scheduled task "mod_ratingallocate\task\cron_task"
+ And I press "Publish Allocation"
+ And I wait "1" seconds
+ And I run the scheduled task "core\task\completion_regular_task"
+ And I wait "1" seconds
+ And I log out
+
+ @javascript
+ Scenario: User completes ratingallocate only if they have been allocated
+ When I log in as "teacher1"
+ And I am on "Course 1" course homepage
+ Then "Student 1" user has completed "My Fair Allocation" activity
+ And "Student 2" user has not completed "My Fair Allocation" activity
diff --git a/tests/behat/completion_condition_vote.feature b/tests/behat/completion_condition_vote.feature
new file mode 100644
index 00000000..b904d29f
--- /dev/null
+++ b/tests/behat/completion_condition_vote.feature
@@ -0,0 +1,43 @@
+@mod @mod_ratingallocate @core_completion
+Feature: Set a ratingallocate activity marked as completed when a user submits a vote
+ In order to ensure a student has voted in the activity
+ As a teacher
+ I need to set the ratingallocate to complete when the student has voted
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ And the following "activities" exist:
+ | activity | course | idnumber | name | completion | completionvote |
+ | ratingallocate | C1 | ra1 | My Fair Allocation | 2 | 1 |
+ And I log in as "teacher1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > Choices" page
+ And I add a new choice with the values:
+ | title | My first choice |
+ | Description (optional) | Test 1 |
+ | maxsize | 2 |
+
+ @javascript
+ Scenario: User completes ratingallocate only if they voted
+ When I log in as "student1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > View" page
+ And I press "Edit Rating"
+ And I press "Save changes"
+ And I log out
+ And I log in as "teacher1"
+ And I am on "Course 1" course homepage
+ And I navigate to "Reports" in current page administration
+ And I click on "Activity completion" "link"
+ Then "Completed" "icon" should exist in the "Student 1" "table_row"
+ And "Completed" "icon" should not exist in the "Student 2" "table_row"
diff --git a/tests/behat/completion_manual.feature b/tests/behat/completion_manual.feature
new file mode 100644
index 00000000..8e14026a
--- /dev/null
+++ b/tests/behat/completion_manual.feature
@@ -0,0 +1,41 @@
+@mod @mod_ratingallocate @core_completion
+Feature: Manually mark a ratingallocate activity as completed
+ In order to meet manual ratingallocate completion requirements
+ As a student
+ I need to be able to view and modify my ratingallocate manual completion status
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And the following "activities" exist:
+ | activity | course | idnumber | name | completion |
+ | ratingallocate | C1 | ra1 | My Fair Allocation | 1 |
+ And I log in as "teacher1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > Choices" page
+ And I add a new choice with the values:
+ | title | My first choice |
+ | Description (optional) | Test 1 |
+ | maxsize | 2 |
+
+ @javascript
+ Scenario: Use manual completion as teacher
+ When I log in as "teacher1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > View" page
+ Then the manual completion button for "My Fair Allocation" should be disabled
+
+ @javascript
+ Scenario: Use manual completion student view
+ When I log in as "student1"
+ And I am on the "My Fair Allocation" "mod_ratingallocate > View" page
+ Then the manual completion button of "My Fair Allocation" is displayed as "Mark as done"
+ And I toggle the manual completion state of "My Fair Allocation"
+ And the manual completion button of "My Fair Allocation" is displayed as "Done"
diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php
index f3c2d233..f3ada301 100644
--- a/tests/mod_generator_test.php
+++ b/tests/mod_generator_test.php
@@ -73,6 +73,8 @@ public function test_create_instance(): void {
'algorithmstarttime' => null,
'algorithmstatus' => '0',
'runalgorithmbycron' => '1',
+ 'completionvote' => '0',
+ 'completionallocation' => '0',
];
$this->assertEquals(json_decode(json_encode($expectedvaluesdb, false)), reset($records));
diff --git a/version.php b/version.php
index b122377e..bf60a6cc 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2024060300; // The current module version (Date: YYYYMMDDXX).
+$plugin->version = 2024080900; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2022112800; // Requires Moodle 4.1 and higher.
$plugin->maturity = MATURITY_STABLE;
$plugin->release = 'v4.4-r1';