MDL-52106 tool_lp: Remove rules when manipulating competencies

This commit is contained in:
Frederic Massart 2015-12-17 19:09:32 +08:00
parent 29530d6356
commit 20c2fe3e98
8 changed files with 358 additions and 59 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
define(["core/ajax","core/notification","core/templates","tool_lp/tree","jquery"],function(a,b,c,d,e){var f={},g=0,h="",i="",j="",k=function(a,b){var c=0,d=!1;for(a.haschildren=!1,a.children=[],c=0;c<b.length;c++)d=b[c],d.parentid==a.id&&(a.haschildren=!0,a.children.push(d),k(d,b))},l=function(b){var l=e.Deferred();return c.render("tool_lp/loading",{}).done(function(m,n){c.replaceNodeContents(e(i),m,n);var o=a.call([{methodname:"tool_lp_search_competencies",args:{searchtext:b,competencyframeworkid:g}}]);o[0].done(function(a){f={};var b=0;for(b=0;b<a.length;b++)f[a[b].id]=a[b];var g=[],m=!1;for(b=0;b<a.length;b++)m=a[b],0===parseInt(m.parentid,10)&&(g.push(m),k(m,a));var n={shortname:h,competencies:g};c.render("tool_lp/competencies_tree_root",n).done(function(a,b){c.replaceNodeContents(e(i),e(a).html(),b);var g=new d(i,!1);if(j){var h=e(i).find("[data-id="+j+"]");h.length&&(g.selectItem(h),g.updateFocus(h))}l.resolve(f)}).fail(l.reject)}).fail(l.reject)}),l.promise()},m=function(a,b){var c=b.selected;j=c.attr("data-id")};return{init:function(a,c,d,e){g=a,h=c,i=e,l(d).fail(b.exception),this.on("selectionchanged",m)},on:function(a,b){e(i).on(a,b)},getChildren:function(a){var b=[];return e.each(f,function(c,d){d.parentid==a&&b.push(d)}),b},getCompetencyFrameworkId:function(){return g},getCompetency:function(a){return f[a]},getCompetencyLevel:function(a){var b=this.getCompetency(a),c=b.path.replace(/^\/|\/$/g,"").split("/").length;return c},hasChildren:function(a){return this.getChildren(a).length>0},reloadCompetencies:function(){return l("").fail(b.exception)},listCompetencies:function(){return f}}});
define(["core/ajax","core/notification","core/templates","tool_lp/tree","tool_lp/competency_outcomes","jquery"],function(a,b,c,d,e,f){var g={},h=0,i="",j="",k="",l=function(a,b){var c=0,d=!1;for(a.haschildren=!1,a.children=[],c=0;c<b.length;c++)d=b[c],d.parentid==a.id&&(a.haschildren=!0,a.children.push(d),l(d,b))},m=function(b){var e=f.Deferred();return c.render("tool_lp/loading",{}).done(function(m,n){c.replaceNodeContents(f(j),m,n);var o=a.call([{methodname:"tool_lp_search_competencies",args:{searchtext:b,competencyframeworkid:h}}]);o[0].done(function(a){g={};var b=0;for(b=0;b<a.length;b++)g[a[b].id]=a[b];var h=[],m=!1;for(b=0;b<a.length;b++)m=a[b],0===parseInt(m.parentid,10)&&(h.push(m),l(m,a));var n={shortname:i,competencies:h};c.render("tool_lp/competencies_tree_root",n).done(function(a,b){c.replaceNodeContents(f(j),f(a).html(),b);var h=new d(j,!1);if(k){var i=f(j).find("[data-id="+k+"]");i.length&&(h.selectItem(i),h.updateFocus(i))}e.resolve(g)}).fail(e.reject)}).fail(e.reject)}),e.promise()},n=function(a,b){var c=b.selected;k=c.attr("data-id")};return{init:function(a,c,d,e){h=a,i=c,j=e,m(d).fail(b.exception),this.on("selectionchanged",n)},on:function(a,b){f(j).on(a,b)},getChildren:function(a){var b=[];return f.each(g,function(c,d){d.parentid==a&&b.push(d)}),b},getCompetencyFrameworkId:function(){return h},getCompetency:function(a){return g[a]},getCompetencyLevel:function(a){var b=this.getCompetency(a),c=b.path.replace(/^\/|\/$/g,"").split("/").length;return c},hasChildren:function(a){return this.getChildren(a).length>0},hasRule:function(a){var b=this.getCompetency(a);return b?b.ruleoutcome!=e.OUTCOME_NONE&&b.ruletype:!1},reloadCompetencies:function(){return m("").fail(b.exception)},listCompetencies:function(){return g}}});

View file

@ -72,8 +72,30 @@ define(['jquery',
// We are adding at a sub node.
params.parentid = parent.id;
}
var queryparams = $.param(params);
window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
var relocate = function() {
var queryparams = $.param(params);
window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
};
if (parent !== null && treeModel.hasRule(parent.id)) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname },
{ key: 'yes', component: 'core' },
{ key: 'no', component: 'core' }
]).done(function (strings) {
notification.confirm(
strings[0],
strings[1],
strings[2],
strings[3],
relocate
);
}).fail(notification.exception);
} else {
relocate();
}
};
/**
@ -81,14 +103,6 @@ define(['jquery',
* @method doMove
*/
var doMove = function() {
if (typeof (moveTarget) === "undefined") {
// This is a top level node.
moveTarget = 0;
}
if (moveTarget == moveSource) {
return;
}
var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
var requests = ajax.call([{
methodname: 'tool_lp_set_parent_competency',
@ -101,6 +115,61 @@ define(['jquery',
requests[1].done(reloadPage).fail(notification.exception);
};
/**
* Confirms a competency move.
*
* @method confirmMove
*/
var confirmMove = function() {
moveTarget = typeof moveTarget === "undefined" ? 0 : moveTarget;
if (moveTarget == moveSource) {
// No move to do.
return;
}
var targetComp = treeModel.getCompetency(moveTarget) || {},
sourceComp = treeModel.getCompetency(moveSource) || {},
confirmMessage = 'movecompetencywillresetrules',
showConfirm = false;
// We shouldn't be moving the competency to the same parent.
if (sourceComp.parentid == moveTarget) {
return;
}
// If we are moving to a child of self.
if (targetComp.path && targetComp.path.indexOf('/' + sourceComp.id + '/') >= 0) {
confirmMessage = 'movecompetencytochildofselfwillresetrules';
// Show a confirmation if self has rules, as they'll disappear.
showConfirm = showConfirm || treeModel.hasRule(sourceComp.id);
}
// Show a confirmation if the current parent, or the destination have rules.
showConfirm = showConfirm || (treeModel.hasRule(targetComp.id) || treeModel.hasRule(sourceComp.parentid));
// Show confirm, and/or do the things.
if (showConfirm) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: confirmMessage, component: 'tool_lp' },
{ key: 'yes', component: 'moodle' },
{ key: 'no', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency X?
strings[2], // Delete.
strings[3], // Cancel.
doMove
);
}).fail(notification.exception);
} else {
doMove();
}
};
/**
* A move competency popup was opened - initialise the aria tree in it.
* @method initMovePopup
@ -116,7 +185,7 @@ define(['jquery',
});
treeRoot.show();
body.on('click', '[data-action="move"]', function() { popup.close(); doMove(); });
body.on('click', '[data-action="move"]', function() { popup.close(); confirmMove(); });
body.on('click', '[data-action="cancel"]', function() { popup.close(); });
};
@ -144,7 +213,8 @@ define(['jquery',
* A node was chosen and "Move" was selected from the menu. Open a popup to select the target.
* @method moveHandler
*/
var moveHandler = function() {
var moveHandler = function(e) {
e.preventDefault();
var competency = $('[data-region="competencyactions"]').data('competency');
// Remember what we are moving.
@ -417,31 +487,27 @@ define(['jquery',
* @method deleteCompetencyHandler
*/
var deleteCompetencyHandler = function() {
var competency = $('[data-region="competencyactions"]').data('competency');
var competency = $('[data-region="competencyactions"]').data('competency'),
confirmMessage = 'deletecompetency';
var context = {
competency: competency
};
templates.render('tool_lp/competency_summary', context)
.done(function(html) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'deletecompetency', component: 'tool_lp', param: html },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency X?
strings[2], // Delete.
strings[3], // Cancel.
doDelete
);
}).fail(notification.exception);
}).fail(notification.exception);
if (treeModel.hasRule(competency.parentid)) {
confirmMessage = 'deletecompetencyparenthasrule';
}
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: confirmMessage, component: 'tool_lp', param: competency.shortname },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency X?
strings[2], // Delete.
strings[3], // Cancel.
doDelete
);
}).fail(notification.exception);
};
/**
@ -489,7 +555,7 @@ define(['jquery',
moveTarget = $(e.target).parent().data('id');
$(this).removeClass('currentdragtarget');
doMove();
confirmMove();
};
/**

View file

@ -21,8 +21,8 @@
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax', 'core/notification', 'core/templates', 'tool_lp/tree', 'jquery'],
function(ajax, notification, templates, Ariatree, $) {
define(['core/ajax', 'core/notification', 'core/templates', 'tool_lp/tree', 'tool_lp/competency_outcomes', 'jquery'],
function(ajax, notification, templates, Ariatree, CompOutcomes, $) {
// Private variables and functions.
/** @var {Object[]} competencies - Cached list of competencies */
@ -220,6 +220,21 @@ define(['core/ajax', 'core/notification', 'core/templates', 'tool_lp/tree', 'jqu
return this.getChildren(id).length > 0;
},
/**
* Does the competency have a rule?
*
* @param {Number} id The competency ID.
* @return {Boolean}
*/
hasRule: function(id) {
var comp = this.getCompetency(id);
if (comp) {
return comp.ruleoutcome != CompOutcomes.OUTCOME_NONE
&& comp.ruletype;
}
return false;
},
/**
* Reload all the page competencies framework competencies.
* @method reloadCompetencies

View file

@ -85,9 +85,15 @@ class api {
// Reset the sortorder, use reorder instead.
$competency->set_sortorder(null);
$competency->create();
// Reset the rule of the parent.
$parent = $competency->get_parent();
if ($parent) {
$parent->reset_rule();
$parent->update();
}
// OK - all set.
$id = $competency->create();
return $competency;
}
@ -105,6 +111,13 @@ class api {
// First we do a permissions check.
require_capability('tool/lp:competencymanage', $competency->get_context());
// Reset the rule of the parent.
$parent = $competency->get_parent();
if ($parent) {
$parent->reset_rule();
$parent->update();
}
// OK - all set.
return $competency->delete();
}
@ -195,39 +208,62 @@ class api {
* @return boolean
*/
public static function set_parent_competency($id, $newparentid) {
global $DB;
$current = new competency($id);
// First we do a permissions check.
require_capability('tool/lp:competencymanage', $current->get_context());
// This will throw an exception if the parent does not exist.
// Check the current one too.
$parentframeworkid = $current->get_competencyframeworkid();
$parentpath = '/0/';
if ($newparentid) {
$parent = new competency($newparentid);
$parentframeworkid = $parent->get_competencyframeworkid();
$parentpath = $parent->get_path();
if ($id == $newparentid) {
throw new coding_exception('Can not set a competency as a parent of itself.');
} if ($newparentid == $current->get_parentid()) {
throw new coding_exception('Can not move a competency to the same location.');
}
// If we are moving a node to a child of itself, promote all the child nodes by one level.
// Some great variable assignment right here.
$currentparent = $current->get_parent();
$parent = !empty($newparentid) ? new competency($newparentid) : null;
$parentpath = !empty($parent) ? $parent->get_path() : '/0/';
// We're going to change quite a few things.
$transaction = $DB->start_delegated_transaction();
// If we are moving a node to a child of itself:
// - promote all the child nodes by one level.
// - remove the rule on self.
// - re-read the parent.
$newparents = explode('/', $parentpath);
if (in_array($current->get_id(), $newparents)) {
$filters = array('parentid' => $current->get_id(), 'competencyframeworkid' => $current->get_competencyframeworkid());
$children = self::list_competencies($filters, 'id');
$children = competency::get_records(array('parentid' => $current->get_id()), 'id');
foreach ($children as $child) {
$child->set_parentid($current->get_parentid());
$child->update();
}
// Reset the rule on self as our children have changed.
$current->reset_rule();
// The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).
$parent->read();
}
$current->set_parentid($newparentid);
// Reset the rules of initial parent and destination.
if (!empty($currentparent)) {
$currentparent->reset_rule();
$currentparent->update();
}
if (!empty($parent)) {
$parent->reset_rule();
$parent->update();
}
// OK - all set.
return $current->update();
// Do the actual move.
$current->set_parentid($newparentid);
$current->update();
// All right, let's commit this.
$transaction->allow_commit();
return true;
}
/**

View file

@ -352,6 +352,17 @@ class competency extends persistent {
return $DB->count_records_select(self::TABLE, "id $insql AND parentid = :parentid", $params) == count($ids);
}
/**
* Reset the rule.
*
* @return void
*/
public function reset_rule() {
$this->set_ruleoutcome(static::OUTCOME_NONE);
$this->set_ruletype(null);
$this->set_ruleconfig(null);
}
/**
* Helper method to set the path.
*

View file

@ -25,6 +25,7 @@
$string['actions'] = 'Actions';
$string['addcohorts'] = 'Add cohorts';
$string['addcompetency'] = 'Add competency';
$string['addingcompetencywillresetparentrule'] = 'Adding a new competency will remove the rule set on \'{$a}\'. Do you want to continue?';
$string['addnewcompetency'] = 'Add new competency';
$string['addnewcompetencyframework'] = 'Add new competency framework';
$string['addnewplan'] = 'Add new learning plan';
@ -69,6 +70,7 @@ $string['coveragesummary'] = '{$a->competenciescoveredcount} of {$a->competencie
$string['createplans'] = 'Create plans';
$string['default'] = 'Default';
$string['deletecompetency'] = 'Delete competency \'{$a}\'?';
$string['deletecompetencyparenthasrule'] = 'Delete competency \'{$a}\'? This will also remove the rule set on its parent.';
$string['deletecompetencyframework'] = 'Delete competency framework \'{$a}\'?';
$string['deleteplan'] = 'Delete plan \'{$a}\'?';
$string['deletetemplate'] = 'Delete learning plan template \'{$a}\'?';
@ -154,6 +156,8 @@ $string['move'] = 'Move';
$string['movecompetency'] = 'Move competency';
$string['movecompetencyafter'] = 'Move competency after \'{$a}\'';
$string['movecompetencyframework'] = 'Move competency framework';
$string['movecompetencywillresetrules'] = 'Moving the competency will remove the rules set on its parent and destination. Do you want to continune?';
$string['movecompetencytochildofselfwillresetrules'] = 'Moving the competency will remove its own rule, and the rules set on its parent and destination. Do you want to continune?';
$string['moveframeworkafter'] = 'Move competency framework after \'{$a}\'';
$string['movetonewparent'] = 'Relocate';
$string['myplans'] = 'My plans';

View file

@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
use tool_lp\api;
use tool_lp\competency;
use tool_lp\evidence;
use tool_lp\user_competency;
@ -1853,4 +1854,170 @@ class tool_lp_api_testcase extends advanced_testcase {
}
$this->assertEquals(0, count(array_diff($ucp1, $ucp2)));
}
protected function setup_framework_for_reset_rules_tests() {
$this->resetAfterTest(true);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('tool_lp');
$this->setAdminUser();
$f1 = $lpg->create_framework();
$c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1->get_id()));
$c1a1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1a->get_id()));
$c1a1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1a1->get_id()));
$c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c1b1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1b->get_id()));
$c1b1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1b1->get_id()));
$c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c1->set_ruleoutcome(competency::OUTCOME_EVIDENCE);
$c1->set_ruletype('tool_lp\\competency_rule_all');
$c1->update();
$c1a->set_ruleoutcome(competency::OUTCOME_EVIDENCE);
$c1a->set_ruletype('tool_lp\\competency_rule_all');
$c1a->update();
$c1a1->set_ruleoutcome(competency::OUTCOME_EVIDENCE);
$c1a1->set_ruletype('tool_lp\\competency_rule_all');
$c1a1->update();
$c1b->set_ruleoutcome(competency::OUTCOME_EVIDENCE);
$c1b->set_ruletype('tool_lp\\competency_rule_all');
$c1b->update();
$c2->set_ruleoutcome(competency::OUTCOME_EVIDENCE);
$c2->set_ruletype('tool_lp\\competency_rule_all');
$c2->update();
return array(
'f1' => $f1,
'c1' => $c1,
'c1a' => $c1a,
'c1a1' => $c1a1,
'c1a1a' => $c1a1a,
'c1b' => $c1b,
'c1b1' => $c1b1,
'c1b1a' => $c1b1a,
'c2' => $c2,
'c2a' => $c2a,
);
}
public function test_moving_competency_reset_rules_updown() {
extract($this->setup_framework_for_reset_rules_tests());
// Moving up and down doesn't change anything.
api::move_down_competency($c1a->get_id());
$c1->read();
$c1a->read();
$c1a1->read();
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get_ruleoutcome());
api::move_up_competency($c1a->get_id());
$c1->read();
$c1a->read();
$c1a1->read();
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get_ruleoutcome());
}
public function test_moving_competency_reset_rules_parent() {
extract($this->setup_framework_for_reset_rules_tests());
// Moving out of parent will reset the parent, and the destination.
api::set_parent_competency($c1a->get_id(), $c1b->get_id());
$c1->read();
$c1a->read();
$c1a1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_NONE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_NONE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
public function test_moving_competency_reset_rules_totoplevel() {
extract($this->setup_framework_for_reset_rules_tests());
// Moving to top level only affects the initial parent.
api::set_parent_competency($c1a1->get_id(), 0);
$c1->read();
$c1a->read();
$c1a1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_NONE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
public function test_moving_competency_reset_rules_fromtoplevel() {
extract($this->setup_framework_for_reset_rules_tests());
// Moving from top level only affects the destination parent.
api::set_parent_competency($c2->get_id(), $c1a1->get_id());
$c1->read();
$c1a->read();
$c1a1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_NONE, $c1a1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
public function test_moving_competency_reset_rules_child() {
extract($this->setup_framework_for_reset_rules_tests());
// Moving to a child of self resets self, parent and destination.
api::set_parent_competency($c1a->get_id(), $c1a1->get_id());
$c1->read();
$c1a->read();
$c1a1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_NONE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_NONE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_NONE, $c1a1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
public function test_create_competency_reset_rules() {
extract($this->setup_framework_for_reset_rules_tests());
// Adding a new competency resets the rule of its parent.
api::create_competency((object) array('shortname' => 'A', 'parentid' => $c1->get_id(), 'idnumber' => 'A',
'competencyframeworkid' => $f1->get_id()));
$c1->read();
$c1a->read();
$c1a1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_NONE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
public function test_delete_competency_reset_rules() {
extract($this->setup_framework_for_reset_rules_tests());
// Deleting a competency resets the rule of its parent.
api::delete_competency($c1a->get_id());
$c1->read();
$c1b->read();
$c2->read();
$this->assertEquals(competency::OUTCOME_NONE, $c1->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get_ruleoutcome());
$this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get_ruleoutcome());
}
}