add assessment feature
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateStudentAssessmentTables extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (! $this->db->tableExists('question_pools')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'name' => ['type' => 'VARCHAR', 'constraint' => 150],
|
||||
'subject_tag' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'created_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('subject_tag');
|
||||
$this->forge->createTable('question_pools', true);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('assessment_questions')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'pool_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'type' => ['type' => 'ENUM', 'constraint' => ['multiple_choice', 'short_answer', 'true_false', 'essay']],
|
||||
'text' => ['type' => 'TEXT'],
|
||||
'options' => ['type' => 'TEXT', 'null' => true],
|
||||
'correct_answer' => ['type' => 'TEXT', 'null' => true],
|
||||
'points' => ['type' => 'DECIMAL', 'constraint' => '8,2', 'default' => 0],
|
||||
'order_index' => ['type' => 'INT', 'constraint' => 11, 'default' => 0],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['pool_id', 'order_index']);
|
||||
$this->forge->addForeignKey('pool_id', 'question_pools', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->forge->createTable('assessment_questions', true);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('assessment_forms')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'name' => ['type' => 'VARCHAR', 'constraint' => 150],
|
||||
'pool_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'created_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'status' => ['type' => 'ENUM', 'constraint' => ['draft', 'published', 'archived'], 'default' => 'draft'],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['pool_id', 'status']);
|
||||
$this->forge->addForeignKey('pool_id', 'question_pools', 'id', 'RESTRICT', 'CASCADE');
|
||||
$this->forge->createTable('assessment_forms', true);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('assessment_form_questions')) {
|
||||
$this->forge->addField([
|
||||
'form_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'question_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'order_index' => ['type' => 'INT', 'constraint' => 11, 'default' => 0],
|
||||
]);
|
||||
$this->forge->addKey(['form_id', 'question_id'], true);
|
||||
$this->forge->addKey(['form_id', 'order_index']);
|
||||
$this->forge->addForeignKey('form_id', 'assessment_forms', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->forge->addForeignKey('question_id', 'assessment_questions', 'id', 'RESTRICT', 'CASCADE');
|
||||
$this->forge->createTable('assessment_form_questions', true);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('student_assessments')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'form_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'student_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'status' => ['type' => 'ENUM', 'constraint' => ['not_started', 'in_progress', 'completed', 'graded'], 'default' => 'not_started'],
|
||||
'assigned_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'started_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'submitted_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'graded_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'graded_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
|
||||
'score' => ['type' => 'DECIMAL', 'constraint' => '10,2', 'null' => true],
|
||||
'education_committee_note' => ['type' => 'TEXT', 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey(['form_id', 'student_id'], 'uq_assessment_form_student');
|
||||
$this->forge->addKey(['student_id', 'status']);
|
||||
$this->forge->addForeignKey('form_id', 'assessment_forms', 'id', 'RESTRICT', 'CASCADE');
|
||||
$this->forge->addForeignKey('student_id', 'students', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->forge->createTable('student_assessments', true);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('student_answers')) {
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'student_assessment_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'question_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
|
||||
'answer_value' => ['type' => 'TEXT', 'null' => true],
|
||||
'is_correct' => ['type' => 'TINYINT', 'constraint' => 1, 'null' => true],
|
||||
'points_awarded' => ['type' => 'DECIMAL', 'constraint' => '8,2', 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey(['student_assessment_id', 'question_id'], 'uq_student_answer_question');
|
||||
$this->forge->addForeignKey('student_assessment_id', 'student_assessments', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->forge->addForeignKey('question_id', 'assessment_questions', 'id', 'RESTRICT', 'CASCADE');
|
||||
$this->forge->createTable('student_answers', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
foreach (['student_answers', 'student_assessments', 'assessment_form_questions', 'assessment_forms', 'assessment_questions', 'question_pools'] as $table) {
|
||||
$this->forge->dropTable($table, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddAssessmentNavigation extends Migration
|
||||
{
|
||||
private const ITEMS = [
|
||||
'administrator/assessments' => 'Assessment Management',
|
||||
'student/assessments' => 'My Assessments',
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('nav_items')) return;
|
||||
$parentColumn = $this->parentColumn();
|
||||
$studentAffairsId = null;
|
||||
if ($parentColumn !== null) {
|
||||
$parent = $this->db->table('nav_items')->select('id')->where('label', 'Student-Affairs')->where($parentColumn, null)->get()->getRowArray();
|
||||
$studentAffairsId = $parent ? (int) $parent['id'] : null;
|
||||
}
|
||||
$adminId = $this->upsertItem(self::ITEMS['administrator/assessments'], 'administrator/assessments', $studentAffairsId, 7);
|
||||
$studentId = $this->upsertItem(self::ITEMS['student/assessments'], 'student/assessments', null, 80);
|
||||
if ($this->db->tableExists('role_nav_items') && $this->db->tableExists('roles')) {
|
||||
$roles = $this->db->table('roles')->select('id, name')->get()->getResultArray();
|
||||
foreach ($roles as $role) {
|
||||
$token = strtolower(str_replace([' ', '-'], '_', trim((string) ($role['name'] ?? ''))));
|
||||
if (in_array($token, ['parent', 'student'], true)) {
|
||||
$this->grant((int) $role['id'], $studentId);
|
||||
} elseif (! in_array($token, ['guest', 'teacher', 'teacher_assistant', 'assistant_teacher', 'ta'], true)) {
|
||||
$this->grant((int) $role['id'], $adminId);
|
||||
}
|
||||
}
|
||||
}
|
||||
cache()->clean();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! $this->db->tableExists('nav_items')) return;
|
||||
foreach (array_keys(self::ITEMS) as $url) {
|
||||
$row = $this->db->table('nav_items')->select('id')->where('url', $url)->get()->getRowArray();
|
||||
if ($row && $this->db->tableExists('role_nav_items')) {
|
||||
$this->db->table('role_nav_items')->where('nav_item_id', (int) $row['id'])->delete();
|
||||
}
|
||||
$this->db->table('nav_items')->where('url', $url)->delete();
|
||||
}
|
||||
cache()->clean();
|
||||
}
|
||||
|
||||
private function upsertItem(string $label, string $url, ?int $parentId, int $sort): int
|
||||
{
|
||||
$existing = $this->db->table('nav_items')->select('id')->where('url', $url)->get()->getRowArray();
|
||||
$data = ['label' => $label, 'is_enabled' => 1, 'sort_order' => $sort, 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$parentColumn = $this->parentColumn();
|
||||
if ($parentColumn !== null) $data[$parentColumn] = $parentId;
|
||||
if ($this->db->fieldExists('icon_class', 'nav_items')) $data['icon_class'] = 'bi bi-ui-checks-grid';
|
||||
if ($existing) {
|
||||
$this->db->table('nav_items')->where('id', (int) $existing['id'])->update($data);
|
||||
return (int) $existing['id'];
|
||||
}
|
||||
$data += ['url' => $url, 'created_at' => date('Y-m-d H:i:s')];
|
||||
$this->db->table('nav_items')->insert($data);
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
private function grant(int $roleId, int $navItemId): void
|
||||
{
|
||||
if ($roleId <= 0 || $navItemId <= 0) return;
|
||||
$builder = $this->db->table('role_nav_items');
|
||||
if ($builder->where(['role_id' => $roleId, 'nav_item_id' => $navItemId])->countAllResults() > 0) return;
|
||||
$data = ['role_id' => $roleId, 'nav_item_id' => $navItemId];
|
||||
if ($this->db->fieldExists('created_at', 'role_nav_items')) $data['created_at'] = date('Y-m-d H:i:s');
|
||||
if ($this->db->fieldExists('updated_at', 'role_nav_items')) $data['updated_at'] = date('Y-m-d H:i:s');
|
||||
$this->db->table('role_nav_items')->insert($data);
|
||||
}
|
||||
|
||||
private function parentColumn(): ?string
|
||||
{
|
||||
if ($this->db->fieldExists('menu_parent_id', 'nav_items')) return 'menu_parent_id';
|
||||
if ($this->db->fieldExists('parent_id', 'nav_items')) return 'parent_id';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class SeedNewStudentAssessmentQuestions extends Migration
|
||||
{
|
||||
private const POOL_NAME = 'New Student Assessment';
|
||||
|
||||
private const QUESTIONS = [
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'What is the student’s Islamic education background?',
|
||||
],
|
||||
[
|
||||
'type' => 'short_answer',
|
||||
'text' => 'What is the student’s Arabic competency level?',
|
||||
],
|
||||
[
|
||||
'type' => 'short_answer',
|
||||
'text' => 'What is the student’s English competency level?',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'Has the student attended any Sunday/Islamic school in the past? If YES, which one?',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'Has the student memorized any Surahs? If yes, which one(s)?',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'Why have the parents chosen AlRahma Sunday School for their child(ren)?',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'Any serious/special medical condition about the student which we have to be aware of?',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'If surnames of enrolled students are different, please confirm if they are all siblings; if NOT, then please specify their relationship to each other:',
|
||||
],
|
||||
[
|
||||
'type' => 'essay',
|
||||
'text' => 'Any other questions / concerns that the parents may have?',
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('question_pools') || ! $this->db->tableExists('assessment_questions')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pool = $this->db->table('question_pools')
|
||||
->select('id')
|
||||
->where('name', self::POOL_NAME)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($pool === null) {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$this->db->table('question_pools')->insert([
|
||||
'name' => self::POOL_NAME,
|
||||
'subject_tag' => 'New Student Intake',
|
||||
'created_by' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$poolId = (int) $this->db->insertID();
|
||||
} else {
|
||||
$poolId = (int) $pool['id'];
|
||||
}
|
||||
|
||||
if ($poolId <= 0) return;
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$maxOrder = $this->db->table('assessment_questions')->selectMax('order_index')->where('pool_id', $poolId)->get()->getRowArray();
|
||||
$nextOrder = ((int) ($maxOrder['order_index'] ?? 0)) + 1;
|
||||
foreach (self::QUESTIONS as $question) {
|
||||
$exists = $this->db->table('assessment_questions')
|
||||
->where('pool_id', $poolId)
|
||||
->where('text', $question['text'])
|
||||
->countAllResults() > 0;
|
||||
if ($exists) continue;
|
||||
|
||||
$this->db->table('assessment_questions')->insert([
|
||||
'pool_id' => $poolId,
|
||||
'type' => $question['type'],
|
||||
'text' => $question['text'],
|
||||
'options' => null,
|
||||
'correct_answer' => null,
|
||||
'points' => 0,
|
||||
'order_index' => $nextOrder++,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! $this->db->tableExists('question_pools') || ! $this->db->tableExists('assessment_questions')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pool = $this->db->table('question_pools')->select('id')->where('name', self::POOL_NAME)->get()->getRowArray();
|
||||
if ($pool === null) return;
|
||||
|
||||
$poolId = (int) $pool['id'];
|
||||
foreach (array_column(self::QUESTIONS, 'text') as $text) {
|
||||
$question = $this->db->table('assessment_questions')->select('id')->where(['pool_id' => $poolId, 'text' => $text])->get()->getRowArray();
|
||||
if ($question === null) continue;
|
||||
if ($this->db->tableExists('assessment_form_questions')
|
||||
&& $this->db->table('assessment_form_questions')->where('question_id', (int) $question['id'])->countAllResults() > 0) {
|
||||
continue;
|
||||
}
|
||||
$this->db->table('assessment_questions')->where('id', (int) $question['id'])->delete();
|
||||
}
|
||||
|
||||
$hasQuestions = $this->db->table('assessment_questions')->where('pool_id', $poolId)->countAllResults() > 0;
|
||||
$hasForms = $this->db->tableExists('assessment_forms')
|
||||
&& $this->db->table('assessment_forms')->where('pool_id', $poolId)->countAllResults() > 0;
|
||||
if (! $hasQuestions && ! $hasForms) {
|
||||
$this->db->table('question_pools')->where('id', $poolId)->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddEducationCommitteeNoteToAssessmentForms extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('assessment_forms')
|
||||
|| $this->db->fieldExists('education_committee_note', 'assessment_forms')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addColumn('assessment_forms', [
|
||||
'education_committee_note' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
'after' => 'status',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if ($this->db->tableExists('assessment_forms')
|
||||
&& $this->db->fieldExists('education_committee_note', 'assessment_forms')) {
|
||||
$this->forge->dropColumn('assessment_forms', 'education_committee_note');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddSchoolYearToAssessmentForms extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('assessment_forms')) return;
|
||||
|
||||
if (! $this->db->fieldExists('school_year', 'assessment_forms')) {
|
||||
$this->forge->addColumn('assessment_forms', [
|
||||
'school_year' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 9,
|
||||
'null' => true,
|
||||
'after' => 'pool_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$schoolYear = $this->activeSchoolYear();
|
||||
if ($schoolYear !== null) {
|
||||
$this->db->table('assessment_forms')
|
||||
->groupStart()->where('school_year', null)->orWhere('school_year', '')->groupEnd()
|
||||
->update(['school_year' => $schoolYear]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if ($this->db->tableExists('assessment_forms') && $this->db->fieldExists('school_year', 'assessment_forms')) {
|
||||
$this->forge->dropColumn('assessment_forms', 'school_year');
|
||||
}
|
||||
}
|
||||
|
||||
private function activeSchoolYear(): ?string
|
||||
{
|
||||
if ($this->db->tableExists('school_years')) {
|
||||
$row = $this->db->table('school_years')->select('name')->where('status', 'active')->orderBy('id', 'DESC')->get()->getRowArray();
|
||||
$name = trim((string) ($row['name'] ?? ''));
|
||||
if (preg_match('/^\d{4}-\d{4}$/', $name)) return $name;
|
||||
}
|
||||
if ($this->db->tableExists('configuration')) {
|
||||
$row = $this->db->table('configuration')->select('config_value')->where('config_key', 'school_year')->orderBy('id', 'DESC')->get()->getRowArray();
|
||||
$name = trim((string) ($row['config_value'] ?? ''));
|
||||
if (preg_match('/^\d{4}-\d{4}$/', $name)) return $name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddEducationCommitteeNoteToStudentAssessments extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('student_assessments')
|
||||
|| $this->db->fieldExists('education_committee_note', 'student_assessments')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addColumn('student_assessments', [
|
||||
'education_committee_note' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
'after' => 'score',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if ($this->db->tableExists('student_assessments')
|
||||
&& $this->db->fieldExists('education_committee_note', 'student_assessments')) {
|
||||
$this->forge->dropColumn('student_assessments', 'education_committee_note');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user