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();
        }
    }
}