Skip to content

Commit

Permalink
MDL-80984 gradereport_grader: apply penalty to overridden grade
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan Nguyen committed Dec 17, 2024
1 parent 5695e61 commit 9fd8342
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 7 deletions.
5 changes: 5 additions & 0 deletions admin/settings/grades.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@
new lang_string('gradepenalty_supportedplugins', 'grades'),
new lang_string('gradepenalty_supportedplugins_help', 'grades'), [], $options));

// Option to apply penalty to overridden grades.
$temp->add(new admin_setting_configcheckbox('gradepenalty_overriddengrade',
new lang_string('gradepenalty_overriddengrade', 'grades'),
new lang_string('gradepenalty_overriddengrade_help', 'grades'), 0));

$ADMIN->add('gradepenalty', $temp);
}

Expand Down
2 changes: 2 additions & 0 deletions grade/report/grader/lang/en/gradereport_grader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

$string['applypenaltytext'] = 'Penalty exemption';
$string['applypenaltytooltip'] = 'If the box is checked, penalty will not be applied to the overridden grade.';
$string['aria:dropdowncolumns'] = 'Collapsed columns found';
$string['clearsearch'] = 'Clear searched users';
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge rounded-pill bg-primary text-white ms-1" data-collapse="count">{$a}</span>';
Expand Down
47 changes: 45 additions & 2 deletions grade/report/grader/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,17 +241,29 @@ public function process_data($data) {
continue;
}

// Detect changes in exemption checkbox.
if ($oldvalue->can_apply_penalty_to_overridden_mark()) {
if (!isset($data->exemption[$userid][$itemid])) {
$newvalue = format_float($postedvalue - $oldvalue->deductedmark,
$oldvalue->grade_item->get_decimals());
} else {
$newvalue = $postedvalue;
}
} else {
$newvalue = $postedvalue;
}

// If the grade item uses a custom scale
if (!empty($oldvalue->grade_item->scaleid)) {

if ((int)$oldvalue->finalgrade === (int)$postedvalue) {
if ((int)$oldvalue->finalgrade === (int)$newvalue) {
continue;
}
} else {
// The grade item uses a numeric scale

// Format the finalgrade from the DB so that it matches the grade from the client
if ($postedvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
if ($newvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
continue;
}
}
Expand Down Expand Up @@ -326,8 +338,19 @@ public function process_data($data) {
}
}

// Save final grade, without penalty.
$gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', false,
FORMAT_MOODLE, null, null, true);

// Save overridden mark, without penalty.
$gradeitem->update_overridden_mark($userid, $finalgrade);

// Apply penalty.
if ($oldvalue->can_apply_penalty_to_overridden_mark() && !isset($data->exemption[$userid][$itemid])) {
// Apply penalty.
$gradeitem->update_final_grade($userid, $newvalue, 'gradepenalty', false,
FORMAT_MOODLE, null, null, true);
}
}
}
}
Expand Down Expand Up @@ -1146,6 +1169,26 @@ public function get_right_rows(bool $displayaverages): array {
if ($context->statusicons) {
$context->extraclasses .= ' statusicons';
}

// Show option for user to apply penalty or not.
if ($grade->can_apply_penalty_to_overridden_mark()) {
$context->canapplypenalty = true;
if ($grade->is_penalty_applied_to_final_grade()) {
// We are editing the original grade value, ie, before applying penalty.
$context->value = format_float($gradeval + $grade->deductedmark, $decimalpoints);
} else {
$context->value = $value;
}
// Current grade.
$context->effectivegrade = $value;
$context->deductedmark = format_float($grade->deductedmark, $decimalpoints);
$context->penaltyexempted = !$grade->is_penalty_applied_to_final_grade();
$context->exemptionid = 'exemption' . $userid . '_' . $item->id;
$context->exemptionname = 'exemption[' . $userid . '][' . $item->id .']';
$context->exemptionlabel = $gradelabel . ' ' .
get_string('applypenaltytext', 'gradereport_grader');
$context->exemptiontooltip = get_string('applypenaltytooltip', 'gradereport_grader');
}
} else {
$context->extraclasses = 'gradevalue' . $hidden . $gradepass;
$context->text = format_float($gradeval, $decimalpoints);
Expand Down
10 changes: 8 additions & 2 deletions grade/report/grader/templates/cell.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"extraclasses": "statusicons",
"value": "Text information",
"tabindex": "1",
"name": "grade[313][624]"
"name": "grade[313][624]",
"canapplypenalty": "false"
}
}}
<div class="d-flex flex-column h-100" data-collapse="content">
Expand All @@ -41,7 +42,12 @@
{{>core_grades/grades/grader/scale}}
{{/scale}}
{{^scale}}
{{>core_grades/grades/grader/input}}
{{#canapplypenalty}}
{{>core_grades/grades/grader/overriden_with_penalty}}
{{/canapplypenalty}}
{{^canapplypenalty}}
{{>core_grades/grades/grader/input}}
{{/canapplypenalty}}
{{/scale}}
{{/iseditable}}
{{^iseditable}}
Expand Down
10 changes: 10 additions & 0 deletions grade/report/grader/tests/behat/behat_gradereport_grader.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,14 @@ public static function get_partial_named_selectors(): array {
),
];
}

/**
* Enable penalty for overridden grade.
*
* @Given I enable penalty for overridden grade
*/
public function i_enable_penalty_for_overridden_grade(): void {
set_config('gradepenalty_enabled', 1);
set_config('gradepenalty_overriddengrade', 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@gradereport @gradereport_grader @gradereport_grader_deduction
Feature: As a teacher, I want to override a grade with a deduction and check the gradebook.

Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And I enable penalty for overridden grade
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "grade items" exist:
| itemname | grademin | grademax | course |
| Manual grade 01 | 0 | 100 | C1 |
| Manual grade 02 | 0 | 100 | C1 |
And the following "grade grades" exist:
| gradeitem | user | grade | deductedmark |
| Manual grade 01 | student1 | 60 | 10 |
| Manual grade 02 | student1 | 80 | 20 |
When I log in as "teacher1"

@javascript
Scenario: Override a grade with a deduction and check the gradebook
Given I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 60 | 80 | 140 |
And I turn editing mode on
And I set the following fields to these values:
| Student 1 Manual grade 01 grade | 80 |
| Student 1 Manual grade 01 Penalty exemption | 0 |
| Student 1 Manual grade 02 Penalty exemption | 1 |
And I click on "Save changes" "button"
When I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 70 | 100 | 170 |
When I turn editing mode on
And I set the following fields to these values:
| Student 1 Manual grade 02 grade | 100 |
| Student 1 Manual grade 02 Penalty exemption | 0 |
And I click on "Save changes" "button"
And I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 70 | 80 | 150 |
54 changes: 54 additions & 0 deletions grade/templates/grades/grader/overriden_with_penalty.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_grades/grades/grader/overriden_with_penalty
Input template for grader report cell.
Example context (json):
{
"deductedmark": "30.00",
"effectivegrade": "70.00",
"exemptionid": "exemption3_2",
"exemptionlabel": "Exempt penalty",
"exemptiontooltip": "Exempt penalty",
"exemptionname": "exemption[3][2]",
"penaltyexempted": "true",
"id": "grade_313_624",
"label": "grade_313_624",
"name": "grade[313][624]"
}
}}

<div class="container">
<div class="row">
<div class="mr-0">Original grade: </div>
{{>core_grades/grades/grader/input}}
</div>

<div class="row">
<div class="mr-0">Current grade: {{{effectivegrade}}}</div>
</div>

<div class="row">
<div class="mr-0" title="{{exemptiontooltip}}">Exempt penalty:</div>
<label for="{{exemptionid}}" class="accesshide">{{{exemptionlabel}}}</label>
<input type="checkbox" name="{{exemptionname}}"
value="{{deductedmark}}" id="{{exemptionid}}"
{{#penaltyexempted}}checked{{/penaltyexempted}}>
</div>
</div>
2 changes: 2 additions & 0 deletions lang/en/grades.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@
$string['gradepenalty_enabled'] = 'Grade penalty';
$string['gradepenalty_enabled_help'] = 'If enabled, the penalty will be applied to the grades of supported modules.';
$string['gradepenalty_indicator_info'] = 'Late penalty applied -{$a} marks';
$string['gradepenalty_overriddengrade'] = 'Apply penalty to overridden grades';
$string['gradepenalty_overriddengrade_help'] = 'If enabled, the penalty will be applied to overridden grades.';
$string['gradepenalty_supportedplugins'] = 'Supported modules';
$string['gradepenalty_supportedplugins_help'] = 'Enable the grade penalty for the selected modules.';
$string['gradepointdefault'] = 'Grade point default';
Expand Down
1 change: 1 addition & 0 deletions lib/db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,7 @@
<FIELD NAME="aggregationstatus" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="unknown" SEQUENCE="false" COMMENT="One of several values describing how this grade_grade was used when calculating the aggregation. Possible values are &quot;unknown&quot;, &quot;dropped&quot;, &quot;novalue&quot;, &quot;used&quot;"/>
<FIELD NAME="aggregationweight" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If the aggregationstatus == 'included', then this is the percent this item contributed to the aggregation."/>
<FIELD NAME="deductedmark" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The mark deducted from final grade"/>
<FIELD NAME="overriddenmark" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The overridden mark before applied penalty"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
16 changes: 16 additions & 0 deletions lib/db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -1338,5 +1338,21 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2024120500.03);
}

if ($oldversion < 2024121301.00) {

// Define field overriddenmark to be added to grade_grades.
$table = new xmldb_table('grade_grades');
$field = new xmldb_field('overriddenmark', XMLDB_TYPE_NUMBER, '10, 5', null,
XMLDB_NOTNULL, null, '0', 'deductedmark');

// Conditionally launch add field penalty.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2024121301.00);
}

return true;
}
46 changes: 45 additions & 1 deletion lib/grade/grade_grade.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class grade_grade extends grade_object {
public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
'locktime', 'exported', 'overridden', 'excluded', 'timecreated',
'timemodified', 'aggregationstatus', 'aggregationweight', 'deductedmark');
'timemodified', 'aggregationstatus', 'aggregationweight', 'deductedmark', 'overriddenmark');

/**
* Array of optional fields with default values (these should match db defaults)
Expand Down Expand Up @@ -221,6 +221,9 @@ class grade_grade extends grade_object {
/** @var float $deductedmark mark deducted from final grade */
public $deductedmark = 0;

/** @var float $overriddenmark mark overridden by teacher */
public $overriddenmark = 0;

/**
* Returns array of grades for given grade_item+users
*
Expand Down Expand Up @@ -1287,4 +1290,45 @@ public function get_context() {
$this->load_grade_item();
return $this->grade_item->get_context();
}

/**
* Determine if penalty is applied to this overridden mark.
*
* @return bool whether penalty is applied
*/
public function can_apply_penalty_to_overridden_mark(): bool {
// Check config.
if (!get_config('core', 'gradepenalty_overriddengrade')) {
return false;
}

// Check if the raw grade was deducted.
if ($this->deductedmark <= 0) {
return false;
}

return true;
}

/**
* Whether the penalty is applied to this overridden mark.
*
* @return bool whether penalty is applied
*/
public function is_penalty_applied_to_overridden_mark(): bool {
return $this->overridden > 0 && $this->overriddenmark > $this->finalgrade;
}

/**
* Whether the penalty is applied to this final grade.
*
* @return bool whether penalty is applied
*/
public function is_penalty_applied_to_final_grade(): bool {
if ($this->overridden > 0) {
return $this->is_penalty_applied_to_overridden_mark();
} else {
return $this->deductedmark > 0;
}
}
}
15 changes: 15 additions & 0 deletions lib/grade/grade_item.php
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,21 @@ public function update_deducted_mark(int $userid, float $deductedmark): void {
$grade->update();
}

/**
* Update overridden mark for given user
*
* @param int $userid The graded user
* @param float $overriddenmark The mark deducted from final grade
*/
public function update_overridden_mark(int $userid, float $overriddenmark): void {
$grade = new grade_grade([
'itemid' => $this->id,
'userid' => $userid,
]);
$grade->overriddenmark = $overriddenmark;
$grade->update();
}

/**
* Calculates final grade values using the formula in the calculation property.
* The parameters are taken from final grades of grade items in current course only.
Expand Down
2 changes: 1 addition & 1 deletion lib/gradelib.php
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ function show_penalty_indicator(grade_grade $grade): string {
global $PAGE;

// Show penalty indicator if penalty is greater than 0.
if ($grade->deductedmark > 0) {
if ($grade->is_penalty_applied_to_final_grade()) {
$indicator = new \core_grades\output\penalty_indicator(2, $grade);
$renderer = $PAGE->get_renderer('core_grades');
return $renderer->render_penalty_indicator($indicator);
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

defined('MOODLE_INTERNAL') || die();

$version = 2024121300.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2024121301.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '5.0dev (Build: 20241213)'; // Human-friendly version name
Expand Down

0 comments on commit 9fd8342

Please sign in to comment.