add projet

This commit is contained in:
root
2026-03-05 12:29:37 -05:00
parent 8d1eef8ba8
commit 23b7db1107
9109 changed files with 1106501 additions and 73 deletions
+525
View File
@@ -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);
}
}
}