select('id', DB::raw("CONCAT(lastname, ', ', firstname) AS name")) ->orderBy('lastname') ->orderBy('firstname') ->get() ->map(fn ($r) => (array) $r) ->all(); } public function searchSuggestions(string $query, int $limit = 8): array { $query = trim($query); if ($query === '') { return []; } $qs = '%'.str_replace(['%', '_'], ['\\%', '\\_'], $query).'%'; $students = DB::table('students') ->select('id', 'firstname', 'lastname') ->whereRaw("CONCAT_WS(' ', firstname, lastname) LIKE ?", [$qs]) ->orderBy('lastname') ->orderBy('firstname') ->limit($limit) ->get() ->map(fn ($r) => (array) $r) ->all(); $guardians = DB::table('users') ->select('id', 'firstname', 'lastname', 'email', 'cellphone') ->where(function ($q) use ($qs) { $q->whereRaw("CONCAT_WS(' ', firstname, lastname) LIKE ?", [$qs]) ->orWhere('email', 'like', $qs) ->orWhere('cellphone', 'like', $qs); }) ->orderBy('lastname') ->orderBy('firstname') ->limit($limit) ->get() ->map(fn ($r) => (array) $r) ->all(); $items = []; foreach ($students as $s) { $items[] = [ 'type' => 'student', 'id' => (int) ($s['id'] ?? 0), 'label' => trim(($s['firstname'] ?? '').' '.($s['lastname'] ?? '')), 'sub' => 'Student', ]; } foreach ($guardians as $g) { $items[] = [ 'type' => 'guardian', 'id' => (int) ($g['id'] ?? 0), 'label' => trim(($g['firstname'] ?? '').' '.($g['lastname'] ?? '')), 'sub' => trim(($g['email'] ?? '').' '.($g['cellphone'] ?? '')), ]; } return $items; } public function resolveStudentIdByGuardian(int $guardianId): ?int { if ($guardianId <= 0) { return null; } $row = DB::table('family_guardians as fg') ->join('family_students as fs', 'fs.family_id', '=', 'fg.family_id') ->join('students as s', 's.id', '=', 'fs.student_id') ->where('fg.user_id', $guardianId) ->orderBy('s.lastname') ->orderBy('s.firstname') ->select('s.id as student_id') ->first(); if ($row && ! empty($row->student_id)) { return (int) $row->student_id; } $fallback = Student::query() ->select('id') ->where('parent_id', $guardianId) ->orderBy('lastname') ->orderBy('firstname') ->first(); return $fallback?->id ? (int) $fallback->id : null; } public function listFamiliesByStudent(int $studentId): array { return DB::table('family_students as fs') ->join('families as f', 'f.id', '=', 'fs.family_id') ->where('fs.student_id', $studentId) ->where('f.is_active', 1) ->orderByDesc('fs.is_primary_home') ->orderBy('f.household_name') ->select('f.*', 'fs.is_primary_home') ->get() ->map(fn ($r) => (array) $r) ->all(); } public function listGuardiansByFamily(int $familyId): array { return DB::table('family_guardians as fg') ->join('users as u', 'u.id', '=', 'fg.user_id') ->where('fg.family_id', $familyId) ->orderByDesc('fg.is_primary') ->orderBy('u.lastname') ->orderBy('u.firstname') ->select( 'u.id as user_id', 'u.firstname', 'u.lastname', 'u.email', 'u.cellphone', 'u.address_street', 'u.city', 'u.state', 'u.zip', 'fg.relation', 'fg.is_primary', 'fg.receive_emails', 'fg.receive_sms' ) ->get() ->map(fn ($r) => (array) $r) ->all(); } public function adminIndexData(?int $studentId, ?int $guardianId, string $schoolYear): array { $studentId = (int) ($studentId ?? 0); $guardianId = (int) ($guardianId ?? 0); $resolvedStudentId = null; if ($studentId <= 0 && $guardianId > 0) { $resolvedStudentId = $this->resolveStudentIdByGuardian($guardianId); if ($resolvedStudentId) { $studentId = $resolvedStudentId; } } $data = [ 'student' => null, 'families' => [], 'guardians' => [], 'students' => $this->listStudentsForSelect(), 'searchStudents' => DB::table('students') ->select('id', 'firstname', 'lastname') ->orderBy('lastname') ->orderBy('firstname') ->get() ->map(fn ($r) => (array) $r) ->all(), 'searchGuardians' => DB::table('family_guardians as fg') ->join('users as u', 'u.id', '=', 'fg.user_id') ->distinct() ->select('u.id', 'u.firstname', 'u.lastname', 'u.email', 'u.cellphone') ->orderBy('u.lastname') ->orderBy('u.firstname') ->get() ->map(fn ($r) => (array) $r) ->all(), 'resolved_student_id' => $resolvedStudentId, ]; if ($studentId > 0) { $data['student'] = DB::table('students') ->select('id', 'firstname', 'lastname') ->where('id', $studentId) ->first(); $data['student'] = $data['student'] ? (array) $data['student'] : null; $families = DB::table('family_students as fs') ->join('families as f', 'f.id', '=', 'fs.family_id') ->where('fs.student_id', $studentId) ->orderByDesc('fs.is_primary_home') ->orderBy('f.household_name') ->select( 'f.id', 'f.family_code', 'f.household_name', 'f.address_line1', 'f.address_line2', 'f.city', 'f.state', 'f.postal_code', 'f.country', 'f.primary_phone', 'f.preferred_lang', 'f.preferred_contact_method', 'f.is_active', 'fs.is_primary_home' ) ->get() ->map(fn ($r) => (array) $r) ->all(); foreach ($families as &$fam) { $familyId = (int) ($fam['id'] ?? 0); $fam['guardians'] = $this->listGuardiansByFamily($familyId); $fam['students'] = $this->studentsForFamily($familyId, $schoolYear); $parentIds = array_values(array_filter(array_map( static fn ($g) => (int) ($g['user_id'] ?? 0), $fam['guardians'] ))); $finance = $this->finance->loadFinancialsForParents($parentIds); $fam['invoices'] = $finance['invoices']; $fam['payments'] = $finance['payments']; $fam['finance_summary'] = $finance['summary']; $fam['invoice_map'] = $finance['invoice_map']; } unset($fam); $data['families'] = $families; if (! empty($families)) { $familyId = (int) ($families[0]['id'] ?? 0); $data['guardians'] = $familyId ? $this->listGuardiansByFamily($familyId) : []; } } return $data; } public function familyCard(?int $studentId, ?int $guardianId, ?int $familyId, string $schoolYear): ?array { $studentId = (int) ($studentId ?? 0); $guardianId = (int) ($guardianId ?? 0); $familyId = (int) ($familyId ?? 0); if ($familyId <= 0) { if ($studentId > 0) { $row = DB::table('family_students as fs') ->join('families as f', 'f.id', '=', 'fs.family_id') ->where('fs.student_id', $studentId) ->orderByDesc('fs.is_primary_home') ->orderBy('f.household_name') ->select('f.id') ->first(); $familyId = $row?->id ? (int) $row->id : 0; } elseif ($guardianId > 0) { $row = DB::table('family_guardians as fg') ->join('families as f', 'f.id', '=', 'fg.family_id') ->where('fg.user_id', $guardianId) ->orderBy('f.household_name') ->select('f.id') ->first(); if ($row?->id) { $familyId = (int) $row->id; } else { $fallback = DB::table('students as s') ->join('family_students as fs', 'fs.student_id', '=', 's.id') ->join('families as f', 'f.id', '=', 'fs.family_id') ->where('s.parent_id', $guardianId) ->orderByDesc('fs.is_primary_home') ->orderBy('f.household_name') ->select('f.id') ->first(); $familyId = $fallback?->id ? (int) $fallback->id : 0; } } } if ($familyId <= 0) { return null; } $family = Family::query() ->select( 'id', 'family_code', 'household_name', 'address_line1', 'address_line2', 'city', 'state', 'postal_code', 'country', 'primary_phone', 'preferred_lang', 'preferred_contact_method', 'is_active' ) ->find($familyId); if (! $family) { return null; } $payload = $family->toArray(); $payload['guardians'] = $this->listGuardiansByFamily($familyId); $payload['students'] = $this->studentsForFamily($familyId, $schoolYear); $payload['emergency_contacts'] = $this->emergencyContactsForParents($payload['guardians']); $parentIds = array_values(array_filter(array_map( static fn ($g) => (int) ($g['user_id'] ?? 0), $payload['guardians'] ))); $finance = $this->finance->loadFinancialsForParents($parentIds); $payload['invoices'] = $finance['invoices']; $payload['payments'] = $finance['payments']; $payload['finance_summary'] = $finance['summary']; $payload['invoice_map'] = $finance['invoice_map']; return $payload; } private function studentsForFamily(int $familyId, string $schoolYear): array { $rows = DB::table('family_students as fs') ->join('students as s', 's.id', '=', 'fs.student_id') ->where('fs.family_id', $familyId) ->orderBy('s.lastname') ->orderBy('s.firstname') ->select('s.id', 's.firstname', 's.lastname') ->get() ->map(fn ($r) => (array) $r) ->all(); foreach ($rows as &$row) { $sid = (int) ($row['id'] ?? 0); $row['grade'] = $sid ? (string) (StudentClass::getClassSectionsByStudentId($sid, $schoolYear) ?? '') : ''; } unset($row); return $rows; } private function emergencyContactsForParents(array $guardians): array { $parentIds = array_values(array_filter(array_map( static fn ($g) => (int) ($g['user_id'] ?? 0), $guardians ))); if (empty($parentIds)) { return []; } $guardianMap = []; foreach ($guardians as $g) { $gid = (int) ($g['user_id'] ?? 0); if ($gid > 0) { $guardianMap[$gid] = trim(($g['firstname'] ?? '').' '.($g['lastname'] ?? '')); } } $rows = DB::table('emergency_contacts') ->select('id', 'parent_id', 'emergency_contact_name', 'relation', 'cellphone', 'email', 'school_year', 'semester', 'created_at', 'updated_at') ->whereIn('parent_id', $parentIds) ->orderBy('updated_at', 'DESC') ->get() ->map(fn ($r) => (array) $r) ->all(); foreach ($rows as &$row) { $pid = (int) ($row['parent_id'] ?? 0); $row['parent_label'] = $guardianMap[$pid] ?? ('Parent #'.$pid); } unset($row); return $rows; } }