fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s

This commit is contained in:
root
2026-08-15 15:07:16 -04:00
parent c12bb59372
commit 4603d9ced2
95 changed files with 6892 additions and 1295 deletions
@@ -73,7 +73,7 @@ class CreateWhatsappGroupMemberships extends Migration
$this->forge->addKey(['class_section_id', 'school_year', 'semester']);
$this->forge->addUniqueKey(['class_section_id', 'school_year', 'semester', 'subject_type', 'subject_id'], 'uniq_whatsapp_membership');
$this->forge->createTable('whatsapp_group_memberships');
$this->forge->createTable('whatsapp_group_memberships', true);
}
public function down()
@@ -55,7 +55,7 @@ class CreateReimbursementBatchAdminFiles extends Migration
$this->forge->addKey('batch_id');
$this->forge->addKey('admin_id');
$this->forge->addUniqueKey(['batch_id', 'admin_id']);
$this->forge->createTable('reimbursement_batch_admin_files');
$this->forge->createTable('reimbursement_batch_admin_files', true);
}
public function down()
@@ -68,7 +68,7 @@ class CreatePrintRequests extends Migration
if ($this->db->tableExists('classes')) {
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
}
$this->forge->createTable('print_requests');
$this->forge->createTable('print_requests', true);
}
public function down()
@@ -30,6 +30,16 @@ class RevertPrintRequestsForeignKey extends Migration
return;
}
$orphanCount = (int) $db->table('print_requests pr')
->join('classes c', 'c.id = pr.class_id', 'left')
->where('pr.class_id IS NOT NULL', null, false)
->where('c.id IS NULL', null, false)
->countAllResults();
if ($orphanCount > 0) {
return;
}
// Add the new foreign key
$this->forge->addForeignKey('class_id', 'classes', 'id', 'CASCADE', 'CASCADE');
@@ -29,6 +29,16 @@ class FixPrintRequestsForeignKeyOnceAndForAll extends Migration
return;
}
$orphanCount = (int) $db->table('print_requests pr')
->join('classSection cs', 'cs.id = pr.class_id', 'left')
->where('pr.class_id IS NOT NULL', null, false)
->where('cs.id IS NULL', null, false)
->countAllResults();
if ($orphanCount > 0) {
return;
}
$this->forge->addForeignKey('class_id', 'classSection', 'id', 'CASCADE', 'CASCADE');
$this->forge->processIndexes('print_requests');
}
@@ -20,6 +20,16 @@ class FixPrintRequestsForeignKeyAgain extends Migration
}
}
$orphanCount = (int) $db->table('print_requests pr')
->join('classSection cs', 'cs.class_id = pr.class_id', 'left')
->where('pr.class_id IS NOT NULL', null, false)
->where('cs.class_id IS NULL', null, false)
->countAllResults();
if ($orphanCount > 0) {
return;
}
$this->forge->addForeignKey('class_id', 'classSection', 'class_id', 'CASCADE', 'CASCADE');
$this->forge->processIndexes('print_requests');
}
@@ -36,7 +36,7 @@ class CreateClassProgressReports extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['class_section_id', 'week_start', 'week_end']);
$this->forge->createTable('class_progress_reports');
$this->forge->createTable('class_progress_reports', true);
}
public function down()
@@ -54,7 +54,7 @@ class CreateSubjectCurriculumItems extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['class_id', 'subject']);
$this->forge->createTable('subject_curriculum_items');
$this->forge->createTable('subject_curriculum_items', true);
}
public function down()
@@ -63,7 +63,7 @@ class CreateTeacherSubmissionNotificationHistory extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['teacher_id', 'class_section_id']);
$this->forge->createTable('teacher_submission_notification_history');
$this->forge->createTable('teacher_submission_notification_history', true);
}
public function down()
@@ -100,7 +100,7 @@ class CreateExamDraftSubmissions extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['teacher_id', 'class_section_id']);
$this->forge->createTable('exam_drafts');
$this->forge->createTable('exam_drafts', true);
}
public function down()
@@ -8,19 +8,27 @@ class AddVersionToExamDrafts extends Migration
{
public function up()
{
$this->forge->addColumn('exam_drafts', [
'version' => [
$fields = [];
if (! $this->db->fieldExists('version', 'exam_drafts')) {
$fields['version'] = [
'type' => 'INT',
'unsigned' => true,
'default' => 1,
],
'previous_draft_id' => [
];
}
if (! $this->db->fieldExists('previous_draft_id', 'exam_drafts')) {
$fields['previous_draft_id'] = [
'type' => 'INT',
'unsigned' => true,
'null' => true,
],
]);
$this->forge->addKey('version');
];
}
if ($fields !== []) {
$this->forge->addColumn('exam_drafts', $fields);
}
}
public function down()
@@ -8,6 +8,10 @@ class AddFinalPdfToExamDrafts extends Migration
{
public function up()
{
if ($this->db->fieldExists('final_pdf_file', 'exam_drafts')) {
return;
}
$this->forge->addColumn('exam_drafts', [
'final_pdf_file' => [
'type' => 'VARCHAR',
@@ -20,7 +24,8 @@ class AddFinalPdfToExamDrafts extends Migration
public function down()
{
$this->forge->dropColumn('exam_drafts', 'final_pdf_file');
if ($this->db->fieldExists('final_pdf_file', 'exam_drafts')) {
$this->forge->dropColumn('exam_drafts', 'final_pdf_file');
}
}
}
@@ -8,6 +8,10 @@ class AddIsLegacyToExamDrafts extends Migration
{
public function up()
{
if ($this->db->fieldExists('is_legacy', 'exam_drafts')) {
return;
}
$this->forge->addColumn('exam_drafts', [
'is_legacy' => [
'type' => 'TINYINT',
@@ -20,7 +24,8 @@ class AddIsLegacyToExamDrafts extends Migration
public function down()
{
$this->forge->dropColumn('exam_drafts', 'is_legacy');
if ($this->db->fieldExists('is_legacy', 'exam_drafts')) {
$this->forge->dropColumn('exam_drafts', 'is_legacy');
}
}
}
@@ -24,7 +24,7 @@ class CreateClassProgressAttachments extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey('report_id');
$this->forge->createTable('class_progress_attachments');
$this->forge->createTable('class_progress_attachments', true);
}
public function down()
@@ -26,7 +26,7 @@ class CreatePlacementLevels extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['student_id', 'school_year'], 'unique_student_school_year');
$this->forge->addKey('school_year');
$this->forge->createTable('placement_levels');
$this->forge->createTable('placement_levels', true);
}
public function down()
@@ -24,7 +24,7 @@ class CreatePlacementBatches extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['placement_test', 'school_year']);
$this->forge->createTable('placement_batches');
$this->forge->createTable('placement_batches', true);
}
public function down()
@@ -26,7 +26,7 @@ class CreatePlacementScores extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['batch_id', 'student_id'], 'unique_batch_student');
$this->forge->addKey('batch_id');
$this->forge->createTable('placement_scores');
$this->forge->createTable('placement_scores', true);
}
public function down()
@@ -67,7 +67,7 @@ class CreateReportCardAcknowledgements extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey(['parent_id', 'student_id', 'school_year', 'semester'], false, true);
$this->forge->createTable('report_card_acknowledgements');
$this->forge->createTable('report_card_acknowledgements', true);
}
public function down()
@@ -75,7 +75,7 @@ class CreateCertificateRecords extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey('certificate_number');
$this->forge->addKey(['school_year', 'student_id']);
$this->forge->createTable('certificate_records');
$this->forge->createTable('certificate_records', true);
}
public function down()
@@ -60,7 +60,7 @@ class CreateBelowSixtyDecisions extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['student_id', 'semester', 'school_year']);
$this->forge->addKey(['school_year', 'semester']);
$this->forge->createTable('below_sixty_decisions');
$this->forge->createTable('below_sixty_decisions', true);
}
public function down()
@@ -75,7 +75,7 @@ class CreateStudentDecisions extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['student_id', 'semester', 'school_year']);
$this->forge->addKey(['school_year', 'semester']);
$this->forge->createTable('student_decisions');
$this->forge->createTable('student_decisions', true);
}
public function down()
@@ -40,8 +40,10 @@ class AddVerificationTokenToCertificateRecords extends Migration
->update(['verification_token' => $this->generateToken()]);
}
$this->forge->addUniqueKey('verification_token', self::INDEX_NAME);
$this->forge->processIndexes('certificate_records');
if (! $this->indexExists('certificate_records', self::INDEX_NAME)) {
$this->forge->addUniqueKey('verification_token', self::INDEX_NAME);
$this->forge->processIndexes('certificate_records');
}
}
public function down()
@@ -70,4 +72,15 @@ class AddVerificationTokenToCertificateRecords extends Migration
return $token;
}
private function indexExists(string $table, string $indexName): bool
{
foreach ($this->db->getIndexData($table) as $index) {
if (($index->name ?? '') === $indexName) {
return true;
}
}
return false;
}
}
@@ -149,11 +149,11 @@ class FinancialSystemLedgerCleanup extends Migration
$defaults = [
'tuition_calculator_version' => 'old',
'youth_fee' => '200.00',
'new_tuition_full_amount' => '370.00',
'new_tuition_youth_amount' => '200.00',
'new_tuition_second_student_discount' => '50.00',
'new_tuition_third_student_discount' => '50.00',
'youth_fee' => '380.00',
'new_tuition_full_amount' => '380.00',
'new_tuition_youth_amount' => '380.00',
'new_tuition_second_student_discount' => '100.00',
'new_tuition_third_student_discount' => '100.00',
'new_tuition_fourth_plus_discount' => '100.00',
];
@@ -104,7 +104,7 @@ class CreateSchoolYears extends Migration
$this->forge->addKey('id', true);
$this->forge->addKey('name', false, true);
$this->forge->addKey('status');
$this->forge->createTable('school_years');
$this->forge->createTable('school_years', true);
} else {
$this->ensureSchoolYearColumns();
}
@@ -193,7 +193,7 @@ class CreateSchoolYears extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['source_school_year_id', 'status']);
$this->forge->createTable('school_year_closing_batches');
$this->forge->createTable('school_year_closing_batches', true);
}
if (! $this->db->tableExists('school_year_closing_items')) {
@@ -214,7 +214,7 @@ class CreateSchoolYears extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['closing_batch_id', 'family_id'], false, true);
$this->forge->createTable('school_year_closing_items');
$this->forge->createTable('school_year_closing_items', true);
}
if (! $this->db->tableExists('school_year_transition_logs')) {
@@ -230,7 +230,7 @@ class CreateSchoolYears extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['school_year_id', 'created_at']);
$this->forge->createTable('school_year_transition_logs');
$this->forge->createTable('school_year_transition_logs', true);
}
}
@@ -33,7 +33,7 @@ class CreateStudentSectionDistributionDrafts extends Migration
$this->forge->addUniqueKey(['student_id', 'school_year'], 'unique_distribution_draft_student_year');
$this->forge->addKey(['class_id', 'school_year', 'status'], false, false, 'distribution_draft_class_year_status');
$this->forge->addKey(['class_section_id', 'school_year'], false, false, 'distribution_draft_section_year');
$this->forge->createTable('student_section_distribution_drafts');
$this->forge->createTable('student_section_distribution_drafts', true);
}
public function down()
@@ -63,7 +63,7 @@ class CreateParentPolicyAcceptances extends Migration
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['parent_id', 'school_year'], 'uq_parent_policy_year');
$this->forge->addKey('school_year');
$this->forge->createTable('parent_policy_acceptances');
$this->forge->createTable('parent_policy_acceptances', true);
$this->backfillExistingAcceptances();
}
@@ -39,7 +39,7 @@ class CreateEnrollmentPhaseTwoTables extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['school_year', 'grade_class_id']);
$this->forge->createTable('enrollment_age_rules');
$this->forge->createTable('enrollment_age_rules', true);
}
if (! $this->db->tableExists('enrollment_flags')) {
@@ -59,7 +59,7 @@ class CreateEnrollmentPhaseTwoTables extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['student_id', 'school_year', 'flag_type']);
$this->forge->createTable('enrollment_flags');
$this->forge->createTable('enrollment_flags', true);
}
if (! $this->db->tableExists('enrollment_transition_audits')) {
@@ -77,7 +77,7 @@ class CreateEnrollmentPhaseTwoTables extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['student_id', 'school_year', 'created_at']);
$this->forge->createTable('enrollment_transition_audits');
$this->forge->createTable('enrollment_transition_audits', true);
}
}
@@ -43,7 +43,7 @@ class CreateEnrollmentEmailRecords extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['school_year', 'parent_user_id']);
$this->forge->createTable('enrollment_email_records');
$this->forge->createTable('enrollment_email_records', true);
}
public function down()
@@ -0,0 +1,48 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateEnrollmentExceptions extends Migration
{
public function up()
{
if ($this->db->tableExists('enrollment_exceptions')) {
return;
}
$this->forge->addField([
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
'parent_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
'student_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
'school_year' => ['type' => 'VARCHAR', 'constraint' => 20],
'source_school_year' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => true],
'status' => ['type' => 'VARCHAR', 'constraint' => 20, 'default' => 'active'],
'reason_code' => ['type' => 'VARCHAR', 'constraint' => 80],
'reason_note' => ['type' => 'TEXT'],
'bypassed_rule_codes_json' => ['type' => 'TEXT', 'null' => true],
'created_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'approved_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'starts_at' => ['type' => 'DATETIME', 'null' => true],
'expires_at' => ['type' => 'DATETIME', 'null' => true],
'used_at' => ['type' => 'DATETIME', 'null' => true],
'enrollment_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'revoked_at' => ['type' => 'DATETIME', 'null' => true],
'revoked_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'revocation_reason' => ['type' => 'TEXT', 'null' => true],
'created_at' => ['type' => 'DATETIME', 'null' => true],
'updated_at' => ['type' => 'DATETIME', 'null' => true],
]);
$this->forge->addKey('id', true);
$this->forge->addKey(['parent_id', 'student_id', 'school_year', 'status'], false, false, 'idx_enrollment_exceptions_scope_status');
$this->forge->addKey(['student_id', 'school_year'], false, false, 'idx_enrollment_exceptions_student_year');
$this->forge->addKey(['status', 'expires_at'], false, false, 'idx_enrollment_exceptions_status_expiry');
$this->forge->createTable('enrollment_exceptions', true);
}
public function down()
{
$this->forge->dropTable('enrollment_exceptions', true);
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class GrantEnrollmentExceptionPermission extends Migration
{
private string $permissionName = 'enrollment.exception.manage';
public function up()
{
if (! $this->db->tableExists('permissions')) {
return;
}
$permission = $this->db->table('permissions')
->where('name', $this->permissionName)
->limit(1)
->get()
->getRowArray();
if ($permission === null) {
$insert = [
'name' => $this->permissionName,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
if ($this->db->fieldExists('description', 'permissions')) {
$insert['description'] = 'Manage scoped enrollment eligibility exceptions.';
}
$this->db->table('permissions')->insert($insert);
$permissionId = (int) $this->db->insertID();
} else {
$permissionId = (int) $permission['id'];
}
// The plan requires a narrow permission but not an automatic broad role grant.
// Assign enrollment.exception.manage through the existing role-permission UI.
}
public function down()
{
if (! $this->db->tableExists('permissions') || ! $this->db->tableExists('role_permissions')) {
return;
}
$permission = $this->db->table('permissions')
->where('name', $this->permissionName)
->limit(1)
->get()
->getRowArray();
if ($permission === null) {
return;
}
$this->db->table('role_permissions')
->where('permission_id', (int) $permission['id'])
->delete();
$this->db->table('permissions')
->where('id', (int) $permission['id'])
->delete();
}
}
@@ -0,0 +1,107 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class UpdateTuitionFeeDefaults extends Migration
{
public function up()
{
if (!$this->db->tableExists('configuration')) {
return;
}
$this->forceConfig('first_student_fee', '380.00');
$this->forceConfig('second_student_fee', '280.00');
$this->forceConfig('new_tuition_full_amount', '380.00');
$this->forceConfig('new_tuition_second_student_discount', '100.00');
$this->aliasAndForceConfig('Youth_fee', 'youth_fee', '380.00');
}
public function down()
{
if (!$this->db->tableExists('configuration')) {
return;
}
$this->rollbackConfig('first_student_fee', '370.00');
$this->rollbackConfig('second_student_fee', '200.00');
$this->rollbackConfig('youth_fee', '200.00');
$this->rollbackConfig('new_tuition_full_amount', '370.00');
$this->rollbackConfig('new_tuition_second_student_discount', '50.00');
}
protected function aliasAndForceConfig(string $legacyKey, string $canonicalKey, string $value): void
{
$legacy = $this->db->table('configuration')
->select('id, config_value')
->where('config_key', $legacyKey)
->orderBy('id', 'ASC')
->get()
->getRowArray();
if ($legacy && strcasecmp($legacyKey, $canonicalKey) !== 0) {
$this->db->table('configuration')
->where('id', (int) $legacy['id'])
->update(['config_key' => $canonicalKey, 'config_value' => $value]);
}
$this->forceConfig($canonicalKey, $value);
}
protected function forceConfig(string $key, string $value): void
{
$row = $this->db->table('configuration')
->select('id')
->where('config_key', $key)
->orderBy('id', 'ASC')
->get()
->getRowArray();
if (!$row) {
$this->db->table('configuration')->insert([
'config_key' => $key,
'config_value' => $value,
]);
return;
}
$this->db->table('configuration')
->where('config_key', $key)
->update(['config_value' => $value]);
}
protected function upsertConfig(string $key, string $value, array $legacyValues): void
{
$row = $this->db->table('configuration')
->select('id, config_value')
->where('config_key', $key)
->orderBy('id', 'ASC')
->get()
->getRowArray();
if (!$row) {
$this->db->table('configuration')->insert([
'config_key' => $key,
'config_value' => $value,
]);
return;
}
$current = trim((string) ($row['config_value'] ?? ''));
if (in_array($current, $legacyValues, true)) {
$this->db->table('configuration')
->where('config_key', $key)
->update(['config_value' => $value]);
}
}
protected function rollbackConfig(string $key, string $value): void
{
$this->db->table('configuration')
->where('config_key', $key)
->where('config_value', '380.00')
->update(['config_value' => $value]);
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class EnrollmentCarryOverAndExceptionUniqueness extends Migration
{
public function up()
{
if ($this->db->tableExists('school_years') && $this->db->fieldExists('carry_over_balance_behavior', 'school_years')) {
$this->db->table('school_years')
->groupStart()
->where('carry_over_balance_behavior', 'information_only')
->orWhere('carry_over_balance_behavior', null)
->orWhere('carry_over_balance_behavior', '')
->groupEnd()
->update(['carry_over_balance_behavior' => 'submission_blocked_until_payment']);
}
if (! $this->db->tableExists('enrollment_exceptions')) {
return;
}
$indexes = $this->db->query('SHOW INDEX FROM enrollment_exceptions')->getResultArray();
$names = array_map(static fn (array $row): string => (string) ($row['Key_name'] ?? ''), $indexes);
if (in_array('uniq_enrollment_exceptions_scope_status', $names, true)) {
return;
}
try {
$this->db->query(
'ALTER TABLE enrollment_exceptions ADD UNIQUE INDEX uniq_enrollment_exceptions_scope_status (parent_id, student_id, school_year, status)'
);
} catch (\Throwable $e) {
log_message('error', 'Unable to add unique enrollment exception index: ' . $e->getMessage());
}
}
public function down()
{
if ($this->db->tableExists('enrollment_exceptions')) {
try {
$this->db->query('ALTER TABLE enrollment_exceptions DROP INDEX uniq_enrollment_exceptions_scope_status');
} catch (\Throwable $e) {
// Index may not exist.
}
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateFinancialAidRequests extends Migration
{
public function up()
{
if ($this->db->tableExists('financial_aid_requests')) {
return;
}
$this->forge->addField([
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
'parent_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true],
'school_year' => ['type' => 'VARCHAR', 'constraint' => 20],
'student_ids_json' => ['type' => 'TEXT', 'null' => true],
'household_size' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'need_statement' => ['type' => 'TEXT'],
'requested_amount' => ['type' => 'DECIMAL', 'constraint' => '10,2', 'null' => true],
'status' => ['type' => 'VARCHAR', 'constraint' => 20, 'default' => 'submitted'],
'admin_amount' => ['type' => 'DECIMAL', 'constraint' => '10,2', 'null' => true],
'admin_note' => ['type' => 'TEXT', 'null' => true],
'reviewed_by' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'reviewed_at' => ['type' => 'DATETIME', 'null' => true],
'invoice_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'discount_usage_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true],
'voucher_id' => ['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(['parent_id', 'school_year', 'status'], false, false, 'idx_financial_aid_parent_year_status');
$this->forge->addKey(['school_year', 'status'], false, false, 'idx_financial_aid_year_status');
$this->forge->createTable('financial_aid_requests', true);
}
public function down()
{
$this->forge->dropTable('financial_aid_requests', true);
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddFinancialAidNavItem extends Migration
{
private string $url = 'administrator/financial-aid';
public function up(): void
{
if (! $this->db->tableExists('nav_items')) {
return;
}
$parentColumn = $this->parentColumn();
$existingQuery = $this->db->table('nav_items')
->where('url', $this->url)
->get();
$existing = $existingQuery !== false ? $existingQuery->getRowArray() : null;
if ($existing !== null) {
return;
}
$parentBuilder = $this->db->table('nav_items')
->where('label', 'Financial');
if ($parentColumn !== null) {
$parentBuilder->where($parentColumn, null);
}
$parentQuery = $parentBuilder->get();
$parent = $parentQuery !== false ? $parentQuery->getRowArray() : null;
$insert = [
'label' => 'Financial Aid',
'url' => $this->url,
'sort_order' => 9,
'is_enabled' => 1,
'created_at' => date('Y-m-d H:i:s'),
];
if ($parentColumn !== null) {
$insert[$parentColumn] = $parent['id'] ?? null;
}
$this->db->table('nav_items')->insert($insert);
$navItemId = (int) $this->db->insertID();
if (
$navItemId <= 0
|| ! $this->db->tableExists('role_nav_items')
|| ! $this->db->fieldExists('role', 'role_nav_items')
|| ! $this->db->fieldExists('nav_item_id', 'role_nav_items')
) {
return;
}
foreach (['administrator', 'principal', 'vice_principal'] as $role) {
$this->db->table('role_nav_items')->insert([
'role' => $role,
'nav_item_id' => $navItemId,
'created_at' => date('Y-m-d H:i:s'),
]);
}
}
public function down(): void
{
if (! $this->db->tableExists('nav_items')) {
return;
}
$query = $this->db->table('nav_items')
->where('url', $this->url)
->get();
$row = $query !== false ? $query->getRowArray() : null;
if ($row === null) {
return;
}
if ($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('id', (int) $row['id'])
->delete();
}
private function parentColumn(): ?string
{
if ($this->db->fieldExists('parent_id', 'nav_items')) {
return 'parent_id';
}
if ($this->db->fieldExists('menu_parent_id', 'nav_items')) {
return 'menu_parent_id';
}
return null;
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddEnrollmentExceptionFamilyStudentIds extends Migration
{
public function up(): void
{
if (! $this->db->tableExists('enrollment_exceptions')) {
return;
}
if ($this->db->fieldExists('family_student_ids_json', 'enrollment_exceptions')) {
return;
}
$this->forge->addColumn('enrollment_exceptions', [
'family_student_ids_json' => [
'type' => 'TEXT',
'null' => true,
'after' => 'bypassed_rule_codes_json',
],
]);
}
public function down(): void
{
if (! $this->db->tableExists('enrollment_exceptions')) {
return;
}
if ($this->db->fieldExists('family_student_ids_json', 'enrollment_exceptions')) {
$this->forge->dropColumn('enrollment_exceptions', 'family_student_ids_json');
}
}
}
@@ -0,0 +1,140 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class GrantEnrollmentExceptionPermissionToAdminRoles extends Migration
{
private string $permissionName = 'enrollment.exception.manage';
/**
* @var list<string>
*/
private array $roleNames = [
'administrator',
'admin',
'principal',
'vice principal',
'vice_principal',
];
public function up(): void
{
if (
! $this->db->tableExists('roles')
|| ! $this->db->tableExists('permissions')
|| ! $this->db->tableExists('role_permissions')
) {
return;
}
$now = date('Y-m-d H:i:s');
$permission = $this->db->table('permissions')
->select('id')
->where('name', $this->permissionName)
->get()
->getRowArray();
if ($permission === null) {
$insert = [
'name' => $this->permissionName,
'created_at' => $now,
'updated_at' => $now,
];
if ($this->db->fieldExists('description', 'permissions')) {
$insert['description'] = 'Manage scoped enrollment eligibility exceptions.';
}
$this->db->table('permissions')->insert($insert);
$permissionId = (int) $this->db->insertID();
} else {
$permissionId = (int) $permission['id'];
}
if ($permissionId <= 0) {
return;
}
$roles = $this->db->table('roles')
->select('id')
->whereIn('name', $this->roleNames)
->get()
->getResultArray();
foreach ($roles as $role) {
$roleId = (int) ($role['id'] ?? 0);
if ($roleId <= 0) {
continue;
}
$existing = $this->db->table('role_permissions')
->where('role_id', $roleId)
->where('permission_id', $permissionId)
->get()
->getRowArray();
$grant = [
'can_create' => 1,
'can_read' => 1,
'can_update' => 1,
'can_delete' => 1,
'updated_at' => $now,
];
if ($this->db->fieldExists('can_manage', 'role_permissions')) {
$grant['can_manage'] = 1;
}
if ($existing === null) {
$grant['role_id'] = $roleId;
$grant['permission_id'] = $permissionId;
$grant['created_at'] = $now;
$this->db->table('role_permissions')->insert($grant);
continue;
}
$this->db->table('role_permissions')
->where('id', (int) $existing['id'])
->update($grant);
}
}
public function down(): void
{
if (
! $this->db->tableExists('roles')
|| ! $this->db->tableExists('permissions')
|| ! $this->db->tableExists('role_permissions')
) {
return;
}
$permission = $this->db->table('permissions')
->select('id')
->where('name', $this->permissionName)
->get()
->getRowArray();
if ($permission === null) {
return;
}
$roles = $this->db->table('roles')
->select('id')
->whereIn('name', $this->roleNames)
->get()
->getResultArray();
$roleIds = array_values(array_filter(array_map(static fn (array $role): int => (int) ($role['id'] ?? 0), $roles)));
if ($roleIds === []) {
return;
}
$this->db->table('role_permissions')
->where('permission_id', (int) $permission['id'])
->whereIn('role_id', $roleIds)
->delete();
}
}