File "SemesterController.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/SemesterController.php
File size: 14.41 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace App\Http\Controllers;
use App\Models\ClassSubject;
use App\Models\ElectiveSubjectGroup;
use App\Models\Exam;
use App\Models\Timetable;
use App\Repositories\Semester\SemesterInterface;
use App\Rules\uniqueForSchool;
use App\Services\BootstrapTableService;
use App\Services\CachingService;
use App\Services\ResponseService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Throwable;
use Carbon\Carbon;
class SemesterController extends Controller
{
private SemesterInterface $semester;
private CachingService $cache;
public function __construct(SemesterInterface $semester, CachingService $cache)
{
$this->semester = $semester;
$this->cache = $cache;
}
public function index()
{
// dd($this->semester->builder()->get()); // Temporary debug line
ResponseService::noPermissionThenRedirect('semester-list');
$defaultSessionYear = $this->cache->getDefaultSessionYear();
return view('semester.index', compact('defaultSessionYear'));
}
public function store(Request $request)
{
ResponseService::noPermissionThenSendJson('semester-create');
$request->validate([
'name' => [
'required',
new uniqueForSchool('semesters', 'name')
],
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date',
]);
try {
// Check if dates overlap with existing semesters
$sessionYearId = $this->cache->getSessionYear()->id;
$checkSemester = $this->checkIfDatesOverlap($request->start_date, $request->end_date);
if ($checkSemester['error']) {
ResponseService::validationError($checkSemester['message'], $checkSemester['data']);
}
$defaultSessionYear = $this->cache->getSessionYear();
// Add check: The start and end date of the request must be between the current semester's dates
if ($defaultSessionYear) {
$defaultStart = Carbon::parse($defaultSessionYear->getRawOriginal('start_date'))->format('Y-m-d');
$defaultEnd = Carbon::parse($defaultSessionYear->getRawOriginal('end_date'))->format('Y-m-d');
$semesterStart = Carbon::parse($request->start_date);
$semesterEnd = Carbon::parse($request->end_date);
if (
$semesterStart->lt($defaultStart) ||
$semesterEnd->gt($defaultEnd)
) {
ResponseService::validationError(
'The semester start and end dates must be within the default session year\'s dates.',
[
'default_start_date' => $defaultStart,
'default_end_date' => $defaultEnd
]
);
}
}
$semester = $this->semester->create([
'name' => $request->name,
'start_date' => Carbon::createFromFormat('d-m-Y', $request->start_date)->format('Y-m-d'),
'end_date' => Carbon::createFromFormat('d-m-Y', $request->end_date)->format('Y-m-d'),
'school_id' => Auth::user()->school_id,
'session_year_id' => $sessionYearId
]);
$this->cache->removeSchoolCache(config("constants.CACHE.SCHOOL.GET_SEMESTERS_BY_SESSION_YEAR"));
ResponseService::successResponse('Data Stored Successfully');
} catch (Throwable $e) {
ResponseService::logErrorResponse($e, "Session Year Controller -> Store method");
ResponseService::errorResponse();
}
}
public function update($id, Request $request)
{
ResponseService::noPermissionThenSendJson('semester-edit');
$request->validate([
'name' => [
'required',
new uniqueForSchool('semesters', 'name', $id)
],
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date',
]);
try {
// Check if dates overlap with existing semesters
$checkSemester = $this->checkIfDatesOverlap($request->start_date, $request->end_date, $id);
$schoolSettings = $this->cache->getSchoolSettings();
$sessionYearId = $this->cache->getSessionYear()->id;
if ($checkSemester['error']) {
ResponseService::validationError($checkSemester['message'], $checkSemester['data']);
}
$defaultSessionYear = $this->cache->getSessionYear();
// Add check: The start and end date of the request must be between the current session year's dates
if ($defaultSessionYear) {
$defaultStart = Carbon::parse($defaultSessionYear->getRawOriginal('start_date'))->format('Y-m-d');
$defaultEnd = Carbon::parse($defaultSessionYear->getRawOriginal('end_date'))->format('Y-m-d');
$semesterStart = Carbon::parse($request->start_date);
$semesterEnd = Carbon::parse($request->end_date);
if (
$semesterStart->lt($defaultStart) ||
$semesterEnd->gt($defaultEnd)
) {
ResponseService::validationError(
'The semester start and end dates must be within the default session year\'s dates.',
[
'default_start_date' => $defaultStart,
'default_end_date' => $defaultEnd
]
);
}
}
$this->semester->update($id, [
'name' => $request->name,
'start_date' => Carbon::createFromFormat('d-m-Y', $request->start_date)->format('Y-m-d'),
'end_date' => Carbon::createFromFormat('d-m-Y', $request->end_date)->format('Y-m-d'),
'school_id' => Auth::user()->school_id,
'session_year_id' => $sessionYearId
]);
$this->cache->removeSchoolCache(config("constants.CACHE.SCHOOL.GET_SEMESTERS_BY_SESSION_YEAR"));
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SEMESTER'));
ResponseService::successResponse('Data Updated Successfully');
} catch (Throwable $e) {
ResponseService::logErrorResponse($e, "Semester Controller -> Update method");
ResponseService::errorResponse();
}
}
public function show()
{
ResponseService::noPermissionThenRedirect('semester-list');
$offset = request('offset', 0);
$limit = request('limit', 10);
$sort = request('sort', 'id');
$order = request('order', 'ASC');
$search = request('search');
$showDeleted = request('show_deleted');
$sessionYearId = $this->cache->getSessionYear()->id;
$sql = $this->semester->builder()
->where('session_year_id', $sessionYearId)
->where(function ($q) use ($search) {
$q->when($search, function ($query) use ($search) {
$query->where('id', 'LIKE', "%$search%")
->orwhere('name', 'LIKE', "%$search%")
->orwhere('start_date', 'LIKE', "%$search%")
->orwhere('end_date', 'LIKE', "%$search%");
});
})
->when(!empty($showDeleted), function ($query) {
$query->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 = '';
if ($showDeleted) {
//Show Restore and Hard Delete Buttons
$operate .= BootstrapTableService::restoreButton(route('semester.restore', $row->id));
$operate .= BootstrapTableService::trashButton(route('semester.trash', $row->id));
} else {
//Show Edit and Soft Delete Buttons
$operate .= BootstrapTableService::editButton(route('semester.update', $row->id));
$operate .= BootstrapTableService::deleteButton(route('semester.destroy', $row->id));
}
$tempRow = $row->toArray();
$tempRow['no'] = $no++;
$tempRow['operate'] = $operate;
$rows[] = $tempRow;
}
$bulkData['rows'] = $rows;
return response()->json($bulkData);
}
public function destroy($id)
{
ResponseService::noPermissionThenSendJson('semester-delete');
try {
$semester = $this->cache->getDefaultSemesterData();
if ($semester->id == $id) {
return ResponseService::errorResponse('Cannot delete the current semester');
}
// Safety Check: Prevent deletion if semester is associated with academic data
if (ClassSubject::where('semester_id', $id)->exists() ||
Exam::where('semester_id', $id)->exists() ||
Timetable::where('semester_id', $id)->exists() ||
ElectiveSubjectGroup::where('semester_id', $id)->exists()) {
return ResponseService::errorResponse("Cannot delete this semester because it is associated with exams, subjects, or timetables. Please remove those associations first.");
}
$this->semester->deleteById($id);
$this->cache->removeSchoolCache(config("constants.CACHE.SCHOOL.GET_SEMESTERS_BY_SESSION_YEAR"));
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SEMESTER'));
ResponseService::successResponse('Data Deleted Successfully');
} catch (Throwable $e) {
ResponseService::logErrorResponse($e, "Semester Controller -> Delete method", 'cannot_delete_because_data_is_associated_with_other_data');
ResponseService::errorResponse();
}
}
public function restore(int $id)
{
ResponseService::noPermissionThenSendJson('semester-delete');
try {
$this->semester->findOnlyTrashedById($id)->restore();
$this->cache->removeSchoolCache(config("constants.CACHE.SCHOOL.GET_SEMESTERS_BY_SESSION_YEAR"));
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SEMESTER'));
ResponseService::successResponse("Data Restored Successfully");
} catch (Throwable $e) {
ResponseService::logErrorResponse($e);
ResponseService::errorResponse();
}
}
public function trash($id)
{
ResponseService::noPermissionThenSendJson('semester-delete');
try {
$semester = $this->semester->findOnlyTrashedById($id);
// Safety Check: Prevent permanent deletion if semester is associated with academic data
if (ClassSubject::where('semester_id', $id)->exists() ||
Exam::where('semester_id', $id)->exists() ||
Timetable::where('semester_id', $id)->exists() ||
ElectiveSubjectGroup::where('semester_id', $id)->exists()) {
return ResponseService::errorResponse("Cannot delete this semester permanently because it is associated with exams, subjects, or timetables. Please remove those associations first.");
}
if ($semester->current) {
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SEMESTER'));
}
$semester->forceDelete();
$this->cache->removeSchoolCache(config("constants.CACHE.SCHOOL.GET_SEMESTERS_BY_SESSION_YEAR"));
$this->cache->removeSchoolCache(config('constants.CACHE.SCHOOL.SEMESTER'));
ResponseService::successResponse("Data Deleted Permanently");
} catch (Throwable $e) {
ResponseService::logErrorResponse($e, "Semester Controller -> Trash Method", 'cannot_delete_because_data_is_associated_with_other_data');
ResponseService::errorResponse();
}
}
/**
* Check if the given date range overlaps with any existing semester
*
* @param string $startDate
* @param string $endDate
* @param int|null $ignoreID - Optional ID to ignore (for updates)
* @return array
*/
private function checkIfDatesOverlap(string $startDate, string $endDate, int $ignoreID = null)
{
$semesters = $this->semester->builder()->withTrashed();
$sessionYearId = $this->cache->getSessionYear()->id;
$semesters = $semesters->where('session_year_id', $sessionYearId);
if ($ignoreID !== null) {
$semesters = $semesters->where('id', '!=', $ignoreID);
}
$semesters = $semesters->whereNotNull('start_date')
->whereNotNull('end_date')
->get();
$newStartDate = Carbon::parse($startDate);
$newEndDate = Carbon::parse($endDate);
foreach ($semesters as $semester) {
$existingStartDate = Carbon::parse($semester->getRawOriginal('start_date'));
$existingEndDate = Carbon::parse($semester->getRawOriginal('end_date'));
// Check for overlap: two date ranges overlap if:
// (newStart <= existingEnd AND newEnd >= existingStart)
if ($newStartDate->lte($existingEndDate) && $newEndDate->gte($existingStartDate)) {
return [
'error' => true,
'message' => trans("The selected date range overlaps with existing semester: ") . $semester->name,
'data' => []
];
}
}
return [
'error' => false,
'message' => 'success'
];
}
public function setViewingSemester(Request $request)
{
$request->validate([
'semester_id' => 'nullable'
]);
try {
$this->cache->setSemester($request->semester_id);
ResponseService::successResponse("Viewing semester updated successfully");
} catch (Throwable $e) {
ResponseService::logErrorResponse($e, "Semester Controller -> setViewingSemester method");
ResponseService::errorResponse();
}
}
}