Files
alrahma_sunday_school_api/app/Services/Families/FamilyQueryService.php
T
2026-06-08 23:45:55 -04:00

388 lines
13 KiB
PHP

<?php
namespace App\Services\Families;
use App\Models\Family;
use App\Models\Student;
use App\Models\StudentClass;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class FamilyQueryService
{
public function __construct(private FamilyFinanceService $finance)
{
}
public function listStudentsForSelect(): array
{
return DB::table('students')
->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;
}
}