recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
View File
@@ -0,0 +1,251 @@
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
use CodeIgniter\Database\BaseConnection;
class FamiliesBootstrapSeeder extends Seeder
{
/** @var BaseConnection */
protected $db;
// Map your legacy `parents` table/columns
protected array $legacy = [
'table' => 'parents', // legacy table
'firstparent_id' => 'firstparent_id', // users.id (primary parent)
'second_user_id' => 'secondparent_id', // users.id of 2nd parent if present (may be 0)
'email' => 'secondparent_email',
'firstname' => 'secondparent_firstname',
'lastname' => 'secondparent_lastname',
];
public function run()
{
$this->db = \Config\Database::connect();
// While tuning, you can comment these two lines to avoid full rollback
$this->db->transStart();
[$familiesCreated, $studentsLinked, $primGuardiansLinked] = $this->bootstrapByPrimaryParents();
[$createdUsers, $secGuardiansLinked, $skippedLegacy, $legacyTotal] = $this->importLegacySecondParents();
$this->db->transComplete();
echo PHP_EOL;
echo "Families created: {$familiesCreated}\n";
echo "Students linked: {$studentsLinked}\n";
echo "Primary guardians: {$primGuardiansLinked}\n";
echo "Legacy rows scanned: {$legacyTotal}\n";
echo "Stub users created: {$createdUsers}\n";
echo "Secondary guardians: {$secGuardiansLinked}\n";
echo "Legacy rows skipped: {$skippedLegacy}\n";
echo PHP_EOL;
}
/** Create/ensure one family per primary parent (students.parent_id), link students, add primary guardian. */
protected function bootstrapByPrimaryParents(): array
{
$familiesCreated = 0; $studentsLinked = 0; $primGuardiansLinked = 0;
$primaryParents = $this->db->query(
"SELECT DISTINCT parent_id FROM students WHERE parent_id IS NOT NULL"
)->getResultArray();
foreach ($primaryParents as $row) {
$primaryUserId = (int) $row['parent_id'];
if ($primaryUserId <= 0) { continue; }
$familyId = $this->ensureFamilyForPrimaryParent($primaryUserId, $familiesCreated);
// link all kids of this primary parent
$kids = $this->db->query("SELECT id FROM students WHERE parent_id = ?", [$primaryUserId])->getResultArray();
foreach ($kids as $s) {
$studentsLinked += $this->attachStudentToFamily((int)$s['id'], $familyId);
}
// ensure primary guardian link
$primGuardiansLinked += $this->attachGuardianUser($familyId, $primaryUserId, 'primary', 1, 1, 0);
}
return [$familiesCreated, $studentsLinked, $primGuardiansLinked];
}
/** Import second parents from legacy `parents` table (by email or secondparent_id). */
protected function importLegacySecondParents(): array
{
$L = $this->legacy;
$createdUsers = 0; $linked = 0; $skipped = 0; $total = 0;
if (! $this->db->tableExists($L['table'])) {
echo "[LEGACY] Table '{$L['table']}' not found; skipping.\n";
return [0,0,0,0];
}
$fields = array_map('strtolower', array_column($this->db->getFieldData($L['table']), 'name'));
foreach (['firstparent_id','email','firstname','lastname','second_user_id'] as $k) {
if (! array_key_exists($k, $L)) continue;
$col = strtolower($L[$k]);
if ($col && ! in_array($col, $fields, true)) {
echo "[LEGACY] Missing column {$L[$k]} in {$L['table']}; skipping import.\n";
return [0,0,0,0];
}
}
$qb = $this->db->table($L['table']);
$qb->select("{$L['firstparent_id']} AS firstparent_id", false)
->select("{$L['second_user_id']} AS secondparent_id", false)
->select("{$L['email']} AS email", false)
->select("{$L['firstname']} AS firstname", false)
->select("{$L['lastname']} AS lastname", false)
->where("{$L['email']} IS NOT NULL", null, false)
->where("{$L['email']} !=", '');
$q = $qb->get();
if ($q === false) {
$err = $this->db->error();
echo "[LEGACY] Query failed: {$err['code']} {$err['message']}\n";
return [0,0,0,0];
}
$rows = $q->getResultArray();
$total = count($rows);
foreach ($rows as $r) {
$primaryUserId = (int)($r['firstparent_id'] ?? 0);
$email = trim((string)($r['email'] ?? ''));
$first = trim((string)($r['firstname'] ?? ''));
$last = trim((string)($r['lastname'] ?? ''));
$secondUserId = (int)($r['secondparent_id'] ?? 0);
if ($primaryUserId <= 0 || $email === '') { $skipped++; continue; }
// students for this primary parent
$students = $this->db->query("SELECT id FROM students WHERE parent_id = ?", [$primaryUserId])->getResultArray();
if (!$students) { $skipped++; continue; }
// resolve/create the second parent user
$userRow = null;
if ($secondUserId > 0) {
$userRow = $this->db->table('users')->select('id, firstname, lastname')->where('id', $secondUserId)->get()->getRowArray();
}
if (!$userRow) {
$userRow = $this->db->table('users')->select('id, firstname, lastname')->where('email', $email)->get()->getRowArray();
}
if (!$userRow) {
// STUB USER with safe defaults for your NOT NULL columns
$this->db->table('users')->insert([
'firstname' => $first ?: '(Parent)',
'lastname' => $last ?: '(Unspecified)',
'email' => $email,
'cellphone' => '',
'address_street' => '',
'apt' => null,
'city' => '',
'state' => '',
'zip' => '',
'accept_school_policy' => 0,
'is_verified' => 0,
'status' => 'Active',
'is_suspended' => 0,
'failed_attempts' => 0,
'password' => password_hash(bin2hex(random_bytes(8)), PASSWORD_BCRYPT),
'created_at' => utc_now(),
'updated_at' => utc_now(),
'token' => null,
'account_id' => null,
'user_type' => 'primary',
'semester' => '',
'school_year' => null,
'rfid_tag' => null,
'last_failed_at' => null,
]);
if ($this->db->affectedRows() === 0) {
$err = $this->db->error();
echo "[ERROR] users insert failed for {$email}: {$err['code']} {$err['message']}\n";
$skipped++; continue;
}
$secondUserId = (int)$this->db->insertID();
$createdUsers++;
$userRow = ['id' => $secondUserId, 'firstname' => $first, 'lastname' => $last];
} else {
$secondUserId = (int)$userRow['id'];
if (empty($userRow['firstname']) || empty($userRow['lastname'])) {
$this->db->table('users')->where('id', $secondUserId)->update([
'firstname' => $userRow['firstname'] ?: ($first ?: '(Parent)'),
'lastname' => $userRow['lastname'] ?: ($last ?: '(Unspecified)'),
]);
}
}
// link second parent to each students family
foreach ($students as $s) {
$studentId = (int)$s['id'];
$familyId = $this->familyIdFromStudentPrimary($studentId);
if (!$familyId) {
$familyId = $this->ensureFamilyForPrimaryParent($primaryUserId, $dummy = 0);
$this->attachStudentToFamily($studentId, $familyId);
}
$linked += $this->attachGuardianUser($familyId, $secondUserId, 'secondary', 0, 1, 0);
}
}
return [$createdUsers, $linked, $skipped, $total];
}
// ---------- Helpers ----------
/** Ensure there is exactly one family per primary parent user. */
protected function ensureFamilyForPrimaryParent(int $primaryUserId, int &$createdCounter): int
{
$code = 'FAM-' . $primaryUserId;
$row = $this->db->query("SELECT id FROM families WHERE family_code = ?", [$code])->getRowArray();
if ($row) return (int)$row['id'];
$this->db->table('families')->insert([
'family_code' => $code,
'household_name' => 'Family of User ' . $primaryUserId,
'is_active' => 1,
]);
$createdCounter++;
return (int)$this->db->insertID();
}
/** Idempotent link student → family. */
protected function attachStudentToFamily(int $studentId, int $familyId): int
{
$this->db->query(
"INSERT IGNORE INTO family_students (family_id, student_id, is_primary_home) VALUES (?, ?, 1)",
[$familyId, $studentId]
);
return $this->db->affectedRows();
}
/** Idempotent link guardian user → family. */
protected function attachGuardianUser(int $familyId, int $userId, string $relation, int $isPrimary, int $receiveEmails, int $receiveSms): int
{
$this->db->query(
"INSERT IGNORE INTO family_guardians (family_id, user_id, relation, is_primary, receive_emails, receive_sms) VALUES (?, ?, ?, ?, ?, ?)",
[$familyId, $userId, $relation, $isPrimary, $receiveEmails, $receiveSms]
);
return $this->db->affectedRows();
}
/** Canonical family for a student based on parent_id → FAM-{parent_id}, fallback to any linked family. */
protected function familyIdFromStudentPrimary(int $studentId): ?int
{
$row = $this->db->query("SELECT parent_id FROM students WHERE id = ?", [$studentId])->getRowArray();
if (!$row || empty($row['parent_id'])) return null;
$code = 'FAM-' . (int)$row['parent_id'];
$fam = $this->db->query("SELECT id FROM families WHERE family_code = ?", [$code])->getRowArray();
if ($fam) return (int)$fam['id'];
$fam2 = $this->db->query(
"SELECT family_id FROM family_students WHERE student_id = ? ORDER BY is_primary_home DESC LIMIT 1",
[$studentId]
)->getRowArray();
return $fam2 ? (int)$fam2['family_id'] : null;
}
}
@@ -0,0 +1,306 @@
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
use CodeIgniter\Database\BaseConnection;
class FamiliesRebuildSeeder extends Seeder
{
/** @var BaseConnection */
protected $db;
/** Legacy parents table mapping (fits your schema exactly) */
protected array $legacy = [
'table' => 'parents',
'firstparent_id' => 'firstparent_id', // users.id of primary parent
'second_user_id' => 'secondparent_id', // users.id of 2nd parent (may be 0)
'second_email' => 'secondparent_email',
'second_firstname' => 'secondparent_firstname',
'second_lastname' => 'secondparent_lastname',
];
/** Defaults for prefs rows per category (your schema) */
protected array $prefsDefaults = [
// category => [via_email, via_sms, cc_all_guardians]
'attendance' => [1, 0, 1],
'grade' => [1, 0, 1],
'behavior' => [1, 0, 1],
'general' => [1, 0, 1],
];
public function run()
{
$this->db = \Config\Database::connect();
$this->db->transStrict(false); // don't auto-rollback the entire txn on one failure
$this->db->transException(false); // don't throw (so we can print errors ourselves)
// Start a transaction so the DB stays consistent; we'll print status after.
$this->db->transStart();
[$familiesCreated, $studentLinks] = $this->ensureFamiliesAndLinks();
$primaryLinked = $this->linkPrimaryGuardians();
[$stubUsers, $secondaryLinked] = $this->importSecondaryGuardiansFromParents();
$prefsUpserts = $this->seedFamilyCommPrefsPerCategory();
$this->db->transComplete();
echo PHP_EOL;
$status = $this->db->transStatus();
echo $status ? "[TXN] Transaction COMMITTED.\n" : "[TXN] Transaction FAILED.\n";
if (! $status) {
$err = $this->db->error();
echo " DB error: " . ($err['message'] ?? 'unknown') . " (code " . ($err['code'] ?? 'n/a') . ")\n";
}
echo "Families created: {$familiesCreated}\n";
echo "family_students links inserted: {$studentLinks}\n";
echo "Primary guardians linked: {$primaryLinked}\n";
echo "Stub users created (secondary): {$stubUsers}\n";
echo "Secondary guardians linked: {$secondaryLinked}\n";
echo "family_comm_prefs upserts: {$prefsUpserts}\n";
echo PHP_EOL;
}
protected function exec(string $label, string $sql): int
{
$ok = $this->db->query($sql);
if (! $ok) {
$err = $this->db->error();
echo "[ERROR] {$label}: " . ($err['message'] ?? 'unknown') . " (code " . ($err['code'] ?? 'n/a') . ")\n";
return 0;
}
$n = (int) $this->db->affectedRows();
echo "[OK] {$label}: affectedRows={$n}\n";
return $n;
}
/**
* Create families from students where parent_id maps to a real users.id,
* then link all students to their family's id.
*/
protected function ensureFamiliesAndLinks(): array
{
$familiesCreated = $this->exec(
'families insert',
"
INSERT IGNORE INTO families (family_code, household_name, is_active, created_at, updated_at)
SELECT DISTINCT
CONCAT('FAM-', s.parent_id),
CONCAT('Family of User ', s.parent_id),
1, NOW(), NOW()
FROM students s
JOIN users u ON u.id = s.parent_id
WHERE s.parent_id IS NOT NULL
"
);
$studentLinks = $this->exec(
'family_students insert',
"
INSERT IGNORE INTO family_students (family_id, student_id, is_primary_home)
SELECT f.id, s.id, 1
FROM students s
JOIN users u ON u.id = s.parent_id
JOIN families f ON f.family_code = CONCAT('FAM-', s.parent_id)
"
);
// Backfill addresses/phone/household_name from users if empty
$this->exec(
'families backfill address/phone/name',
"
UPDATE families f
JOIN users u ON u.id = CAST(SUBSTRING(f.family_code, 5) AS UNSIGNED)
SET
f.address_line1 = COALESCE(NULLIF(f.address_line1, ''), NULLIF(u.address_street, '')),
f.city = COALESCE(NULLIF(f.city, ''), NULLIF(u.city, '')),
f.state = COALESCE(NULLIF(f.state, ''), NULLIF(u.state, '')),
f.postal_code = COALESCE(NULLIF(f.postal_code, ''), NULLIF(u.zip, '')),
f.primary_phone = COALESCE(NULLIF(f.primary_phone, ''), NULLIF(u.cellphone, '')),
f.household_name= CASE
WHEN f.household_name IS NULL OR f.household_name = ''
THEN TRIM(CONCAT('Family of ', NULLIF(u.firstname, ''), ' ', NULLIF(u.lastname, '')))
ELSE f.household_name
END
"
);
return [$familiesCreated, $studentLinks];
}
/**
* Link primary guardians using student-anchored joins (no family_code parsing assumptions).
* relation uses a safe generic 'guardian' (fits varchar(32) and common enums).
*/
protected function linkPrimaryGuardians(): int
{
// Log candidate count (already printed earlier for you)
$row = $this->db->query("
SELECT COUNT(*) AS c FROM (
SELECT DISTINCT fs.family_id, u.id AS user_id
FROM family_students fs
JOIN students s ON s.id = fs.student_id
JOIN users u ON u.id = s.parent_id
WHERE s.parent_id IS NOT NULL
) X
")->getRowArray();
echo "[DEBUG] Primary guardian candidates (via students): " . (int)($row['c'] ?? 0) . "\n";
// Path A: student-anchored (ignore duplicates/violations)
$linkedA = $this->exec(
'primary guardians insert A (student-anchored, IGNORE)',
"
INSERT IGNORE INTO family_guardians
(family_id, user_id, relation, is_primary, receive_emails, receive_sms)
SELECT DISTINCT
fs.family_id,
u.id,
'guardian', 1, 1, 0
FROM family_students fs
JOIN students s ON s.id = fs.student_id
JOIN users u ON u.id = s.parent_id
WHERE s.parent_id IS NOT NULL
"
);
// Path B: via family_code (belt & suspenders)
$linkedB = $this->exec(
'primary guardians insert B (family_code, IGNORE)',
"
INSERT IGNORE INTO family_guardians
(family_id, user_id, relation, is_primary, receive_emails, receive_sms)
SELECT f.id, u.id, 'guardian', 1, 1, 0
FROM families f
JOIN users u ON u.id = CAST(SUBSTRING(f.family_code, 5) AS UNSIGNED)
"
);
return $linkedA + $linkedB;
}
/**
* Create/link secondary guardians from legacy parents:
* - Create stub users for secondparent_email not present in users.
* - Link those (or explicit secondparent_id) to all families of the first parents students.
*/
protected function importSecondaryGuardiansFromParents(): array
{
$L = $this->legacy;
if (! $this->db->tableExists($L['table'])) {
echo "[LEGACY] Table '{$L['table']}' not found; skipping secondary import.\n";
return [0, 0];
}
$fields = array_map('strtolower', array_column($this->db->getFieldData($L['table']), 'name'));
foreach (['firstparent_id','second_user_id','second_email','second_firstname','second_lastname'] as $k) {
$col = strtolower($L[$k]);
if (! in_array($col, $fields, true)) {
echo "[LEGACY] Missing column {$L[$k]} in {$L['table']}; skipping secondary import.\n";
return [0, 0];
}
}
$stubUsers = $this->exec(
'secondary guardian stubs (users)',
"
INSERT INTO users
(firstname, lastname, email, cellphone, address_street, apt, city, state, zip,
accept_school_policy, is_verified, status, is_suspended, failed_attempts,
password, created_at, updated_at, token, account_id, user_type, semester,
school_year, rfid_tag, last_failed_at)
SELECT
NULLIF(p.{$L['second_firstname']},''),
NULLIF(p.{$L['second_lastname']},''),
p.{$L['second_email']},
'', '', NULL, '', '', '',
0, 0, 'Active', 0, 0,
'$2y$10abcdefghijklmnopqrstuv',
NOW(), NOW(), NULL, NULL, 'secondary',
'', NULL, NULL, NULL
FROM {$L['table']} p
LEFT JOIN users u ON u.email = p.{$L['second_email']}
WHERE p.{$L['second_email']} IS NOT NULL
AND p.{$L['second_email']} <> ''
AND u.id IS NULL
"
);
$secondaryLinked = $this->exec(
'secondary guardians insert (IGNORE)',
"
INSERT IGNORE INTO family_guardians
(family_id, user_id, relation, is_primary, receive_emails, receive_sms)
SELECT DISTINCT
fs.family_id,
COALESCE(u2.id, u_email.id) AS user_id,
'guardian', 0, 1, 0
FROM {$L['table']} p
JOIN students s
ON s.parent_id = p.{$L['firstparent_id']}
OR s.parent_id = p.id
JOIN family_students fs ON fs.student_id = s.id
LEFT JOIN users u2 ON u2.id = NULLIF(p.{$L['second_user_id']}, 0)
LEFT JOIN users u_email ON u_email.email = p.{$L['second_email']}
WHERE p.{$L['second_email']} IS NOT NULL
AND p.{$L['second_email']} <> ''
AND COALESCE(u2.id, u_email.id) IS NOT NULL
"
);
return [$stubUsers, $secondaryLinked];
}
/**
* Seed one prefs row per (family_id, category) with your defaults.
* Counts how many rows were *actually inserted* across all categories.
*/
protected function seedFamilyCommPrefsPerCategory(): int
{
if (! $this->db->tableExists('family_comm_prefs')) {
echo "[INFO] family_comm_prefs not found; skipping prefs seed.\n";
return 0;
}
$insertedTotal = 0;
foreach ($this->prefsDefaults as $category => [$viaEmail, $viaSms, $ccAll]) {
// Count how many families are missing this category BEFORE
$row = $this->db->query("
SELECT COUNT(*) AS c
FROM families f
LEFT JOIN family_comm_prefs p
ON p.family_id = f.id AND p.category = ?
WHERE p.family_id IS NULL
", [$category])->getRowArray();
$missing = (int)($row['c'] ?? 0);
if ($missing > 0) {
$this->db->query("
INSERT INTO family_comm_prefs (family_id, category, via_email, via_sms, cc_all_guardians)
SELECT f.id, ?, ?, ?, ?
FROM families f
LEFT JOIN family_comm_prefs p
ON p.family_id = f.id AND p.category = ?
WHERE p.family_id IS NULL
", [$category, (int)$viaEmail, (int)$viaSms, (int)$ccAll, $category]);
$insertedTotal += $missing;
}
}
return $insertedTotal;
}
/** Utility: check if a column exists (case-insensitive) */
protected function columnExists(string $table, string $column): bool
{
if (! $this->db->tableExists($table)) return false;
$cols = array_map('strtolower', array_column($this->db->getFieldData($table), 'name'));
return in_array(strtolower($column), $cols, true);
}
}
+179
View File
@@ -0,0 +1,179 @@
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
class NavSeeder extends Seeder
{
public function run()
{
$db = \Config\Database::connect();
// Top-level groups (dropdown headers are just parents with no URL)
$groups = [
['label'=>'Users', 'url'=>null, 'sort_order'=>10],
['label'=>'Roles', 'url'=>null, 'sort_order'=>20],
['label'=>'Configuration', 'url'=>null, 'sort_order'=>30],
['label'=>'Staffing', 'url'=>null, 'sort_order'=>40],
['label'=>'Parents', 'url'=>null, 'sort_order'=>50],
['label'=>'Student-Affairs', 'url'=>null, 'sort_order'=>60],
['label'=>'Classes', 'url'=>null, 'sort_order'=>70],
['label'=>'Communication', 'url'=>null, 'sort_order'=>80],
['label'=>'Financial', 'url'=>null, 'sort_order'=>90],
['label'=>'Printables', 'url'=>null, 'sort_order'=>100],
['label'=>'Event Management', 'url'=>null, 'sort_order'=>110],
];
$builder = $db->table('nav_items');
$ids = [];
foreach ($groups as $g) {
$builder->insert([
'parent_id' => null,
'label' => $g['label'],
'url' => null,
'sort_order' => $g['sort_order'],
'is_enabled' => 1,
'created_at' => utc_now()
]);
$ids[$g['label']] = $db->insertID();
}
// Children for Users
$items = [
['parent'=>'Users', 'label'=>'Active Notifications', 'url'=>'notifications/active', 'sort_order'=>1],
['parent'=>'Users', 'label'=>'Deleted Notifications','url'=>'notifications/deleted','sort_order'=>2],
['parent'=>'Users', 'label'=>'Login Activity', 'url'=>'user/login_activity', 'sort_order'=>3],
['parent'=>'Users', 'label'=>'User List', 'url'=>'user/user_list', 'sort_order'=>4],
// Roles
['parent'=>'Roles', 'label'=>'Assign User Role', 'url'=>'rolepermission/assign_role', 'sort_order'=>1],
['parent'=>'Roles', 'label'=>'Role Management', 'url'=>'rolepermission/roles', 'sort_order'=>2],
// Configuration
['parent'=>'Configuration','label'=>'Add/Edit Configuration','url'=>'configuration/configuration_view','sort_order'=>1],
// Staffing
['parent'=>'Staffing','label'=>'Staff Profile','url'=>'staff/index','sort_order'=>1],
['parent'=>'Staffing','label'=>'Teacher Class Assignment','url'=>'administrator/teacher_class_assignment','sort_order'=>2],
// Parents
['parent'=>'Parents','label'=>'Parent Profile','url'=>'administrator/parent_profiles','sort_order'=>1],
// Student-Affairs
['parent'=>'Student-Affairs','label'=>'Attendance Management','url'=>'administrator/daily_attendance','sort_order'=>1],
['parent'=>'Student-Affairs','label'=>'Attendance Tracking System','url'=>'attendance/violations','sort_order'=>2],
['parent'=>'Student-Affairs','label'=>'Attendance Scans','url'=>'rfid_coming_soon','sort_order'=>3],
['parent'=>'Student-Affairs','label'=>'Classes List','url'=>'administrator/class_assignment','sort_order'=>4],
['parent'=>'Student-Affairs','label'=>'Emergency Contact','url'=>'administrator/emergency_contact','sort_order'=>5],
['parent'=>'Student-Affairs','label'=>'Enrollment-Withdrawal','url'=>'enroll_withdraw/enrollment_withdrawal','sort_order'=>6],
['parent'=>'Student-Affairs','label'=>'Flags Management','url'=>'flags/flags_management','sort_order'=>7],
['parent'=>'Student-Affairs','label'=>'School Calendar','url'=>'administrator/calendar_view','sort_order'=>8],
['parent'=>'Student-Affairs','label'=>'Score Analysis','url'=>'report/combined','sort_order'=>9],
['parent'=>'Student-Affairs','label'=>'Score Management','url'=>'grading','sort_order'=>10],
['parent'=>'Student-Affairs','label'=>'Student Class Assignment','url'=>'administrator/student_class_assignment','sort_order'=>11],
['parent'=>'Student-Affairs','label'=>'Student Profile','url'=>'administrator/student_profiles','sort_order'=>12],
// Classes
['parent'=>'Classes','label'=>'Classes List','url'=>'administrator/class_assignment','sort_order'=>1],
// Communication
['parent'=>'Communication','label'=>'Parent Email Extractor','url'=>'emails/parent_email_extractor','sort_order'=>1],
['parent'=>'Communication','label'=>'Parent Profile','url'=>'administrator/parent_profiles','sort_order'=>2],
['parent'=>'Communication','label'=>'Student Profile','url'=>'administrator/student_profiles','sort_order'=>3],
// Financial
['parent'=>'Financial','label'=>'Discount Management','url'=>'discounts/list','sort_order'=>1],
['parent'=>'Financial','label'=>'Expenses Management','url'=>'expenses/index','sort_order'=>2],
['parent'=>'Financial','label'=>'Financial Report','url'=>'payment/financial_report','sort_order'=>3],
['parent'=>'Financial','label'=>'Invoices Management','url'=>'invoice_payment/invoice_management','sort_order'=>4],
['parent'=>'Financial','label'=>'Manual Payment','url'=>'payment/manual_pay','sort_order'=>5],
['parent'=>'Financial','label'=>'PaypalTransactions','url'=>'administrator/paypal_transactions','sort_order'=>6],
['parent'=>'Financial','label'=>'Refund Management','url'=>'refunds/list','sort_order'=>7],
['parent'=>'Financial','label'=>'Reimbursement Management','url'=>'reimbursements/index','sort_order'=>8],
// Printables
['parent'=>'Printables','label'=>'Badges','url'=>'printables_reports/badge_form','sort_order'=>1],
['parent'=>'Printables','label'=>'Class Prep','url'=>'class-prep','sort_order'=>2],
['parent'=>'Printables','label'=>'Report Cards','url'=>'printables_reports/report_card','sort_order'=>3],
['parent'=>'Printables','label'=>'Stickers','url'=>'printables_reports/stickers','sort_order'=>4],
// Event Management
['parent'=>'Event Management','label'=>'Calendar','url'=>'administrator/calendar','sort_order'=>1],
['parent'=>'Event Management','label'=>'Events','url'=>'administrator/events','sort_order'=>2],
];
foreach ($items as $it) {
$builder->insert([
'parent_id' => $ids[$it['parent']] ?? null,
'label' => $it['label'],
'url' => $it['url'],
'sort_order' => $it['sort_order'],
'is_enabled' => 1,
'created_at' => utc_now(),
]);
}
// Map roles quickly: give full menu to administrator & principal; a subset to others
$roleMap = $db->table('role_nav_items');
$navRows = $db->table('nav_items')->orderBy('id')->get()->getResultArray();
$grantAllTo = ['administrator','principal','vice_principal'];
foreach ($navRows as $row) {
foreach ($grantAllTo as $role) {
$roleMap->insert([
'role' => strtolower($role),
'nav_item_id' => $row['id'],
'created_at' => utc_now()
]);
}
}
// Example: "admin" (limited)
$limitForAdmin = ['Enrollment-Withdrawal','Invoices Management'];
foreach ($navRows as $row) {
if (in_array($row['label'], $limitForAdmin, true)) {
$roleMap->insert([
'role' => 'admin',
'nav_item_id' => $row['id'],
'created_at' => utc_now()
]);
}
}
// Example: HOD Communication
$hodComm = ['Communication','Parent Profile','Student Profile','Parent Email Extractor'];
foreach ($navRows as $row) {
if (in_array($row['label'], $hodComm, true)) {
$roleMap->insert([
'role' => 'head of department (communication)',
'nav_item_id' => $row['id'],
'created_at' => utc_now()
]);
}
}
// Example: HOD Education (Student-Affairs group)
$hodEduLabels = ['Student-Affairs','Attendance Management','Classes List','Emergency Contact','Enrollment-Withdrawal','Flags Management','School Calendar','Score Analysis','Score Management','Student Class Assignment','Student Profile'];
foreach ($navRows as $row) {
if (in_array($row['label'], $hodEduLabels, true)) {
$roleMap->insert([
'role' => 'head of department (education)',
'nav_item_id' => $row['id'],
'created_at' => utc_now()
]);
}
}
// Example: HOD IT (Grades)
foreach ($navRows as $row) {
if (in_array($row['label'], ['Score Management'], true)) {
$roleMap->insert([
'role' => 'head of department (information technology)',
'nav_item_id' => $row['id'],
'created_at' => utc_now()
]);
}
}
}
}
@@ -0,0 +1,159 @@
<?php
namespace App\Database\Seeds;
use App\Models\SubjectCurriculumModel;
use CodeIgniter\Database\Seeder;
class SubjectCurriculumSeeder extends Seeder
{
public function run()
{
$model = new SubjectCurriculumModel();
$db = \Config\Database::connect();
$classMap = $this->buildClassMap($db);
if (empty($classMap)) {
log_message('warning', 'SubjectCurriculumSeeder could not map any classes. Skipping import.');
return;
}
$db->table($model->table)->truncate();
$now = date('Y-m-d H:i:s');
$this->importIslamic($model, $classMap, $now);
$this->importQuran($model, $classMap, $now);
}
private function buildClassMap($db): array
{
$rows = $db->table('classes')
->select('id, class_name')
->get()
->getResultArray();
$map = [];
foreach ($rows as $row) {
$key = $this->normalizeGradeKey($row['class_name'] ?? '');
if ($key === '') {
continue;
}
$map[$key] = (int) $row['id'];
}
return $map;
}
private function importIslamic(SubjectCurriculumModel $model, array $classMap, string $now): void
{
$entries = $this->readCsv(ROOTPATH . 'levels_1_to_9_table.csv');
if (empty($entries)) {
log_message('warning', 'SubjectCurriculumSeeder could not read levels_1_to_9_table.csv.');
return;
}
$batch = [];
foreach ($entries as $row) {
$grade = $this->normalizeGradeKey($row['Grade'] ?? '');
$classId = $classMap[$grade] ?? null;
if (! $classId) {
continue;
}
$unitNumber = is_numeric($row['Unit'] ?? null) ? (int) $row['Unit'] : null;
$unitTitle = trim($row['Unit Title'] ?? '');
$chapterName = trim($row['chapter'] ?? '');
if ($chapterName === '') {
continue;
}
$batch[] = [
'class_id' => $classId,
'subject' => 'islamic',
'unit_number' => $unitNumber,
'unit_title' => $unitTitle ?: null,
'chapter_name' => $chapterName,
'created_at' => $now,
'updated_at' => $now,
];
}
if (! empty($batch)) {
$model->insertBatch($batch);
}
}
private function importQuran(SubjectCurriculumModel $model, array $classMap, string $now): void
{
$entries = $this->readCsv(ROOTPATH . 'quran_surahs_by_grade.csv');
if (empty($entries)) {
log_message('warning', 'SubjectCurriculumSeeder could not read quran_surahs_by_grade.csv.');
return;
}
$batch = [];
foreach ($entries as $row) {
$grade = $this->normalizeGradeKey($row['Grade'] ?? '');
$classId = $classMap[$grade] ?? null;
if (! $classId) {
continue;
}
$surah = trim($row['Surah'] ?? '');
if ($surah === '') {
continue;
}
$batch[] = [
'class_id' => $classId,
'subject' => 'quran',
'unit_number' => null,
'unit_title' => null,
'chapter_name' => $surah,
'created_at' => $now,
'updated_at' => $now,
];
}
if (! empty($batch)) {
$model->insertBatch($batch);
}
}
private function readCsv(string $path): array
{
if (! is_file($path)) {
return [];
}
$handle = fopen($path, 'r');
if (! $handle) {
return [];
}
$rows = [];
$headers = fgetcsv($handle);
if (! $headers) {
fclose($handle);
return [];
}
$headers = array_map('trim', $headers);
while (($data = fgetcsv($handle)) !== false) {
$row = [];
foreach ($headers as $index => $header) {
$row[$header] = $data[$index] ?? '';
}
$rows[] = $row;
}
fclose($handle);
return $rows;
}
private function normalizeGradeKey(string $value): string
{
$value = strtolower(trim($value));
$value = preg_replace('/^grade\s*/i', '', $value);
return trim($value, " \t\n\r\0\x0B");
}
}