<?php namespace App\Http\Controllers; use App\Models\Timetable; use App\Repositories\ClassSchool\ClassSchoolInterface; use App\Repositories\ClassSection\ClassSectionInterface; use App\Repositories\Medium\MediumInterface; use App\Repositories\SchoolSetting\SchoolSettingInterface; use App\Repositories\Subject\SubjectInterface; use App\Repositories\SubjectTeacher\SubjectTeacherInterface; use App\Repositories\Timetable\TimetableInterface; use App\Repositories\User\UserInterface; use App\Services\BootstrapTableService; use App\Services\CachingService; use App\Services\ResponseService; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Throwable; class TimetableController extends Controller { private SubjectTeacherInterface $subjectTeacher; private SubjectInterface $subject; private TimetableInterface $timetable; private ClassSectionInterface $classSection; private UserInterface $user; private SchoolSettingInterface $schoolSettings; private CachingService $cache; private ClassSchoolInterface $class; private MediumInterface $medium; public function __construct(SubjectTeacherInterface $subjectTeacher, SubjectInterface $subject, TimetableInterface $timetable, ClassSectionInterface $classSection, UserInterface $user, SchoolSettingInterface $schoolSettings, CachingService $cache, ClassSchoolInterface $class, MediumInterface $medium) { $this->subjectTeacher = $subjectTeacher; $this->subject = $subject; $this->timetable = $timetable; $this->classSection = $classSection; $this->user = $user; $this->schoolSettings = $schoolSettings; $this->cache = $cache; $this->class = $class; $this->medium = $medium; } public function index() { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-list'); // Get Timetable Settings Data $timetableData = $this->schoolSettings->getBulkData([ 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ]); // Convert Timetable Duration time to number $timetableData['timetable_duration'] = Carbon::parse($timetableData['timetable_duration'] ?? "00:00:00")->diffInMinutes(Carbon::parse('00:00:00')); $classes = $this->class->builder()->with('stream', 'shift')->get()->pluck('full_name', 'id'); $mediums = $this->medium->builder()->pluck('name', 'id'); return view('timetable.index', compact('timetableData', 'classes', 'mediums')); } public function store(Request $request) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect(['timetable-create']); $request->validate([ 'subject_teacher_id' => 'nullable|numeric', 'class_section_id' => 'required|numeric', 'subject_id' => 'nullable|numeric', 'start_time' => 'required', 'end_time' => 'required', 'day' => 'required', 'note' => 'nullable', ]); try { $sessionYearId = $this->cache->getSessionYear()->id; $classSection = $this->classSection->findById($request->class_section_id, ['*'], ['class.shift']); if ($classSection->class->shift) { $timetable_start_time = Carbon::parse($classSection->class->shift->getRawOriginal('start_time'))->format('H:i:s'); $timetable_end_time = Carbon::parse($classSection->class->shift->getRawOriginal('end_time'))->format('H:i:s'); } else { $schoolSettings = $this->cache->getSchoolSettings(); $timetable_start_time = Carbon::parse($schoolSettings['timetable_start_time'])->format('H:i:s'); $timetable_end_time = Carbon::parse($schoolSettings['timetable_end_time'])->format('H:i:s'); } $start_time = Carbon::parse($request->start_time)->format('H:i:s'); $end_time = Carbon::parse($request->end_time)->format('H:i:s'); if ($start_time < $timetable_start_time || $start_time >= $timetable_end_time) { ResponseService::errorResponse(__('Please select a valid time within school hours')); } if ($end_time > $timetable_end_time) { $end_time = $timetable_end_time; $request->merge(['end_time' => $end_time]); } if ($request->note == null && ($request->subject_teacher_id == null || $request->subject_teacher_id == '')) { ResponseService::errorResponse(__('please_assign_a_teacher_to_the_subject_before_scheduling')); } // Check for teacher conflicts if ($request->subject_teacher_id) { // First get the teacher_id from the subject_teacher record $subjectTeacher = $this->subjectTeacher->findById($request->subject_teacher_id); if ($subjectTeacher) { $teacher_id = $subjectTeacher->teacher_id; // Now check for conflicts using the teacher_id $conflictingTimetable = $this->timetable->builder() ->where('session_year_id', $sessionYearId) ->where('day', $request->day) ->whereHas('subject_teacher', function ($q) use ($teacher_id) { $q->where('teacher_id', $teacher_id); }) ->where(function ($query) use ($start_time, $end_time) { $query->where('start_time', '<', $end_time) ->where('end_time', '>', $start_time); }) ->first(); if ($conflictingTimetable) { ResponseService::errorResponse(__('teacher_is_already_scheduled_for_another_class_at_this_time')); } } } $timetable = $this->timetable->create([ ...$request->all(), 'type' => (!empty($request->subject_id)) ? "Lecture" : "Break", 'session_year_id' => $sessionYearId ]); ResponseService::successResponse('Data Stored Successfully', $timetable); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function edit($classSectionID) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-edit'); $currentSemester = $this->cache->getDefaultSemesterData(); $sessionYearId = $this->cache->getSessionYear()->id; $classSection = $this->classSection->findById($classSectionID, ['*'], ['class', 'class.stream', 'class.shift', 'section', 'medium']); $subjectTeachers = $this->subjectTeacher->builder() ->where('session_year_id', $sessionYearId) ->with([ 'subject:id,name,type,bg_color', 'teacher:id,first_name,last_name', 'class_subject' ]) ->whereHas('class_section', function ($q) use ($classSectionID) { $q->where('id', $classSectionID); }) ->whereHas('class_subject', function ($q) { $q->whereNull('deleted_at'); }) ->orderBy('subject_id', 'ASC') ->CurrentSemesterData() ->get(); $subjectWithoutTeacherAssigned = $this->subject->builder() ->with([ 'class_subjects' => function ($query) use ($classSection, $sessionYearId) { $query->where('class_id', $classSection->class_id) ->where('session_year_id', $sessionYearId) ->CurrentSemesterData(); } ]) ->whereHas('class_subjects', function ($q) use ($classSection, $sessionYearId) { $q->where('class_id', $classSection->class_id) ->where('session_year_id', $sessionYearId) ->CurrentSemesterData(); }) ->select(['id', 'name', 'type', 'bg_color']) ->whereNotIn('id', $subjectTeachers->pluck('subject_id')) ->get(); $timetables = $this->timetable->builder() ->where('class_section_id', $classSectionID) ->where('session_year_id', $sessionYearId) ->with([ 'teacher:users.id,first_name,last_name', 'subject:id,name,type,bg_color', 'subject.class_subjects', 'subject_teacher.class_subject' ]) ->CurrentSemesterData() ->get(); // Get Timetable Settings Data $timetableSettingsData = $this->schoolSettings->getBulkData([ 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ]); if ($classSection->class->shift) { $timetableSettingsData['timetable_start_time'] = $classSection->class->shift->getRawOriginal('start_time'); $timetableSettingsData['timetable_end_time'] = $classSection->class->shift->getRawOriginal('end_time'); } return view('timetable.edit', compact('subjectTeachers', 'subjectWithoutTeacherAssigned', 'classSection', 'timetables', 'timetableSettingsData', 'currentSemester')); } public function update(Request $request, $id) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect(['timetable-edit']); $request->validate([ 'start_time' => 'required', 'end_time' => 'required', 'day' => 'required', ]); $start_time = $request->start_time; $end_time = $request->end_time; $sessionYearId = $this->cache->getSessionYear()->id; // check if teacher is already scheduled for another class at this time $timetable = $this->timetable->findById($id); $day = $request->day; $start_time = Carbon::parse($request->start_time)->format('H:i:s'); $end_time = Carbon::parse($request->end_time)->format('H:i:s'); if ($timetable->subject_teacher_id) { // First get the teacher_id from the subject_teacher record $subjectTeacher = $this->subjectTeacher->findById($timetable->subject_teacher_id); if ($subjectTeacher) { $teacher_id = $subjectTeacher->teacher_id; // Now check for conflicts using the teacher_id $conflictingTimetable = $this->timetable->builder() ->where('session_year_id', $sessionYearId) ->where('id', '!=', $id) // Exclude current record ->where('day', $request->day) ->whereHas('subject_teacher', function ($q) use ($teacher_id) { $q->where('teacher_id', $teacher_id); }) ->where(function ($query) use ($request) { $query->where('start_time', '<', $request->end_time) ->where('end_time', '>', $request->start_time); }) ->first(); if ($conflictingTimetable) { ResponseService::errorResponse(__('teacher_is_already_scheduled_for_another_class_at_this_time')); } } } $start_time = Carbon::parse($request->start_time)->format('H:i:s'); $end_time = Carbon::parse($request->end_time)->format('H:i:s'); $classSection = $this->classSection->findById($timetable->class_section_id, ['*'], ['class.shift']); if ($classSection->class->shift) { $timetable_start_time = Carbon::parse($classSection->class->shift->getRawOriginal('start_time'))->format('H:i:s'); $timetable_end_time = Carbon::parse($classSection->class->shift->getRawOriginal('end_time'))->format('H:i:s'); } else { $schoolSettings = $this->cache->getSchoolSettings(); $timetable_start_time = Carbon::parse($schoolSettings['timetable_start_time'])->format('H:i:s'); $timetable_end_time = Carbon::parse($schoolSettings['timetable_end_time'])->format('H:i:s'); } try { if ($timetable_start_time <= $start_time && $timetable_end_time >= $end_time) { $this->timetable->updateOrCreate(['id' => $id,], $request->all()); ResponseService::successResponse('Data Stored Successfully'); } else if ($timetable_start_time <= $start_time && $start_time < $timetable_end_time) { // If start_time is valid but end_time is beyond school hours, clip it $end_time = $timetable_end_time; $request->merge(['end_time' => $end_time]); $this->timetable->updateOrCreate(['id' => $id,], $request->all()); ResponseService::successResponse('Data Stored Successfully'); } else { ResponseService::errorResponse(__('Please select a valid time within school hours')); } } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function show(Request $request) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-list'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $sessionYearId = $this->cache->getSessionYear()->id; $schoolSettings = $this->cache->getSchoolSettings([ 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ]); $sql = $this->classSection->builder()->with([ 'class:id,name,stream_id,shift_id', 'class.stream', 'class.shift', 'section:id,name', 'medium:id,name', 'timetable' => function ($query) use ($sessionYearId) { $query->where('session_year_id', $sessionYearId)->CurrentSemesterData()->with('subject:id,name,type'); } ]); if (!empty($request->search)) { $search = $request->search; $sql->where(function ($query) use ($search) { $query->orWhereHas('section', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); })->orWhereHas('medium', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); })->orWhereHas('class', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); })->orWhereHas('class.stream', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); })->orWhereHas('class.shift', function ($q) use ($search) { $q->where('name', 'LIKE', "%$search%"); }); }); } if (!empty($request->medium_id)) { $sql = $sql->where('medium_id', $request->medium_id); } if (!empty($request->class_id)) { $sql = $sql->where('class_id', $request->class_id); } if (!empty($request->section_id)) { $sql = $sql->where('section_id', $request->section_id); } if (!empty($request->medium_id)) { $sql = $sql->where('medium_id', $request->medium_id); } if (!empty($request->teacher_id)) { $sql = $sql->whereHas('class_teachers', function ($q) use ($request) { $q->where('teacher_id', $request->teacher_id); }); } if (!empty($request->subject_id)) { $sql = $sql->whereHas('subject_teachers', function ($q) use ($request) { $q->where('subject_id', $request->subject_id); }); } if (!empty($request->class_subject_id)) { $sql = $sql->whereHas('subject_teachers.class_subject', function ($q) use ($request) { $q->where('id', $request->class_subject_id); }); } if (!empty($showDeleted)) { $sql = $sql->onlyTrashed(); } $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = 1; foreach ($res as $row) { $operate = BootstrapTableService::editButton(route('timetable.edit', $row->id), false); $operate .= BootstrapTableService::button('fa fa-trash', '#', ['delete-class-timetable', 'btn-gradient-danger'], ['title' => trans("Delete Class Timetable"), 'data-id' => $row->id]); $tempRow = $row->toArray(); $timetable = $row->timetable->groupBy('day')->sortBy('start_time'); $tempRow['no'] = $no++; $tempRow['Monday'] = $timetable['Monday'] ?? []; $tempRow['Tuesday'] = $timetable['Tuesday'] ?? []; $tempRow['Wednesday'] = $timetable['Wednesday'] ?? []; $tempRow['Thursday'] = $timetable['Thursday'] ?? []; $tempRow['Friday'] = $timetable['Friday'] ?? []; $tempRow['Saturday'] = $timetable['Saturday'] ?? []; $tempRow['Sunday'] = $timetable['Sunday'] ?? []; $tempRow['operate'] = $operate; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } public function destroy($id) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenSendJson('timetable-delete'); try { $sessionYearId = $this->cache->getSessionYear()->id; Timetable::where('id', $id)->where('session_year_id', $sessionYearId)->delete(); ResponseService::successResponse('Data Deleted Successfully'); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function teacherIndex() { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-list'); // Get Timetable Settings Data $timetableSettingsData = $this->schoolSettings->getBulkData([ 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ]); return view('timetable.teacher.index', compact('timetableSettingsData')); } public function teacherList(Request $request) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-list'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'DESC'); $sessionYearId = $this->cache->getSessionYear()->id; $sql = $this->user->builder()->role('Teacher')->with([ 'timetable' => function ($query) use ($sessionYearId) { $query->where('timetables.session_year_id', $sessionYearId)->CurrentSemesterData()->with('subject:id,name,type', 'class_section.class', 'class_section.class.shift', 'class_section.class.stream', 'class_section.medium', 'class_section.section'); } ]); if (!empty($request->search)) { $search = $request->search; $sql->where(function ($query) use ($search) { $query->where('id', 'LIKE', "%$search%")->orwhereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'"); }); } if (!empty($request->class_id)) { $sql->whereHas('timetable.class_section.class', function ($q) use ($request) { $q->where('id', $request->class_id); }); } if (!empty($request->section_id)) { $sql->whereHas('timetable.class_section.section', function ($q) use ($request) { $q->where('id', $request->section_id); }); } if (!empty($request->subject_id)) { $sql->whereHas('timetable.subject', function ($q) use ($request) { $q->where('id', $request->subject_id); }); } if (!empty($request->teacher_id)) { $sql->where('id', $request->teacher_id); } if (!empty($request->status)) { $sql->where('status', $request->status); } if (!empty($request->role)) { $sql->where('role', $request->role); } if (!empty($request->created_at)) { $sql->whereDate('created_at', '=', $request->created_at); } $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $no = 1; foreach ($res as $row) { $operate = BootstrapTableService::button('fa fa-eye', route('timetable.teacher.show', $row->id), ['btn-gradient-success'], ['title' => "View Timetable"]); $tempRow = $row->toArray(); $timetable = $row->timetable->groupBy('day')->sortBy('start_time'); $tempRow['no'] = $no++; $tempRow['Monday'] = $timetable['Monday'] ?? []; $tempRow['Tuesday'] = $timetable['Tuesday'] ?? []; $tempRow['Wednesday'] = $timetable['Wednesday'] ?? []; $tempRow['Thursday'] = $timetable['Thursday'] ?? []; $tempRow['Friday'] = $timetable['Friday'] ?? []; $tempRow['Saturday'] = $timetable['Saturday'] ?? []; $tempRow['Sunday'] = $timetable['Sunday'] ?? []; $tempRow['operate'] = $operate; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } public function teacherShow($teacherID) { ResponseService::noFeatureThenRedirect('Timetable Management'); $sessionYearId = $this->cache->getSessionYear()->id; $teacher = $this->user->findById($teacherID, ['id', 'first_name', 'last_name']); $timetables = $this->timetable->builder()->whereHas('subject_teacher', function ($q) use ($teacherID) { $q->where('teacher_id', $teacherID); })->where('session_year_id', $sessionYearId)->with('subject:id,name,type,bg_color', 'class_section.class', 'class_section.section', 'class_section.medium', 'class_section.class.stream', 'class_section.class.shift')->get(); // Get Timetable Settings Data $timetableSettingsData = $this->schoolSettings->getBulkData([ 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ]); return view('timetable.teacher.view', compact('timetables', 'teacher', 'timetableSettingsData')); } public function updateTimetableSettings(Request $request) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenRedirect('timetable-list'); try { DB::beginTransaction(); $settings = array( 'timetable_start_time', 'timetable_end_time', 'timetable_duration' ); // $timeTableExistsBeforeStartTime = $this->timetable->builder()->where('start_time', '<', date('H:i:s', strtotime($request->time_table_start_time)))->get(); // if (!empty($timeTableExistsBeforeStartTime->toArray())) { // ResponseService::errorResponse("Updates are prohibited as there are pre-existing lectures scheduled before " . $request->time_table_start_time); // } // $timeTableExistsAfterEndTime = $this->timetable->builder()->where('end_time', '>', date('H:i:s', strtotime($request->time_table_end_time)))->get(); // if (!empty($timeTableExistsAfterEndTime->toArray())) { // ResponseService::errorResponse("Updates are prohibited as there are pre-existing lectures scheduled after " . $request->time_table_end_time); // } $data = array(); foreach ($settings as $row) { $data[] = [ "name" => $row, "data" => $row == 'timetable_duration' ? Carbon::createFromTimestampUTC($request->$row * 60)->format('H:i:s') : date("H:i:s", strtotime($request->$row)), "type" => 'time' ]; } $this->schoolSettings->upsert($data, ["name"], ["data", "type"]); $this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SETTINGS')); DB::commit(); ResponseService::successResponse('Data Updated Successfully'); } catch (Throwable $e) { DB::rollBack(); ResponseService::logErrorResponse($e, "Timetable Controller -> updateTimetableSettings"); ResponseService::errorResponse(); } } public function deleteClassTimetable($id) { ResponseService::noFeatureThenRedirect('Timetable Management'); ResponseService::noPermissionThenSendJson('timetable-delete'); try { $sessionYearId = $this->cache->getSessionYear()->id; $this->timetable->builder()->where('class_section_id', $id)->where('session_year_id', $sessionYearId)->delete(); ResponseService::successResponse('Data Deleted Successfully'); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } }