307 lines
11 KiB
PHP
307 lines
11 KiB
PHP
<?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 parent’s 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);
|
||
}
|
||
}
|