add projet
This commit is contained in:
+519
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceDay;
|
||||
use App\Models\Calendar;
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\StaffAttendance;
|
||||
use App\Models\StudentClass;
|
||||
use App\Models\Student;
|
||||
use App\Models\TeacherClass;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AttendanceController extends BaseApiController
|
||||
{
|
||||
protected AttendanceData $attendance;
|
||||
protected StudentClass $studentClass;
|
||||
protected ClassSection $classSection;
|
||||
protected Configuration $config;
|
||||
protected TeacherClass $teacherClass;
|
||||
protected StaffAttendance $staffAttendance;
|
||||
protected AttendanceDay $attendanceDay;
|
||||
protected Student $student;
|
||||
protected Calendar $calendar;
|
||||
protected User $user;
|
||||
protected string $schoolYear;
|
||||
protected string $semester;
|
||||
protected int $enableAttendance;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->attendance = model(AttendanceData::class);
|
||||
$this->studentClass = model(StudentClass::class);
|
||||
$this->classSection = model(ClassSection::class);
|
||||
$this->config = model(Configuration::class);
|
||||
$this->teacherClass = model(TeacherClass::class);
|
||||
$this->staffAttendance = model(StaffAttendance::class);
|
||||
$this->attendanceDay = model(AttendanceDay::class);
|
||||
$this->student = model(Student::class);
|
||||
$this->calendar = model(Calendar::class);
|
||||
$this->user = model(User::class);
|
||||
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
||||
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
||||
$this->enableAttendance = (int) ($this->config->getConfig('enable_attendance') ?? 0);
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
$perPage = min(100, (int) ($this->request->getGet('per_page') ?? 20));
|
||||
$studentId = $this->request->getGet('student_id');
|
||||
$date = $this->request->getGet('date');
|
||||
|
||||
$builder = $this->attendance;
|
||||
|
||||
if ($studentId) {
|
||||
$builder = $builder->where('student_id', $studentId);
|
||||
}
|
||||
|
||||
if ($date) {
|
||||
$builder = $builder->where('date', $date);
|
||||
}
|
||||
|
||||
$data = $builder->paginate($perPage, 'default', $page);
|
||||
$pager = $builder->pager;
|
||||
|
||||
return $this->respondSuccess([
|
||||
'data' => $data,
|
||||
'pagination' => [
|
||||
'current_page' => $pager->getCurrentPage(),
|
||||
'per_page' => $pager->getPerPage(),
|
||||
'total' => $pager->getTotal(),
|
||||
'total_pages' => $pager->getPageCount(),
|
||||
],
|
||||
], 'Attendance records retrieved successfully');
|
||||
}
|
||||
|
||||
public function show($id = null)
|
||||
{
|
||||
$record = $this->attendance->find($id);
|
||||
|
||||
if (!$record) {
|
||||
return $this->respondError('Attendance record not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->respondSuccess($record, 'Attendance record retrieved successfully');
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$data = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'student_id' => 'required|integer',
|
||||
'date' => 'required|valid_date[Y-m-d]',
|
||||
'status' => 'required|in_list[present,absent,late]',
|
||||
'notes' => 'permit_empty|string',
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->respondValidationError($this->validator->getErrors());
|
||||
}
|
||||
|
||||
$existing = $this->attendance
|
||||
->where('student_id', $data['student_id'])
|
||||
->where('date', $data['date'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return $this->respondError('Attendance already recorded for this student and date', 409);
|
||||
}
|
||||
|
||||
$this->attendance->insert($data);
|
||||
$id = $this->attendance->getInsertID();
|
||||
|
||||
return $this->respondCreated([
|
||||
'id' => $id,
|
||||
'data' => $data,
|
||||
], 'Attendance record created successfully');
|
||||
}
|
||||
|
||||
public function update($id = null)
|
||||
{
|
||||
$record = $this->attendance->find($id);
|
||||
|
||||
if (!$record) {
|
||||
return $this->respondError('Attendance record not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'status' => 'permit_empty|in_list[present,absent,late]',
|
||||
'notes' => 'permit_empty|string',
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->respondValidationError($this->validator->getErrors());
|
||||
}
|
||||
|
||||
$this->attendance->update($id, $data);
|
||||
|
||||
return $this->respondSuccess($this->attendance->find($id), 'Attendance record updated successfully');
|
||||
}
|
||||
|
||||
public function delete($id = null)
|
||||
{
|
||||
$record = $this->attendance->find($id);
|
||||
|
||||
if (!$record) {
|
||||
return $this->respondError('Attendance record not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->attendance->delete($id);
|
||||
return $this->respondDeleted(null, 'Attendance record deleted successfully');
|
||||
}
|
||||
|
||||
public function student($id = null)
|
||||
{
|
||||
$records = $this->attendance
|
||||
->where('student_id', $id)
|
||||
->orderBy('date', 'DESC')
|
||||
->findAll();
|
||||
|
||||
return $this->respondSuccess($records, 'Student attendance retrieved successfully');
|
||||
}
|
||||
|
||||
public function classRoster($classSectionId)
|
||||
{
|
||||
$date = $this->request->getGet('date') ?: date('Y-m-d');
|
||||
$db = $this->getDatabaseConnection();
|
||||
|
||||
$rows = $db->table('student_class sc')
|
||||
->select('s.id AS student_id, s.firstname, s.lastname')
|
||||
->join('students s', 's.id = sc.student_id', 'inner')
|
||||
->where('sc.class_section_id', (int)$classSectionId)
|
||||
->where('sc.school_year', $this->schoolYear)
|
||||
->orderBy('s.lastname', 'ASC')->orderBy('s.firstname', 'ASC')
|
||||
->get()->getResultArray();
|
||||
|
||||
$list = [];
|
||||
foreach ($rows as $r) {
|
||||
$att = $this->attendance->getAttendance((int)$r['student_id'], (int)$classSectionId, $date);
|
||||
$list[] = [
|
||||
'student_id' => (int)$r['student_id'],
|
||||
'firstname' => $r['firstname'],
|
||||
'lastname' => $r['lastname'],
|
||||
'status' => $att['status'] ?? null,
|
||||
'reason' => $att['reason'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'class_section_id' => (int)$classSectionId,
|
||||
'date' => $date,
|
||||
'students' => $list,
|
||||
], 'Class roster attendance retrieved');
|
||||
}
|
||||
|
||||
public function recordClass($classSectionId)
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$date = isset($payload['date']) ? trim((string)$payload['date']) : '';
|
||||
$records = isset($payload['records']) && is_array($payload['records']) ? $payload['records'] : [];
|
||||
|
||||
if ($date === '' || empty($records)) {
|
||||
return $this->respondError('Invalid payload: date and records are required.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$allowedStatuses = ['present', 'absent', 'late'];
|
||||
$now = utc_now();
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($records as $record) {
|
||||
$studentId = (int) ($record['student_id'] ?? 0);
|
||||
$status = strtolower(trim((string) ($record['status'] ?? '')));
|
||||
$reason = isset($record['reason']) ? trim((string) $record['reason']) : null;
|
||||
|
||||
if ($studentId <= 0 || !in_array($status, $allowedStatuses, true)) {
|
||||
DB::rollBack();
|
||||
return $this->respondError('Invalid record provided.', Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$existing = $this->attendance
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', (int)$classSectionId)
|
||||
->where('date', $date)
|
||||
->first();
|
||||
|
||||
$payload = [
|
||||
'class_section_id' => (int) $classSectionId,
|
||||
'student_id' => $studentId,
|
||||
'status' => $status,
|
||||
'reason' => $reason ?: null,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
'date' => $date,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if ($existing) {
|
||||
$payload['modified_by'] = $this->getCurrentUserId();
|
||||
$this->attendance->update($existing['id'], $payload);
|
||||
} else {
|
||||
$payload['created_at'] = $now;
|
||||
$payload['modified_by'] = $this->getCurrentUserId();
|
||||
$this->attendance->insert($payload);
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
return $this->respondError('Failed to record attendance.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return $this->success(null, 'Class attendance recorded successfully');
|
||||
}
|
||||
|
||||
public function teacherSectionGrid(): JsonResponse
|
||||
{
|
||||
$semester = (string) ($this->request->get('semester') ?? $this->semester);
|
||||
$schoolYear = (string) ($this->request->get('school_year') ?? $this->schoolYear);
|
||||
$date = (string) ($this->request->get('date') ?? local_date(utc_now(), 'Y-m-d'));
|
||||
$sectionCode = (int) ($this->request->get('class_section_id') ?? 0);
|
||||
|
||||
$assignedBySection = (array) ($this->teacherClass->assignedBySectionForTerm($semester, $schoolYear) ?? []);
|
||||
$sectionCodes = array_keys($assignedBySection);
|
||||
$labels = [];
|
||||
|
||||
if (!empty($sectionCodes)) {
|
||||
$labelRows = $this->classSection
|
||||
->select('class_section_id, class_section_name')
|
||||
->whereIn('class_section_id', $sectionCodes)
|
||||
->orderBy('class_section_id', 'ASC')
|
||||
->findAll();
|
||||
|
||||
foreach ($labelRows as $row) {
|
||||
$code = (int) ($row['class_section_id'] ?? 0);
|
||||
if ($code <= 0) continue;
|
||||
$name = trim((string) ($row['class_section_name'] ?? ''));
|
||||
$labels[$code] = $name !== '' ? $name : ('Section #' . $code);
|
||||
}
|
||||
|
||||
foreach ($sectionCodes as $code) {
|
||||
if (!isset($labels[$code])) {
|
||||
$labels[$code] = 'Section #' . $code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$grid = [];
|
||||
$locked = false;
|
||||
|
||||
if ($sectionCode > 0) {
|
||||
$assigned = (array) ($this->teacherClass->assignedForSectionTerm($sectionCode, $semester, $schoolYear) ?? []);
|
||||
$teacherIds = array_map(static fn($row) => (int) ($row['teacher_id'] ?? 0), $assigned);
|
||||
$teacherIds = array_values(array_filter($teacherIds));
|
||||
|
||||
$statusMap = [];
|
||||
if (!empty($teacherIds)) {
|
||||
$rows = DB::table('staff_attendance as sa')
|
||||
->select('sa.user_id', 'sa.status', 'sa.reason')
|
||||
->where('sa.semester', $semester)
|
||||
->where('sa.school_year', $schoolYear)
|
||||
->where('sa.date', $date)
|
||||
->whereIn('sa.user_id', $teacherIds)
|
||||
->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$statusMap[(int) $row->user_id] = [
|
||||
'status' => $row->status ?? null,
|
||||
'reason' => $row->reason ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($assigned as $teacherRow) {
|
||||
$tid = (int) ($teacherRow['teacher_id'] ?? 0);
|
||||
$name = trim(($teacherRow['firstname'] ?? '') . ' ' . ($teacherRow['lastname'] ?? ''));
|
||||
$name = $name !== '' ? $name : 'User #' . $tid;
|
||||
$posRaw = strtolower((string) ($teacherRow['position'] ?? 'main'));
|
||||
$pos = in_array($posRaw, ['main', 'ta'], true) ? $posRaw : 'main';
|
||||
$cell = $statusMap[$tid] ?? ['status' => null, 'reason' => null];
|
||||
|
||||
$grid[] = [
|
||||
'teacher_id' => $tid,
|
||||
'name' => $name,
|
||||
'position' => $pos,
|
||||
'status' => $cell['status'],
|
||||
'reason' => $cell['reason'],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$locked = (bool) $this->attendanceDay->isFinalized($sectionCode, $date, $semester, $schoolYear);
|
||||
} catch (\Throwable $e) {
|
||||
$locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'date' => $date,
|
||||
'class_section_id' => $sectionCode,
|
||||
'sections' => $labels,
|
||||
'grid' => $grid,
|
||||
'locked' => $locked,
|
||||
], 'Teacher attendance grid retrieved');
|
||||
}
|
||||
|
||||
public function teacherUpdateData(): JsonResponse
|
||||
{
|
||||
$teacherId = (int) ($this->request->get('teacher_id') ?? $this->getCurrentUserId() ?? 0);
|
||||
if ($teacherId <= 0) {
|
||||
return $this->respondError('Teacher not found or unauthorized.', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$classSectionParam = (int) ($this->request->get('class_section_id') ?? 0);
|
||||
if ($classSectionParam <= 0) {
|
||||
$classSection = $this->teacherClass->getClassSectionIdByUserId($teacherId);
|
||||
if (empty($classSection)) {
|
||||
return $this->respondError('No class assigned to this teacher.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
$classSectionId = (int) ($classSection['class_section_id'] ?? 0);
|
||||
} else {
|
||||
$classSectionId = $classSectionParam;
|
||||
}
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return $this->respondError('Invalid class section.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$semester = (string) ($this->request->get('semester') ?? $this->semester);
|
||||
$schoolYear = (string) ($this->request->get('school_year') ?? $this->schoolYear);
|
||||
$today = local_date(utc_now(), 'Y-m-d');
|
||||
|
||||
$recentDatesRows = $this->attendance
|
||||
->select('date')
|
||||
->distinct()
|
||||
->orderBy('date', 'DESC')
|
||||
->limit(3)
|
||||
->findAll();
|
||||
|
||||
$recentDates = array_map(static fn($row) => (string) ($row['date'] ?? ''), $recentDatesRows);
|
||||
$recentDates = array_values(array_filter($recentDates, static fn($d) => $d !== ''));
|
||||
$allDates = array_values(array_unique(array_merge([$today], $recentDates)));
|
||||
|
||||
$dateSlots = [
|
||||
'day_before_before_yesterday' => $allDates[3] ?? null,
|
||||
'day_before_yesterday' => $allDates[2] ?? null,
|
||||
'yesterday' => $allDates[1] ?? null,
|
||||
'today' => $allDates[0] ?? $today,
|
||||
];
|
||||
|
||||
$teacher = $this->user->find($teacherId);
|
||||
$teacherName = $teacher ? trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? '')) : 'Teacher';
|
||||
|
||||
$students = (array) ($this->student->getByClassAndYear($classSectionId, $schoolYear) ?? []);
|
||||
$studentIds = array_map(static fn($row) => (int) ($row['id'] ?? 0), $students);
|
||||
$studentIds = array_values(array_filter($studentIds));
|
||||
|
||||
$studentAttendance = [];
|
||||
if (!empty($studentIds) && !empty($allDates)) {
|
||||
$rows = $this->attendance
|
||||
->newQuery()
|
||||
->whereIn('student_id', $studentIds)
|
||||
->whereIn('date', $allDates)
|
||||
->get()
|
||||
->map(fn($row) => (array) $row)
|
||||
->toArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$studentAttendance[(int) $row['student_id']][$row['date']] = $row['status'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$studentsPayload = [];
|
||||
foreach ($students as $student) {
|
||||
$sid = (int) ($student['id'] ?? 0);
|
||||
$slots = [];
|
||||
foreach ($dateSlots as $key => $date) {
|
||||
$slots[$key] = $date ? ($studentAttendance[$sid][$date] ?? 'N/A') : 'N/A';
|
||||
}
|
||||
|
||||
$studentsPayload[] = [
|
||||
'id' => $sid,
|
||||
'firstname' => $student['firstname'] ?? '',
|
||||
'lastname' => $student['lastname'] ?? '',
|
||||
'school_id' => $student['school_id'] ?? '',
|
||||
'statuses' => $slots,
|
||||
];
|
||||
}
|
||||
|
||||
$assignedTeachers = (array) ($this->teacherClass->assignedForSectionTerm($classSectionId, $semester, $schoolYear) ?? []);
|
||||
$teacherIds = array_map(static fn($row) => (int) ($row['teacher_id'] ?? 0), $assignedTeachers);
|
||||
$teacherIds = array_values(array_filter($teacherIds));
|
||||
|
||||
$teacherStatusMap = [];
|
||||
if (!empty($teacherIds) && !empty($allDates)) {
|
||||
$rows = DB::table('staff_attendance')
|
||||
->select('user_id', 'date', 'status')
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->whereIn('user_id', $teacherIds)
|
||||
->whereIn('date', $allDates)
|
||||
->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$teacherStatusMap[(int) $row->user_id][$row->date] = $row->status ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$teacherRows = [];
|
||||
foreach ($assignedTeachers as $row) {
|
||||
$tid = (int) ($row['teacher_id'] ?? 0);
|
||||
$posRaw = strtolower((string) ($row['position'] ?? 'main'));
|
||||
$position = in_array($posRaw, ['main', 'ta'], true) ? $posRaw : 'main';
|
||||
$name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = 'User #' . $tid;
|
||||
}
|
||||
|
||||
$slots = [];
|
||||
foreach ($dateSlots as $key => $date) {
|
||||
$slots[$key] = $date ? ($teacherStatusMap[$tid][$date] ?? 'N/A') : 'N/A';
|
||||
}
|
||||
|
||||
$teacherRows[] = [
|
||||
'teacher_id' => $tid,
|
||||
'name' => $name,
|
||||
'position' => $position,
|
||||
'statuses' => $slots,
|
||||
];
|
||||
}
|
||||
|
||||
$wantedDates = array_values(array_filter(array_unique(array_filter($dateSlots))));
|
||||
$noSchool = [];
|
||||
if (!empty($wantedDates)) {
|
||||
$events = $this->calendar
|
||||
->where('no_school', 1)
|
||||
->whereIn('date', $wantedDates)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->findAll();
|
||||
|
||||
foreach ($events as $event) {
|
||||
$d = (string) ($event['date'] ?? '');
|
||||
if ($d === '') continue;
|
||||
$noSchool[$d] = [
|
||||
'title' => $event['title'] ?? 'No School',
|
||||
'description' => $event['description'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'teacher' => [
|
||||
'id' => $teacherId,
|
||||
'name' => $teacherName,
|
||||
],
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'dates' => $dateSlots,
|
||||
'students' => $studentsPayload,
|
||||
'teachers' => $teacherRows,
|
||||
'no_school_days' => $noSchool,
|
||||
'enable_attendance'=> $this->enableAttendance,
|
||||
], 'Teacher attendance update data retrieved');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user