diff --git a/admin/tool/lp/classes/api.php b/admin/tool/lp/classes/api.php index 425697a4ac20e..d63e2d528182f 100644 --- a/admin/tool/lp/classes/api.php +++ b/admin/tool/lp/classes/api.php @@ -1279,15 +1279,14 @@ public static function update_plan(stdClass $record) { throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', ''); } + // Are we trying to set the plan as complete? + if ($plan->get_status() == plan::STATUS_COMPLETE && $beforestatus != plan::STATUS_COMPLETE) { + throw new coding_exception('To set a plan as complete api::complete_plan() must be used.'); + } + // Wrap the updates in a DB transaction. $transaction = $DB->start_delegated_transaction(); - // Archive user competencies if the status of the plan is changed to complete. - $mustarchivecompetencies = ($plan->get_status() == plan::STATUS_COMPLETE && $beforestatus != plan::STATUS_COMPLETE); - if ($mustarchivecompetencies) { - self::archive_user_competencies_in_plan($plan); - } - // Delete archived user competencies if the status of the plan is changed from complete to another status. $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get_status() != plan::STATUS_COMPLETE); if ($mustremovearchivedcompetencies) { @@ -1349,6 +1348,51 @@ public static function delete_plan($id) { return $success; } + /** + * Complete a plan. + * + * @param int|plan $planorid The plan, or its ID. + * @return bool + */ + public static function complete_plan($planorid) { + global $DB; + + $plan = $planorid; + if (!is_object($planorid)) { + $plan = new plan($planorid); + } + + // Validate that the plan can be managed. + if (!$plan->can_manage()) { + throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', ''); + } + + // Check if the plan was already completed. + if ($plan->get_status() == plan::STATUS_COMPLETE) { + throw new coding_exception('The plan is already complete.'); + } + + $plan->set_status(plan::STATUS_COMPLETE); + + // The user should also be able to manage the plan when it's completed. + if (!$plan->can_manage()) { + throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', ''); + } + + // Do the things. + $transaction = $DB->start_delegated_transaction(); + self::archive_user_competencies_in_plan($plan); + $success = $plan->update(); + + if (!$success) { + $transaction->rollback(new moodle_exception('The plan could not be updated.')); + return $success; + } + + $transaction->allow_commit(); + return $success; + } + /** * List the competencies in a user plan. * diff --git a/admin/tool/lp/classes/plan.php b/admin/tool/lp/classes/plan.php index 89d1b8078867f..18ded46ad1365 100644 --- a/admin/tool/lp/classes/plan.php +++ b/admin/tool/lp/classes/plan.php @@ -252,6 +252,18 @@ public static function can_read_user_draft($planuserid) { || self::can_manage_user_draft($planuserid); } + /** + * Get the recordset of the plans that are due and incomplete. + * + * @return \moodle_recordset + */ + public static function get_recordset_for_due_and_incomplete() { + global $DB; + $sql = "duedate > 0 AND duedate < :now AND status != :status"; + $params = array('now' => time(), 'status' => self::STATUS_COMPLETE); + return $DB->get_recordset_select(self::TABLE, $sql, $params); + } + /** * Return a list of status depending on capabilities. * diff --git a/admin/tool/lp/classes/task/complete_plans_task.php b/admin/tool/lp/classes/task/complete_plans_task.php new file mode 100644 index 0000000000000..8e9838932939d --- /dev/null +++ b/admin/tool/lp/classes/task/complete_plans_task.php @@ -0,0 +1,64 @@ +. + +/** + * Complete plans task. + * + * @package tool_lp + * @copyright 2015 Frédéric Massart - FMCorz.net + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tool_lp\task; +defined('MOODLE_INTERNAL') || die(); + +use tool_lp\api; +use tool_lp\plan; + +/** + * Complete plans task class. + * + * This task should run relatively often because the plans due dates can be set at + * any time of the day in any timezone. + * + * @package tool_lp + * @copyright 2015 Frédéric Massart - FMCorz.net + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class complete_plans_task extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task. + * + * @return string + */ + public function get_name() { + return get_string('completeplanstask', 'tool_lp'); + } + + /** + * Do the job. + */ + public function execute() { + $records = plan::get_recordset_for_due_and_incomplete(); + foreach ($records as $record) { + $plan = new plan(0, $record); + api::complete_plan($plan); + } + $records->close(); + } + +} diff --git a/admin/tool/lp/db/tasks.php b/admin/tool/lp/db/tasks.php new file mode 100644 index 0000000000000..08b95b1a7e68a --- /dev/null +++ b/admin/tool/lp/db/tasks.php @@ -0,0 +1,37 @@ +. + +/** + * Tasks definitions. + * + * @package tool_lp + * @copyright 2015 Frédéric Massart - FMCorz.net + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'tool_lp\task\complete_plans_task', + 'blocking' => 0, + 'minute' => 'R', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*' + ), +); diff --git a/admin/tool/lp/lang/en/tool_lp.php b/admin/tool/lp/lang/en/tool_lp.php index dfd606d51f84e..1aabc0b14e855 100644 --- a/admin/tool/lp/lang/en/tool_lp.php +++ b/admin/tool/lp/lang/en/tool_lp.php @@ -47,6 +47,7 @@ $string['competencyrelatedcompetencies'] = '{$a} related competencies'; $string['competencyrule'] = 'Competency rule'; $string['competencyupdated'] = 'Competency updated'; +$string['completeplanstask'] = 'Complete plans which are due'; $string['configurescale'] = 'Configure scales'; $string['coursecompetencies'] = 'Course competencies'; $string['coursesusingthiscompetency'] = 'Courses using this competency'; diff --git a/admin/tool/lp/tests/api_test.php b/admin/tool/lp/tests/api_test.php index dbec9cb9e650d..1bcad60f4ceae 100644 --- a/admin/tool/lp/tests/api_test.php +++ b/admin/tool/lp/tests/api_test.php @@ -411,14 +411,87 @@ public function test_update_plan() { 'status' => \tool_lp\plan::STATUS_ACTIVE ); $plan = api::create_plan((object)$plan); + + // Silently transition to complete status to avoid errors about transitioning to complete. + $plan->set_status(\tool_lp\plan::STATUS_COMPLETE); + $plan->update(); + $record = $plan->to_record(); $record->name = 'plan create own modified'; - $record->status = \tool_lp\plan::STATUS_COMPLETE; $plan = api::update_plan($record); $this->assertInstanceOf('\tool_lp\plan', $plan); } + /** + * Test that the method to complete a plan. + */ + public function test_complete_plan() { + global $DB; + + $this->resetAfterTest(true); + $this->setAdminUser(); + $dg = $this->getDataGenerator(); + $lpg = $this->getDataGenerator()->get_plugin_generator('tool_lp'); + $user = $dg->create_user(); + + // Create a framework and assign competencies. + $framework = $lpg->create_framework(); + $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id())); + $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id())); + $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id())); + + // Create two plans and assign competencies. + $plan = $lpg->create_plan(array('userid' => $user->id)); + $otherplan = $lpg->create_plan(array('userid' => $user->id)); + + $lpg->create_plan_competency(array('planid' => $plan->get_id(), 'competencyid' => $c1->get_id())); + $lpg->create_plan_competency(array('planid' => $plan->get_id(), 'competencyid' => $c2->get_id())); + $lpg->create_plan_competency(array('planid' => $plan->get_id(), 'competencyid' => $c3->get_id())); + $lpg->create_plan_competency(array('planid' => $otherplan->get_id(), 'competencyid' => $c1->get_id())); + + $uclist = array( + $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get_id(), + 'proficiency' => true, 'grade' => 1 )), + $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get_id(), + 'proficiency' => false, 'grade' => 2 )) + ); + + $this->assertEquals(2, \tool_lp\user_competency::count_records()); + $this->assertEquals(0, \tool_lp\user_competency_plan::count_records()); + + // Change status of the plan to complete. + api::complete_plan($plan); + + // Check that user competencies are now in user_competency_plan objects and still in user_competency. + $this->assertEquals(2, \tool_lp\user_competency::count_records()); + $this->assertEquals(3, \tool_lp\user_competency_plan::count_records()); + + $usercompetenciesplan = \tool_lp\user_competency_plan::get_records(); + + $this->assertEquals($uclist[0]->get_userid(), $usercompetenciesplan[0]->get_userid()); + $this->assertEquals($uclist[0]->get_competencyid(), $usercompetenciesplan[0]->get_competencyid()); + $this->assertEquals($uclist[0]->get_proficiency(), (bool) $usercompetenciesplan[0]->get_proficiency()); + $this->assertEquals($uclist[0]->get_grade(), $usercompetenciesplan[0]->get_grade()); + $this->assertEquals($plan->get_id(), $usercompetenciesplan[0]->get_planid()); + + $this->assertEquals($uclist[1]->get_userid(), $usercompetenciesplan[1]->get_userid()); + $this->assertEquals($uclist[1]->get_competencyid(), $usercompetenciesplan[1]->get_competencyid()); + $this->assertEquals($uclist[1]->get_proficiency(), (bool) $usercompetenciesplan[1]->get_proficiency()); + $this->assertEquals($uclist[1]->get_grade(), $usercompetenciesplan[1]->get_grade()); + $this->assertEquals($plan->get_id(), $usercompetenciesplan[1]->get_planid()); + + $this->assertEquals($user->id, $usercompetenciesplan[2]->get_userid()); + $this->assertEquals($c3->get_id(), $usercompetenciesplan[2]->get_competencyid()); + $this->assertNull($usercompetenciesplan[2]->get_proficiency()); + $this->assertNull($usercompetenciesplan[2]->get_grade()); + $this->assertEquals($plan->get_id(), $usercompetenciesplan[2]->get_planid()); + + // Completing a plan that is completed throws an exception. + $this->setExpectedException('coding_exception'); + api::complete_plan($plan); + } + /** * Test update plan and the managing of archived user competencies. */ @@ -478,7 +551,12 @@ public function test_update_plan_manage_archived_competencies() { $record = $plan->to_record(); $record->status = \tool_lp\plan::STATUS_COMPLETE; - $plan = api::update_plan($record); + try { + $plan = api::update_plan($record); + $this->fail('We cannot complete a plan using api::update_plan().'); + } catch (coding_exception $e) { + } + api::complete_plan($plan); // Check that user compretencies are now in user_competency_plan objects and still in user_competency. $this->assertEquals(2, \tool_lp\user_competency::count_records()); @@ -504,12 +582,6 @@ public function test_update_plan_manage_archived_competencies() { $this->assertNull($usercompetenciesplan[2]->get_grade()); $this->assertEquals($plan->get_id(), $usercompetenciesplan[2]->get_planid()); - $plan = api::update_plan($record); - - // Check that nothing is done if re-updating plan with same status. - $this->assertEquals(2, \tool_lp\user_competency::count_records()); - $this->assertEquals(3, \tool_lp\user_competency_plan::count_records()); - // Change status of the plan to active. $record = $plan->to_record(); $record->status = \tool_lp\plan::STATUS_ACTIVE; diff --git a/admin/tool/lp/version.php b/admin/tool/lp/version.php index e7ba9c53c39f0..9a86cb6e9eb4c 100644 --- a/admin/tool/lp/version.php +++ b/admin/tool/lp/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2015111005; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2015111010; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2014110400; // Requires this Moodle version. $plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).