031e499819
API CI/CD / Validate (composer + pint) (push) Successful in 3m10s
API CI/CD / Test (PHPUnit) (push) Failing after 6m49s
API CI/CD / Build frontend assets (push) Successful in 1m3s
API CI/CD / Security audit (push) Failing after 1m0s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
268 lines
11 KiB
PHP
268 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Families;
|
|
|
|
use App\Models\StudentClass;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ParentProfileAdminQueryService
|
|
{
|
|
public function __construct(private FamilyFinanceService $finance) {}
|
|
|
|
public function index(string $schoolYear, string $search = '', int $perPage = 25): LengthAwarePaginator
|
|
{
|
|
$query = DB::table('users as u')
|
|
->select(
|
|
'u.id',
|
|
'u.firstname',
|
|
'u.lastname',
|
|
'u.email',
|
|
'u.cellphone',
|
|
'u.status',
|
|
DB::raw('COUNT(DISTINCT fg.family_id) as families_count'),
|
|
DB::raw('COUNT(DISTINCT s.id) as selected_year_students_count'),
|
|
DB::raw('COALESCE(SUM(DISTINCT i.balance), 0) as balance')
|
|
)
|
|
->leftJoin('family_guardians as fg', 'fg.user_id', '=', 'u.id')
|
|
->leftJoin('family_students as fs', 'fs.family_id', '=', 'fg.family_id')
|
|
->leftJoin('students as s', function ($join) use ($schoolYear) {
|
|
$join->on('s.id', '=', 'fs.student_id')
|
|
->where('s.school_year', '=', $schoolYear);
|
|
})
|
|
->leftJoin('invoices as i', function ($join) use ($schoolYear) {
|
|
$join->on('i.parent_id', '=', 'u.id')
|
|
->where('i.school_year', '=', $schoolYear);
|
|
})
|
|
->where($this->relevantParentConstraint($schoolYear))
|
|
->groupBy('u.id', 'u.firstname', 'u.lastname', 'u.email', 'u.cellphone', 'u.status')
|
|
->orderBy('u.lastname')
|
|
->orderBy('u.firstname');
|
|
|
|
if ($search !== '') {
|
|
$needle = '%'.str_replace(['%', '_'], ['\\%', '\\_'], $search).'%';
|
|
$query->where(function ($inner) use ($needle) {
|
|
$inner->whereRaw("CONCAT_WS(' ', u.firstname, u.lastname) LIKE ?", [$needle])
|
|
->orWhere('u.email', 'like', $needle)
|
|
->orWhere('u.cellphone', 'like', $needle)
|
|
->orWhereExists(function ($exists) use ($needle) {
|
|
$exists->selectRaw('1')
|
|
->from('family_guardians as sfg')
|
|
->join('family_students as sfs', 'sfs.family_id', '=', 'sfg.family_id')
|
|
->join('students as ss', 'ss.id', '=', 'sfs.student_id')
|
|
->whereColumn('sfg.user_id', 'u.id')
|
|
->where(function ($student) use ($needle) {
|
|
$student->whereRaw("CONCAT_WS(' ', ss.firstname, ss.lastname) LIKE ?", [$needle]);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
return $query->paginate(max(1, min($perPage, 100)));
|
|
}
|
|
|
|
public function search(string $schoolYear, string $search, int $limit = 10): array
|
|
{
|
|
if (trim($search) === '') {
|
|
return [];
|
|
}
|
|
|
|
return $this->index($schoolYear, $search, $limit)
|
|
->getCollection()
|
|
->map(fn ($row) => $this->parentSummary((array) $row, $schoolYear))
|
|
->all();
|
|
}
|
|
|
|
public function show(int $parentId, string $schoolYear): ?array
|
|
{
|
|
$parent = DB::table('users')
|
|
->select('id', 'firstname', 'lastname', 'email', 'cellphone', 'address_street', 'city', 'state', 'zip', 'status')
|
|
->where('id', $parentId)
|
|
->first();
|
|
|
|
if (! $parent) {
|
|
return null;
|
|
}
|
|
|
|
$guardians = [
|
|
[
|
|
'user_id' => $parentId,
|
|
'firstname' => $parent->firstname,
|
|
'lastname' => $parent->lastname,
|
|
],
|
|
];
|
|
$finance = $this->finance->loadFinancialsForParents([$parentId], $schoolYear);
|
|
|
|
return [
|
|
'parent' => (array) $parent,
|
|
'families' => $this->familiesForParent($parentId, $schoolYear),
|
|
'students' => $this->studentsForParent($parentId, $schoolYear),
|
|
'emergency_contacts' => $this->emergencyContactsForParents($guardians, $schoolYear),
|
|
'invoices' => $finance['invoices'],
|
|
'payments' => $finance['payments'],
|
|
'finance_summary' => $finance['summary'],
|
|
];
|
|
}
|
|
|
|
public function updateParent(int $parentId, array $payload): ?array
|
|
{
|
|
$allowed = array_intersect_key($payload, array_flip([
|
|
'firstname',
|
|
'lastname',
|
|
'email',
|
|
'cellphone',
|
|
'address_street',
|
|
'city',
|
|
'state',
|
|
'zip',
|
|
'status',
|
|
]));
|
|
|
|
if ($allowed !== []) {
|
|
$allowed['updated_at'] = now();
|
|
DB::table('users')->where('id', $parentId)->update($allowed);
|
|
}
|
|
|
|
$parent = DB::table('users')->where('id', $parentId)->first();
|
|
|
|
return $parent ? (array) $parent : null;
|
|
}
|
|
|
|
public function parentSummary(array $row, string $schoolYear): array
|
|
{
|
|
$balance = (float) ($row['balance'] ?? 0);
|
|
$primaryFamily = DB::table('family_guardians as fg')
|
|
->join('families as f', 'f.id', '=', 'fg.family_id')
|
|
->where('fg.user_id', (int) ($row['id'] ?? 0))
|
|
->orderByDesc('fg.is_primary')
|
|
->orderBy('f.household_name')
|
|
->select('f.id', 'f.household_name')
|
|
->first();
|
|
|
|
return [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'firstname' => $row['firstname'] ?? null,
|
|
'lastname' => $row['lastname'] ?? null,
|
|
'email' => $row['email'] ?? null,
|
|
'cellphone' => $row['cellphone'] ?? null,
|
|
'is_active' => strtolower((string) ($row['status'] ?? '')) === 'active',
|
|
'families_count' => (int) ($row['families_count'] ?? 0),
|
|
'students_count' => (int) ($row['selected_year_students_count'] ?? 0),
|
|
'selected_year_students_count' => (int) ($row['selected_year_students_count'] ?? 0),
|
|
'balance' => $balance,
|
|
'positive_unpaid_balance' => max(0, $balance),
|
|
'credit_balance' => $balance < 0 ? abs($balance) : 0.0,
|
|
'primary_family' => $primaryFamily ? (array) $primaryFamily : null,
|
|
'school_year' => $schoolYear,
|
|
];
|
|
}
|
|
|
|
private function relevantParentConstraint(string $schoolYear): callable
|
|
{
|
|
return function ($query) use ($schoolYear) {
|
|
$query->whereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('family_guardians as rfg')
|
|
->join('family_students as rfs', 'rfs.family_id', '=', 'rfg.family_id')
|
|
->join('students as rs', 'rs.id', '=', 'rfs.student_id')
|
|
->whereColumn('rfg.user_id', 'u.id')
|
|
->where('rs.school_year', $schoolYear);
|
|
})->orWhereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('students as direct_students')
|
|
->whereColumn('direct_students.parent_id', 'u.id')
|
|
->where('direct_students.school_year', $schoolYear);
|
|
})->orWhereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('invoices as ri')
|
|
->whereColumn('ri.parent_id', 'u.id')
|
|
->where('ri.school_year', $schoolYear);
|
|
})->orWhereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('parent_accounts as pa')
|
|
->whereColumn('pa.parent_id', 'u.id')
|
|
->where('pa.school_year', $schoolYear);
|
|
})->orWhereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('emergency_contacts as ec')
|
|
->whereColumn('ec.parent_id', 'u.id')
|
|
->where('ec.school_year', $schoolYear);
|
|
});
|
|
};
|
|
}
|
|
|
|
private function familiesForParent(int $parentId, string $schoolYear): array
|
|
{
|
|
return DB::table('family_guardians as fg')
|
|
->join('families as f', 'f.id', '=', 'fg.family_id')
|
|
->where('fg.user_id', $parentId)
|
|
->whereExists(function ($exists) use ($schoolYear) {
|
|
$exists->selectRaw('1')
|
|
->from('family_students as fs')
|
|
->join('students as s', 's.id', '=', 'fs.student_id')
|
|
->whereColumn('fs.family_id', 'f.id')
|
|
->where('s.school_year', $schoolYear);
|
|
})
|
|
->orderBy('f.household_name')
|
|
->select('f.*', 'fg.relation', 'fg.is_primary', 'fg.receive_emails', 'fg.receive_sms')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
}
|
|
|
|
private function studentsForParent(int $parentId, string $schoolYear): array
|
|
{
|
|
$rows = DB::table('students as s')
|
|
->select('s.id', 's.firstname', 's.lastname', 's.parent_id')
|
|
->where('s.school_year', $schoolYear)
|
|
->where(function ($query) use ($parentId) {
|
|
$query->where('s.parent_id', $parentId)
|
|
->orWhereExists(function ($exists) use ($parentId) {
|
|
$exists->selectRaw('1')
|
|
->from('family_guardians as fg')
|
|
->join('family_students as fs', 'fs.family_id', '=', 'fg.family_id')
|
|
->whereColumn('fs.student_id', 's.id')
|
|
->where('fg.user_id', $parentId);
|
|
});
|
|
})
|
|
->orderBy('s.lastname')
|
|
->orderBy('s.firstname')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
|
|
foreach ($rows as &$row) {
|
|
$studentId = (int) ($row['id'] ?? 0);
|
|
$classSectionName = $studentId > 0
|
|
? (string) (StudentClass::getClassSectionsByStudentId($studentId, $schoolYear) ?? '')
|
|
: '';
|
|
$row['class_section_name'] = $classSectionName;
|
|
$row['grade'] = $classSectionName;
|
|
}
|
|
unset($row);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function emergencyContactsForParents(array $guardians, string $schoolYear): array
|
|
{
|
|
$parentIds = array_values(array_filter(array_map(
|
|
static fn ($g) => (int) ($g['user_id'] ?? 0),
|
|
$guardians
|
|
)));
|
|
|
|
if ($parentIds === []) {
|
|
return [];
|
|
}
|
|
|
|
return 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)
|
|
->where('school_year', $schoolYear)
|
|
->orderByDesc('updated_at')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
}
|
|
}
|