add projet
This commit is contained in:
+525
@@ -0,0 +1,525 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FamilyAdminController extends BaseApiController
|
||||
{
|
||||
protected Configuration $config;
|
||||
protected Invoice $invoice;
|
||||
protected Payment $payment;
|
||||
protected StudentClass $studentClass;
|
||||
protected ?string $schoolYear;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->config = model(Configuration::class);
|
||||
$this->invoice = model(Invoice::class);
|
||||
$this->payment = model(Payment::class);
|
||||
$this->studentClass = model(StudentClass::class);
|
||||
$this->schoolYear = $this->config->getConfig('school_year');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$studentId = (int) ($this->request->getGet('student_id') ?? 0);
|
||||
$guardianId = (int) ($this->request->getGet('guardian_id') ?? 0);
|
||||
|
||||
try {
|
||||
$data = [
|
||||
'student' => null,
|
||||
'families' => [],
|
||||
'guardians' => [],
|
||||
'students' => [],
|
||||
'search_students' => [],
|
||||
'search_guardians' => [],
|
||||
];
|
||||
|
||||
// Simple student list for select
|
||||
$data['students'] = DB::table('students')
|
||||
->select('id', DB::raw("CONCAT(lastname, ', ', firstname) AS name"))
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
// Preload search datasets
|
||||
$data['search_students'] = DB::table('students')
|
||||
->select('id', 'firstname', 'lastname')
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$data['search_guardians'] = DB::table('family_guardians as fg')
|
||||
->distinct()
|
||||
->select('u.id', 'u.firstname', 'u.lastname', 'u.email', 'u.cellphone')
|
||||
->join('users as u', 'u.id', '=', 'fg.user_id')
|
||||
->orderBy('u.lastname')
|
||||
->orderBy('u.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
// If a guardian is provided, resolve one of their students
|
||||
if (!$studentId && $guardianId) {
|
||||
$row = DB::table('family_guardians as fg')
|
||||
->select('s.id as student_id')
|
||||
->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')
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
$row = DB::table('students as s')
|
||||
->select('s.id as student_id')
|
||||
->where('s.parent_id', $guardianId)
|
||||
->orderBy('s.lastname')
|
||||
->orderBy('s.firstname')
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($row && !empty($row->student_id)) {
|
||||
$studentId = (int) $row->student_id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($studentId > 0) {
|
||||
$student = DB::table('students')
|
||||
->select('id', 'firstname', 'lastname')
|
||||
->where('id', $studentId)
|
||||
->first();
|
||||
|
||||
if ($student) {
|
||||
$data['student'] = (array) $student;
|
||||
|
||||
// Fetch all families for this student
|
||||
$families = DB::table('family_students as fs')
|
||||
->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'
|
||||
)
|
||||
->join('families as f', 'f.id', '=', 'fs.family_id')
|
||||
->where('fs.student_id', $studentId)
|
||||
->orderByDesc('fs.is_primary_home')
|
||||
->orderBy('f.household_name')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
// Enrich each family with guardians, students, and financials
|
||||
foreach ($families as &$fam) {
|
||||
$fid = (int) $fam['id'];
|
||||
|
||||
// Guardians
|
||||
$guardians = DB::table('family_guardians as fg')
|
||||
->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'
|
||||
)
|
||||
->join('users as u', 'u.id', '=', 'fg.user_id')
|
||||
->where('fg.family_id', $fid)
|
||||
->orderByDesc('fg.is_primary')
|
||||
->orderBy('u.lastname')
|
||||
->orderBy('u.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$fam['guardians'] = $guardians;
|
||||
|
||||
// Students in this family
|
||||
$studentsRows = DB::table('family_students as fs')
|
||||
->select('s.id', 's.firstname', 's.lastname')
|
||||
->join('students as s', 's.id', '=', 'fs.student_id')
|
||||
->where('fs.family_id', $fid)
|
||||
->orderBy('s.lastname')
|
||||
->orderBy('s.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
// Enrich with grade label
|
||||
if (!empty($studentsRows)) {
|
||||
foreach ($studentsRows as &$sr) {
|
||||
$sid = (int) ($sr['id'] ?? 0);
|
||||
$sr['grade'] = $sid && $this->schoolYear
|
||||
? (string) ($this->studentClass->getClassSectionsByStudentId($sid, $this->schoolYear) ?? '')
|
||||
: '';
|
||||
}
|
||||
unset($sr);
|
||||
}
|
||||
|
||||
$fam['students'] = $studentsRows;
|
||||
|
||||
// Financials: invoices and payments for all guardians
|
||||
$parentIds = array_map(static fn($g) => (int) ($g['user_id'] ?? 0), $guardians);
|
||||
$parentIds = array_values(array_filter($parentIds));
|
||||
|
||||
$fam['invoices'] = [];
|
||||
$fam['payments'] = [];
|
||||
$fam['finance_summary'] = [
|
||||
'invoices_count' => 0,
|
||||
'total_amount' => 0.0,
|
||||
'paid_amount' => 0.0,
|
||||
'balance' => 0.0,
|
||||
];
|
||||
|
||||
if (!empty($parentIds)) {
|
||||
// Invoices
|
||||
$invRows = DB::table('invoices')
|
||||
->select(
|
||||
'id', 'parent_id', 'invoice_number', 'status', 'total_amount',
|
||||
'paid_amount', 'balance', 'issue_date', 'due_date'
|
||||
)
|
||||
->whereIn('parent_id', $parentIds)
|
||||
->orderByDesc('issue_date')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$fam['invoices'] = $invRows;
|
||||
|
||||
// Map invoice id -> number
|
||||
$invoiceMap = [];
|
||||
foreach ($invRows as $ir) {
|
||||
$invoiceMap[(int) $ir['id']] = (string) ($ir['invoice_number'] ?? '');
|
||||
}
|
||||
$fam['invoice_map'] = $invoiceMap;
|
||||
|
||||
// Aggregate summary
|
||||
foreach ($invRows as $ir) {
|
||||
$fam['finance_summary']['invoices_count']++;
|
||||
$fam['finance_summary']['total_amount'] += (float) ($ir['total_amount'] ?? 0);
|
||||
$fam['finance_summary']['paid_amount'] += (float) ($ir['paid_amount'] ?? 0);
|
||||
$fam['finance_summary']['balance'] += (float) ($ir['balance'] ?? 0);
|
||||
}
|
||||
|
||||
// Recent payments (limit 10)
|
||||
$payRows = DB::table('payments')
|
||||
->select(
|
||||
'id', 'parent_id', 'invoice_id', 'paid_amount', 'balance',
|
||||
'payment_method', 'payment_date', 'status'
|
||||
)
|
||||
->whereIn('parent_id', $parentIds)
|
||||
->orderByDesc('payment_date')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$fam['payments'] = $payRows;
|
||||
}
|
||||
}
|
||||
unset($fam);
|
||||
|
||||
$data['families'] = $families;
|
||||
|
||||
// Back-compat: also expose guardians of first family
|
||||
if (!empty($families)) {
|
||||
$familyId = (int) $families[0]['id'];
|
||||
$data['guardians'] = DB::table('family_guardians as fg')
|
||||
->select(
|
||||
'u.id', 'u.firstname', 'u.lastname', 'u.email',
|
||||
'fg.relation', 'fg.is_primary', 'fg.receive_emails'
|
||||
)
|
||||
->join('users as u', 'u.id', '=', 'fg.user_id')
|
||||
->where('fg.family_id', $familyId)
|
||||
->orderByDesc('fg.is_primary')
|
||||
->orderBy('u.lastname')
|
||||
->orderBy('u.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success($data, 'Family admin data retrieved successfully');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Family admin error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to retrieve family admin data', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function search()
|
||||
{
|
||||
$q = trim((string) ($this->request->getGet('q') ?? ''));
|
||||
if ($q === '') {
|
||||
return $this->success(['items' => []], 'Search completed');
|
||||
}
|
||||
|
||||
try {
|
||||
$qs = '%' . $q . '%';
|
||||
|
||||
// Students suggestions
|
||||
$students = DB::table('students')
|
||||
->select('id', 'firstname', 'lastname')
|
||||
->whereRaw("CONCAT_WS(' ', firstname, lastname) LIKE ?", [$qs])
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->limit(8)
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
// Parents/Guardians suggestions
|
||||
$guardians = DB::table('users')
|
||||
->select('id', 'firstname', 'lastname', 'email', 'cellphone')
|
||||
->where(function ($query) use ($qs) {
|
||||
$query->whereRaw("CONCAT_WS(' ', firstname, lastname) LIKE ?", [$qs])
|
||||
->orWhere('email', 'like', $qs)
|
||||
->orWhere('cellphone', 'like', $qs);
|
||||
})
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->limit(8)
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$items = [];
|
||||
foreach ($students as $s) {
|
||||
$items[] = [
|
||||
'type' => 'student',
|
||||
'id' => (int) $s['id'],
|
||||
'label' => trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? '')),
|
||||
'sub' => 'Student',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($guardians as $g) {
|
||||
$items[] = [
|
||||
'type' => 'guardian',
|
||||
'id' => (int) $g['id'],
|
||||
'label' => trim(($g['firstname'] ?? '') . ' ' . ($g['lastname'] ?? '')),
|
||||
'sub' => trim(($g['email'] ?? '') . ' ' . ($g['cellphone'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success(['items' => $items], 'Search completed successfully');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Family admin search error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to perform search', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function card()
|
||||
{
|
||||
$studentId = (int) ($this->request->getGet('student_id') ?? 0);
|
||||
$guardianId = (int) ($this->request->getGet('guardian_id') ?? 0);
|
||||
$familyId = (int) ($this->request->getGet('family_id') ?? 0);
|
||||
|
||||
try {
|
||||
if (!$familyId) {
|
||||
if ($studentId) {
|
||||
$row = DB::table('family_students as fs')
|
||||
->select('f.id')
|
||||
->join('families as f', 'f.id', '=', 'fs.family_id')
|
||||
->where('fs.student_id', $studentId)
|
||||
->orderByDesc('fs.is_primary_home')
|
||||
->orderBy('f.household_name')
|
||||
->first();
|
||||
|
||||
if ($row && !empty($row->id)) {
|
||||
$familyId = (int) $row->id;
|
||||
}
|
||||
} elseif ($guardianId) {
|
||||
// Try via guardians link
|
||||
$row = DB::table('family_guardians as fg')
|
||||
->select('f.id')
|
||||
->join('families as f', 'f.id', '=', 'fg.family_id')
|
||||
->where('fg.user_id', $guardianId)
|
||||
->orderBy('f.household_name')
|
||||
->first();
|
||||
|
||||
if ($row && !empty($row->id)) {
|
||||
$familyId = (int) $row->id;
|
||||
} else {
|
||||
// Fallback via students.parent_id → family_students
|
||||
$row = DB::table('students as s')
|
||||
->select('f.id')
|
||||
->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')
|
||||
->first();
|
||||
|
||||
if ($row && !empty($row->id)) {
|
||||
$familyId = (int) $row->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$familyId) {
|
||||
return $this->respondError('Family not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$family = DB::table('families as f')
|
||||
->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'
|
||||
)
|
||||
->where('f.id', $familyId)
|
||||
->first();
|
||||
|
||||
if (!$family) {
|
||||
return $this->respondError('Family not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$family = (array) $family;
|
||||
|
||||
// Guardians
|
||||
$guardians = DB::table('family_guardians as fg')
|
||||
->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'
|
||||
)
|
||||
->join('users as u', 'u.id', '=', 'fg.user_id')
|
||||
->where('fg.family_id', $familyId)
|
||||
->orderByDesc('fg.is_primary')
|
||||
->orderBy('u.lastname')
|
||||
->orderBy('u.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$family['guardians'] = $guardians;
|
||||
|
||||
// Students
|
||||
$studentsRows = DB::table('family_students as fs')
|
||||
->select('s.id', 's.firstname', 's.lastname')
|
||||
->join('students as s', 's.id', '=', 'fs.student_id')
|
||||
->where('fs.family_id', $familyId)
|
||||
->orderBy('s.lastname')
|
||||
->orderBy('s.firstname')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
if (!empty($studentsRows)) {
|
||||
foreach ($studentsRows as &$sr) {
|
||||
$sid = (int) ($sr['id'] ?? 0);
|
||||
$sr['grade'] = $sid && $this->schoolYear
|
||||
? (string) ($this->studentClass->getClassSectionsByStudentId($sid, $this->schoolYear) ?? '')
|
||||
: '';
|
||||
}
|
||||
unset($sr);
|
||||
}
|
||||
|
||||
$family['students'] = $studentsRows;
|
||||
|
||||
// Financials
|
||||
$parentIds = array_map(static fn($g) => (int) ($g['user_id'] ?? 0), $guardians);
|
||||
$parentIds = array_values(array_filter($parentIds));
|
||||
|
||||
$family['invoices'] = [];
|
||||
$family['payments'] = [];
|
||||
$family['finance_summary'] = [
|
||||
'invoices_count' => 0,
|
||||
'total_amount' => 0.0,
|
||||
'paid_amount' => 0.0,
|
||||
'balance' => 0.0,
|
||||
];
|
||||
|
||||
// Emergency contacts
|
||||
$family['emergency_contacts'] = [];
|
||||
|
||||
if (!empty($parentIds)) {
|
||||
// Map guardian name by user_id
|
||||
$gmap = [];
|
||||
foreach ($guardians as $g) {
|
||||
$gid = (int) ($g['user_id'] ?? 0);
|
||||
if ($gid > 0) {
|
||||
$gmap[$gid] = trim(($g['firstname'] ?? '') . ' ' . ($g['lastname'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$ecRows = 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)
|
||||
->orderByDesc('updated_at')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
if (!empty($ecRows)) {
|
||||
foreach ($ecRows as &$ec) {
|
||||
$pid = (int) ($ec['parent_id'] ?? 0);
|
||||
$ec['parent_label'] = $gmap[$pid] ?? ('Parent #' . $pid);
|
||||
}
|
||||
unset($ec);
|
||||
$family['emergency_contacts'] = $ecRows;
|
||||
}
|
||||
|
||||
// Invoices
|
||||
$invRows = DB::table('invoices')
|
||||
->select(
|
||||
'id', 'parent_id', 'invoice_number', 'status', 'total_amount',
|
||||
'paid_amount', 'balance', 'issue_date', 'due_date'
|
||||
)
|
||||
->whereIn('parent_id', $parentIds)
|
||||
->orderByDesc('issue_date')
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$family['invoices'] = $invRows;
|
||||
|
||||
$invoiceMap = [];
|
||||
foreach ($invRows as $ir) {
|
||||
$invoiceMap[(int) $ir['id']] = (string) ($ir['invoice_number'] ?? '');
|
||||
}
|
||||
$family['invoice_map'] = $invoiceMap;
|
||||
|
||||
foreach ($invRows as $ir) {
|
||||
$family['finance_summary']['invoices_count']++;
|
||||
$family['finance_summary']['total_amount'] += (float) ($ir['total_amount'] ?? 0);
|
||||
$family['finance_summary']['paid_amount'] += (float) ($ir['paid_amount'] ?? 0);
|
||||
$family['finance_summary']['balance'] += (float) ($ir['balance'] ?? 0);
|
||||
}
|
||||
|
||||
// Payments
|
||||
$payRows = DB::table('payments')
|
||||
->select(
|
||||
'id', 'parent_id', 'invoice_id', 'paid_amount', 'balance',
|
||||
'payment_method', 'payment_date', 'status'
|
||||
)
|
||||
->whereIn('parent_id', $parentIds)
|
||||
->orderByDesc('payment_date')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
$family['payments'] = $payRows;
|
||||
}
|
||||
|
||||
return $this->success(['family' => $family], 'Family card retrieved successfully');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Family card error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to retrieve family card', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user