ensureTablesExist(['families', 'family_students', 'family_guardians']); return DB::transaction(function () { $primaryParents = DB::table('students') ->select('parent_id') ->whereNotNull('parent_id') ->distinct() ->get() ->map(fn ($r) => (array) $r) ->all(); $created = 0; $linkedStudents = 0; $linkedGuardians = 0; foreach ($primaryParents as $row) { $primaryUserId = (int) ($row['parent_id'] ?? 0); if ($primaryUserId <= 0) { continue; } $familyId = $this->ensureFamilyForPrimaryParent($primaryUserId, $created); $students = DB::table('students') ->select('id') ->where('parent_id', $primaryUserId) ->get() ->map(fn ($r) => (array) $r) ->all(); foreach ($students as $s) { $linkedStudents += $this->attachStudentToFamily((int) ($s['id'] ?? 0), $familyId); } $linkedGuardians += $this->attachGuardianUser($familyId, $primaryUserId, 'primary', true, 1, 0); } return [ 'families_created' => $created, 'students_linked' => $linkedStudents, 'guardians_linked' => $linkedGuardians, ]; }); } public function attachSecondByUser(int $studentId, int $userId, string $relation = 'secondary'): array { if ($studentId <= 0 || $userId <= 0) { return ['ok' => false, 'message' => 'student_id and user_id required']; } $familyId = $this->familyIdFromStudentPrimary($studentId); if (!$familyId) { return ['ok' => false, 'message' => 'No primary family found for this student']; } $rows = $this->attachGuardianUser($familyId, $userId, $relation, false, 1, 0); return ['ok' => true, 'attached' => $rows, 'family_id' => $familyId]; } public function attachSecondByEmail(int $studentId, string $email, ?string $firstname, ?string $lastname, string $relation = 'secondary'): array { if ($studentId <= 0 || $email === '') { return ['ok' => false, 'message' => 'student_id and email required']; } $familyId = $this->familyIdFromStudentPrimary($studentId); if (!$familyId) { return ['ok' => false, 'message' => 'No primary family found for this student']; } return DB::transaction(function () use ($email, $firstname, $lastname, $relation, $familyId) { $user = User::query()->where('email', $email)->first(); if (!$user) { $user = User::query()->create([ 'firstname' => (string) ($firstname ?? ''), 'lastname' => (string) ($lastname ?? ''), 'email' => $email, 'status' => 'Active', 'user_type' => 'secondary', 'cellphone' => '', 'address_street' => '', 'city' => '', 'state' => '', 'zip' => '', 'accept_school_policy' => 1, 'password' => bcrypt('temporary'), 'semester' => '', ]); } $rows = $this->attachGuardianUser($familyId, (int) $user->id, $relation, false, 1, 0); return [ 'ok' => true, 'attached' => $rows, 'family_id' => $familyId, 'user_id' => (int) $user->id, ]; }); } public function setPrimaryHome(int $familyId, int $studentId, bool $isPrimaryHome): void { FamilyStudent::query() ->where('family_id', $familyId) ->where('student_id', $studentId) ->update(['is_primary_home' => $isPrimaryHome ? 1 : 0]); } public function setGuardianFlags(int $familyId, int $userId, array $flags): void { $data = []; foreach (['receive_emails', 'is_primary', 'receive_sms', 'relation'] as $key) { if (array_key_exists($key, $flags)) { $value = $flags[$key]; $data[$key] = in_array($key, ['receive_emails', 'is_primary', 'receive_sms'], true) ? (int) $value : (string) $value; } } if (empty($data)) { return; } FamilyGuardian::query() ->where('family_id', $familyId) ->where('user_id', $userId) ->update($data); } public function unlinkGuardian(int $familyId, int $userId): void { FamilyGuardian::query() ->where('family_id', $familyId) ->where('user_id', $userId) ->delete(); } public function unlinkStudent(int $familyId, int $studentId): void { FamilyStudent::query() ->where('family_id', $familyId) ->where('student_id', $studentId) ->delete(); } public function importSecondParentsFromLegacy(): array { $legacyTable = 'parents'; $this->ensureTablesExist(['families', 'family_students', 'family_guardians']); if (!Schema::hasTable($legacyTable)) { return ['ok' => false, 'message' => "Legacy table '{$legacyTable}' not found"]; } $rows = DB::table($legacyTable) ->selectRaw('firstparent_id AS first_id, secondparent_id AS second_id, secondparent_email AS email, secondparent_firstname AS firstname, secondparent_lastname AS lastname') ->where(function ($q) { $q->where('secondparent_id', '!=', 0) ->orWhere('secondparent_email', '!=', ''); }) ->get() ->map(fn ($r) => (array) $r) ->all(); $createdUsers = 0; $linked = 0; $skipped = 0; foreach ($rows as $r) { $firstId = (int) ($r['first_id'] ?? 0); $secondId = (int) ($r['second_id'] ?? 0); $email = trim((string) ($r['email'] ?? '')); if ($firstId <= 0 || ($secondId <= 0 && $email === '')) { $skipped++; continue; } $tmp = 0; $familyId = $this->ensureFamilyForPrimaryParent($firstId, $tmp); $userId = 0; if ($secondId > 0) { $userId = $secondId; } else { $user = User::query()->where('email', $email)->first(); if (!$user) { $user = User::query()->create([ 'firstname' => (string) ($r['firstname'] ?? ''), 'lastname' => (string) ($r['lastname'] ?? ''), 'email' => $email, 'status' => 'Active', 'user_type' => 'secondary', 'cellphone' => '', 'address_street' => '', 'city' => '', 'state' => '', 'zip' => '', 'accept_school_policy' => 1, 'password' => bcrypt('temporary'), 'semester' => '', ]); $createdUsers++; } $userId = (int) $user->id; } $students = DB::table('students') ->select('id') ->where('parent_id', $firstId) ->get() ->map(fn ($r) => (array) $r) ->all(); foreach ($students as $sr) { $sid = (int) ($sr['id'] ?? 0); if ($sid > 0) { $this->attachStudentToFamily($sid, $familyId); } } $linked += $this->attachGuardianUser($familyId, $userId, 'guardian', false, 1, 0); } return [ 'ok' => true, 'created_users' => $createdUsers, 'guardians_linked' => $linked, 'skipped' => $skipped, 'total_source' => count($rows), ]; } private function ensureFamilyForPrimaryParent(int $primaryUserId, int &$createdCounter): int { $code = 'FAM-' . $primaryUserId; $row = Family::query()->where('family_code', $code)->first(); if ($row) { return (int) $row->id; } $family = Family::query()->create([ 'family_code' => $code, 'household_name' => 'Family of User ' . $primaryUserId, 'is_active' => 1, ]); $createdCounter++; return (int) $family->id; } private function attachStudentToFamily(int $studentId, int $familyId): int { if ($studentId <= 0 || $familyId <= 0) { return 0; } return DB::table('family_students')->insertOrIgnore([ 'family_id' => $familyId, 'student_id' => $studentId, 'is_primary_home' => 1, 'created_at' => now(), 'updated_at' => now(), ]); } private function attachGuardianUser( int $familyId, int $userId, string $relation = 'primary', bool $isPrimary = false, int $receiveEmails = 1, int $receiveSms = 0 ): int { if ($familyId <= 0 || $userId <= 0) { return 0; } return DB::table('family_guardians')->insertOrIgnore([ 'family_id' => $familyId, 'user_id' => $userId, 'relation' => $relation, 'is_primary' => $isPrimary ? 1 : 0, 'receive_emails' => $receiveEmails, 'receive_sms' => $receiveSms, 'created_at' => now(), 'updated_at' => now(), ]); } private function familyIdFromStudentPrimary(int $studentId): ?int { $student = Student::query()->select('parent_id')->find($studentId); if (!$student || empty($student->parent_id)) { return null; } $code = 'FAM-' . (int) $student->parent_id; $row = Family::query()->select('id')->where('family_code', $code)->first(); if ($row) { return (int) $row->id; } $fallback = DB::table('family_students') ->select('family_id') ->where('student_id', $studentId) ->orderByDesc('is_primary_home') ->first(); return $fallback?->family_id ? (int) $fallback->family_id : null; } private function ensureTablesExist(array $tables): void { foreach ($tables as $table) { if (!Schema::hasTable($table)) { Log::warning('Family tables missing', ['table' => $table]); throw new \RuntimeException("Missing required table '{$table}'. Run migrations."); } } } }