Files
alrahma_sunday_school/app/Database/Seeds/FamiliesRebuildSeeder.php
T
2026-02-10 22:11:06 -05:00

307 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}