File "TransportationRequestController-20260607194248.php"

Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/TransportationRequestController-20260607194248.php
File size: 36.73 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\TransportationPayment;
use App\Models\Vehicle;
use App\Models\Shift;
use App\Models\PickupPoint;
use App\Models\RouteVehicle;
use App\Models\TransportationFee;
use App\Models\PaymentTransaction;
use App\Models\StaffSalary;
use App\Models\Staff;
use App\Models\Students;
use App\Models\PayrollSetting;
use App\Repositories\User\UserInterface;
use App\Services\ResponseService;
use App\Services\BootstrapTableService;
use Throwable;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use Auth;
use App\Services\CachingService;
use Barryvdh\DomPDF\Facade\Pdf;
use App\Repositories\ClassSection\ClassSectionInterface;

class TransportationRequestController extends Controller
{
    private UserInterface $user;
    private CachingService $cache;
    private ClassSectionInterface $classSection;
    public function __construct(UserInterface $user, CachingService $cache, ClassSectionInterface $classSection,)
    {
        $this->user = $user;
        $this->cache = $cache;
        $this->classSection = $classSection;
    }
    public function index()
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noAnyPermissionThenSendJson(['transportationRequests-list']);
        $transportationRequests = TransportationPayment::owner()->with(['user', 'pickupPoint', 'shift'])
            ->where('status', 'paid')
            ->get();

        return view('transportation-request.index', compact('transportationRequests'));
    }

    public function show()
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-list');
        $today = now();
        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'id');
        $order = request('order', 'desc');
        $search = request('search');
        $showDeleted = request('show_deleted');
        $pickupPointId = request('pickup_point_id');
        $shiftId = request('shift_id');
        $includeAmount = request('include_amount');

        $sql = TransportationPayment::owner()->with([
            'user',
            'pickupPoint',
            'shift',
            'pickupPoint.routePickupPoints.route',
            'pickupPoint.routePickupPoints.route.routeVehicle',
            'pickupPoint.routePickupPoints.route.routeVehicle.vehicle',
            'transportationFee'
        ])->where('expiry_date', '>=', $today)
            ->where('status', 'paid')->when(!empty($showDeleted), function ($query) {
                $query->whereNotNull('route_vehicle_id');
            })->when(empty($showDeleted), function ($query) {
                $query->whereNull('route_vehicle_id');
            })->when(!empty($pickupPointId), function ($query) use ($pickupPointId) {
                $query->where('pickup_point_id', $pickupPointId);
            })->when(!empty($shiftId), function ($query) use ($shiftId) {
                $query->where('shift_id', $shiftId);
            });

        if (!empty($search)) {
            $sql->where(function ($q) use ($search) {
                $q->orWhereHas('user', function ($d) use ($search) {
                    $d->where('first_name', 'LIKE', "%$search%")
                        ->orWhere('last_name', 'LIKE', "%$search%")
                        ->orWhere('email', 'LIKE', "%$search%")
                        ->orWhereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
                });
                $q->orWhereHas('pickupPoint', function ($d) use ($search) {
                    $d->where('name', 'LIKE', "%$search%");
                });
                $q->orWhereHas('user.roles', function ($d) use ($search) {
                    if ($search == "Staff") {
                        $d->whereNot('name', "Student");
                        $d->whereNot('name', "Teacher");
                    } else {
                        $d->where('name', 'LIKE', "%$search%");
                    }
                });
            });
        }

        if (request('include_amount') !== null && request('include_amount') !== '') {
            $includeAmount = (int) $includeAmount;
            if ($includeAmount == 2) {
                $sql->whereNull('include_amount');
                $sql->where(function ($q) use ($search) {
                    $q->WhereHas('user.roles', function ($d) use ($search) {
                        $d->whereNot('name', "Student");
                    });
                });
            } else {
                $sql->where('include_amount', $includeAmount);
            }
        }

        $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 = $offset + 1;

        $baseUrl = url('/');
        $baseUrlWithoutScheme = preg_replace("(^https?://)", "", $baseUrl);
        $baseUrlWithoutScheme = str_replace("www.", "", $baseUrlWithoutScheme);

        foreach ($res as $row) {
            $operate = BootstrapTableService::editButton(route('transportation-requests.update', $row->id));
            if (!$row->user->hasrole('Student')) {
                $operate .= BootstrapTableService::button(
                    'fa fa-times',
                    route('transportation-requests.cancel', $row->id),
                    ['btn', 'btn-xs', 'btn-gradient-danger', 'btn-rounded', 'btn-icon', 'cancel-service'],
                    ['data-id' => $row->id, 'title' => trans('end_transportation_service')]
                );
            } else {
                $operate .= BootstrapTableService::button('fa fa-file-pdf-o', route('transportation-requests.fee-receipt', $row->id), ['btn', 'btn-xs', 'btn-gradient-info', 'btn-rounded', 'btn-icon', 'generate-paid-fees-pdf'], ['target' => "_blank", 'data-id' => $row->id, 'title' => trans('generate_pdf') . ' ' . trans('fees')]);
            }

            if ($row->user->hasrole('Student')) {
                $role = "Student";
            } elseif ($row->user->hasrole('Teacher')) {
                $role = "Teacher";
            } else {
                $role = 'Staff';
            }

            if (!$row->user->hasrole('Student')) {
                if ($row->include_amount == '0') {
                    $includeAmount = 'No';
                } elseif ($row->include_amount == '1') {
                    $includeAmount = 'Yes';
                } elseif ($row->include_amount == null) {
                    $includeAmount = 'Not Specified';
                }
            } else {
                $includeAmount = '';
            }

            $tempRow = $row->toArray();
            $tempRow['role'] = $role;
            $tempRow['no'] = $no++;
            $tempRow['include_amount'] = $includeAmount;
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function update(Request $request, $id)
    {
        ResponseService::noFeatureThenSendJson('Transportation Module');
        ResponseService::noPermissionThenSendJson(['transportationRequests-edit']);

        $validator = Validator::make($request->all(), [
            'edit_route_id' => 'required|numeric|exists:route_vehicles,id',
        ]);

        if ($validator->fails()) {
            ResponseService::validationError($validator->errors()->first());
        }

        try {
            DB::beginTransaction();
            $requestData = [
                'route_vehicle_id' => $request->edit_route_id,
                'include_amount' => $request->include_amount,
            ];

            $transportationPayment = TransportationPayment::find($id);

            $sessionYear = $this->cache->getDefaultSessionYear();
            $today = now();
            $existingPayment = TransportationPayment::where('user_id', $transportationPayment->user_id)
                ->whereNotNull('route_vehicle_id')
                ->whereNot('id', $transportationPayment->id)
                ->where('session_year_id', $sessionYear->id)
                ->where('expiry_date', '>=', $today)
                ->where('status', 'paid')
                ->first();

            if ($existingPayment) {
                ResponseService::errorResponse('This user already has an active paid record in the current session year.');
            }

            $routes = RouteVehicle::with('vehicle')->where('id', $request->edit_route_id)->first();

            $assignedCounts = TransportationPayment::selectRaw('route_vehicle_id, COUNT(*) as assigned_students')
                ->where('route_vehicle_id', $request->edit_route_id)
                ->where('status', 'paid')
                ->groupBy('route_vehicle_id')->first();

            if (!empty($routes) && !empty($assignedCounts) && ((int) $routes->vehicle->capacity - (int) $assignedCounts->assigned_students) == 0) {
                ResponseService::errorResponse("No seats left in this vehicle");
            }

            if (!$transportationPayment) {
                return redirect()->back()->with('error', 'Transportation payment not found.');
            }

            $dividedAmount = 0;
            if ($request->include_amount !== null && $request->include_amount == 1) {
                // Create StaffSalary record
                $startDate = $transportationPayment->created_at;

                // Calculate number of distinct months covered
                $months = $startDate->diffInMonths($transportationPayment->expiry_date) + 1;

                // Divide amount equally per month
                $dividedAmount = $months > 0
                    ? round(($transportationPayment->amount ?? 0) / $months, 2)
                    : ($transportationPayment->amount ?? 0);



                // StaffSalary::updateOrCreate(
                //     [
                //         'staff_id' => $staffId->id ?? null,
                //         'payroll_setting_id' => $payrollSetting->id ?? null,
                //     ],
                //     [
                //         'amount' => $dividedAmount ?? 0,
                //         'expiry_date' => $transportationPayment->expiry_date ?? null,
                //     ]
                // );
                // } elseif ($request->include_amount !== null && $request->include_amount == 0) {

                //     // StaffSalary::updateOrCreate(
                //     //     [
                //     //         'staff_id' => $staffId->id ?? null,
                //     //         'payroll_setting_id' => $payrollSetting->id ?? null,
                //     //     ],
                //     //     [
                //     //         'amount' => 0,
                //     //         'expiry_date' => $transportationPayment->expiry_date ?? null
                //     //     ]
                //     // );
                //     $requestData['included_amount'] = 0;
            }
            $requestData['included_amount'] = $dividedAmount;

            // Update attributes
            $transportationPayment->update($requestData);

            // $payrollSetting = PayrollSetting::where('name', 'Transportation Deduction')->first();
            // $staffId = Staff::where('user_id', $transportationPayment->user_id)->first();


            $title = "Transportation assigned";
            $body = "Your transportation has been assigned successfully.";
            $type = 'Transportation';

            $studentPayloads = [];
            $guardianPayloads = [];
            $staffPayloads = [];

            // Fetch user to determine if student or staff
            $userId = $transportationPayment->user_id;

            $student = Students::where('user_id', $userId)->first();

            if ($student) {

                // Student
                $studentPayloads = array_merge(
                    $studentPayloads,
                    buildPayloads([$userId], $title, $body, $type, ['user_id' => $userId])
                );

                // Guardian (if exists)
                if (!empty($student->guardian_id)) {
                    $guardianPayloads = array_merge(
                        $guardianPayloads,
                        buildPayloads([$student->guardian_id], $title, $body, $type, [
                            'guardian_id' => $student->guardian_id,
                            'child_id' => $student->id,
                            'user_id' => $student->user_id
                        ])
                    );
                }
            } else {
                // Staff user
                $staffPayloads = array_merge(
                    $staffPayloads,
                    buildPayloads([$userId], $title, $body, $type, ['user_id' => $userId])
                );
            }

            // Send notifications
            $studentGuardianPayloads = array_merge($studentPayloads, $guardianPayloads);

            if (!empty($studentGuardianPayloads)) {
                sendBulk($studentGuardianPayloads);
            }

            if (!empty($staffPayloads)) {
                sendBulk($staffPayloads);
            }

            DB::commit();
            ResponseService::successResponse('Data updated successfully');
        } catch (Throwable $e) {
            DB::rollBack();
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> update');
            ResponseService::errorResponse();
        }
    }

    public function cancelTransportationService($id)
    {
        ResponseService::noFeatureThenSendJson('Transportation Module');
        ResponseService::noPermissionThenSendJson(['transportationRequests-edit']);

        try {
            DB::beginTransaction();
            $today = now();
            $transportationPayment = TransportationPayment::find($id);

            if (!$transportationPayment) {
                return redirect()->back()->with('error', 'Transportation payment not found.');
            }


            // Update attributes
            $transportationPayment->update(['expiry_date' => $today->format('Y-m-d')]);


            // $payrollSetting = PayrollSetting::where('name', 'Transportation Deduction')->first();
            // $staffId = Staff::where('user_id', $transportationPayment->user_id)->first();

            // StaffSalary::where('staff_id', $staffId->id ?? null)
            //     ->where('payroll_setting_id', $payrollSetting->id ?? null)
            //     ->delete();


            DB::commit();
            ResponseService::successResponse('Service cancelled successfully');
        } catch (Throwable $e) {
            DB::rollBack();
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> cancelTransportationService');
            ResponseService::errorResponse();
        }
    }

    public function getVehicleRoutes($pickupPointId)
    {
        ResponseService::noPermissionThenSendJson(['transportationRequests-list']);
        $validator = Validator::make(['pickup_point_id' => $pickupPointId], [
            'pickup_point_id' => 'required|numeric|exists:pickup_points,id',
        ]);

        if ($validator->fails()) {
            ResponseService::validationError($validator->errors()->first());
        }

        try {
            $today = now();
            $routes = RouteVehicle::with('vehicle', 'route.shift')
                ->whereHas('route.routePickupPoints', function ($query) use ($pickupPointId) {
                    $query->where('pickup_point_id', $pickupPointId);
                });
            $routes = $routes->get();

            $assignedCounts = TransportationPayment::selectRaw('route_vehicle_id, COUNT(*) as assigned_students')
                ->whereNotNull('route_vehicle_id')
                ->where('status', 'paid')
                ->where('expiry_date', '>=', $today)
                ->groupBy('route_vehicle_id')
                ->pluck('assigned_students', 'route_vehicle_id');

            $fees = TransportationFee::where('pickup_point_id', $pickupPointId)->get();

            return response()->json([
                'success' => true,
                'data' => $routes,
                'assignedCounts' => $assignedCounts,
                'fees' => $fees,
            ]);
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> getVehicleRoutes');
            return ResponseService::errorResponse();
        }
    }

    public function changeStatusBulk(Request $request)
    {
        ResponseService::noFeatureThenSendJson('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-edit');

        $validator = Validator::make($request->all(), [
            'vehicle_route' => 'required|numeric|exists:route_vehicles,id',
            'ids' => 'required|string',
        ]);

        if ($validator->fails()) {
            ResponseService::validationError($validator->errors()->first());
        }

        try {
            DB::beginTransaction();

            $today = now();
            $paymentIds = json_decode($request->ids, true);

            // 1️⃣ Fetch user IDs for selected payments
            $users = TransportationPayment::whereIn('id', $paymentIds)
                ->pluck('user_id', 'id');  // payment_id => user_id

            $userIds = $users->values()->unique();

            // 2️⃣ Find users who already have ANY assigned payment
            $usersAlreadyAssigned = TransportationPayment::whereIn('user_id', $userIds)
                ->whereNotNull('route_vehicle_id')
                ->where('expiry_date', '>=', $today)
                ->pluck('user_id')
                ->unique();

            // 3️⃣ Filter out payments belonging to these users
            $filteredPaymentIds = $users->filter(function ($userId, $paymentId) use ($usersAlreadyAssigned) {
                return !$usersAlreadyAssigned->contains($userId);
            })->keys();

            $notificationUserIds = TransportationPayment::whereIn('id', $filteredPaymentIds)
                ->pluck('user_id')
                ->unique()
                ->toArray();

            // Get student records for these users
            $students = Students::whereIn('user_id', $notificationUserIds)
                ->get(['id', 'user_id', 'guardian_id']);

            // Index by user_id for O(1) lookup
            $studentsByUserId = $students->keyBy('user_id');

            $title = "Transportation assigned";
            $body = "Your transportation has been assigned successfully.";
            $type = 'Transportation';

            $studentPayloads = [];
            $guardianPayloads = [];
            $staffPayloads = [];

            foreach ($notificationUserIds as $userId) {

                if ($studentsByUserId->has($userId)) {
                    // User is a student
                    $student = $studentsByUserId->get($userId);
                    $childId = $student->id;          // student_id
                    $guardianId = $student->guardian_id;

                    // Notification to Student
                    $studentPayload = buildPayloads(
                        [$userId],
                        $title,
                        $body,
                        $type,
                        ['user_id' => $userId]
                    );
                    $studentPayloads = array_merge($studentPayloads, $studentPayload);

                    // Notification to Guardian (only if exists)
                    if (!empty($guardianId)) {
                        $guardianPayload = buildPayloads(
                            [$guardianId],
                            $title,
                            $body,
                            $type,
                            ['guardian_id' => $guardianId, 'child_id' => $childId, 'user_id' => $userId]
                        );
                        $guardianPayloads = array_merge($guardianPayloads, $guardianPayload);
                    }
                } else {
                    // User is NOT a student (teacher/staff)
                    $staffPayload = buildPayloads(
                        [$userId],
                        $title,
                        $body,
                        $type,
                        ['user_id' => $userId]   // No child_id
                    );
                    $staffPayloads = array_merge($staffPayloads, $staffPayload);
                }
            }

            // Send notifications in bulk
            $studentAndGuardianPayloads = array_merge($studentPayloads, $guardianPayloads);


            $skippedUsersCount = $usersAlreadyAssigned->count();
            $skippedPaymentsCount = count($paymentIds) - $filteredPaymentIds->count();

            // 4️⃣ Update only remaining payments
            $updated = TransportationPayment::whereIn('id', $filteredPaymentIds)
                ->update(['route_vehicle_id' => $request->vehicle_route]);

            DB::commit();

            if (!empty($studentAndGuardianPayloads)) {
                sendBulk($studentAndGuardianPayloads);
            }

            if (!empty($staffPayloads)) {
                sendBulk($staffPayloads);
            }

            $message = "{$updated} updated successfully";
            if ($skippedUsersCount > 0) {
                $message .= ", {$skippedPaymentsCount} skipped ({$skippedUsersCount} users already assigned)";
            }

            ResponseService::successResponse($message);
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e);
            ResponseService::errorResponse();
        }
    }


    public function offlineEntry()
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noAnyPermissionThenRedirect(['transportationRequests-create']);

        $class_sections = $this->classSection->all(['*'], ['class', 'class.stream', 'class.shift', 'section', 'medium']);
        $pickupPoints = pickupPoint::where('status', 1)->get();
        $shifts = Shift::where('status', 1)->get();


        return view('transportation-request.offline_entry', compact('pickupPoints', 'class_sections', 'shifts'));
    }

    public function getStudents($id)
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-create');
        try {
            // $sessionYear = $this->cache->getSessionYear();

            $selectedSessionYearId = $this->cache->getSessionYear()->id;
            $defaultSessionYearId = $this->cache->getDefaultSessionYear()->id;
            $isCurrentSession = ($selectedSessionYearId == $defaultSessionYearId);

            $classSection = $this->classSection->findById($id, ['*'], ['class', 'section', 'medium']);
            $students = $this->user->builder()
                ->role('Student')
                ->select('id', 'first_name', 'last_name');
            // ->whereHas('student', function ($q) use ($id, $sessionYear) {
            //     $q->where(function ($query) use ($id, $sessionYear) {
            //         $query->where('class_section_id', $id)->where('session_year_id', $sessionYear->id);
            //     })->orWhereHas('promote_student', function ($query) use ($id, $sessionYear) {
            //         $query->where('class_section_id', $id)->where('session_year_id', $sessionYear->id);
            //     });
            // })
            // ->with([
            //     'student' => function ($query) {
            //         $query->select('id', 'class_section_id', 'user_id', 'guardian_id');
            //     }
            // ]);

            if ($isCurrentSession) {
                $students = $students->whereHas('student', function ($q) use ($id, $selectedSessionYearId) {
                    $q->where('class_section_id', $id)->where('session_year_id', $selectedSessionYearId);
                });
            } else {
                $students = $students->whereHas('student', function ($q) use ($id, $selectedSessionYearId) {
                    $q->join('promote_students', 'students.user_id', '=', 'promote_students.student_id')
                        ->where('promote_students.session_year_id', $selectedSessionYearId)
                        ->select(
                            'students.*',
                            DB::raw("'historical' as record_source"),
                            'promote_students.roll_number as snapshot_roll_number',
                            'promote_students.class_section_id as snapshot_class_section_id'
                        )
                        ->where('promote_students.class_section_id', $id);
                });
            }
            $students = $students->withTrashed()->get();

            // Manually set class_section relation for all returned students to ensure
            // the UI shows the searched class (handles historical students correctly).
            foreach ($students as $user) {
                if ($user->student) {
                    $user->student->setRelation('class_section', $classSection);
                }
            }

            return response()->json([
                'success' => true,
                'data' => $students,
            ]);
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> getStudents');
            return ResponseService::errorResponse();
        }
    }
    public function getTeachers()
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-create');
        try {
            $teachers = $this->user->builder()->role('Teacher')->select('*')->get();

            return response()->json([
                'success' => true,
                'data' => $teachers,
            ]);
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> getTeacher');
            return ResponseService::errorResponse();
        }
    }
    public function getStaff()
    {
        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-create');
        try {
            $staff = $this->user->builder()->select('id', 'first_name', 'last_name', 'image')->has('staff')->with('roles', 'support_school.school:id,name')->whereHas('roles', function ($q) {
                $q->where('custom_role', 1)->whereNot('name', 'Teacher');
            })->get();

            return response()->json([
                'success' => true,
                'data' => $staff,
            ]);
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> getTeacher');
            return ResponseService::errorResponse();
        }
    }

    public function offlineEntryStore(Request $request)
    {
        ResponseService::noFeatureThenSendJson('Transportation Module');
        ResponseService::noPermissionThenRedirect('transportationRequests-create');

        $validator = Validator::make(
            $request->all(),
            [
                'user_id' => 'required|numeric|exists:users,id',
                // Remove shift_id from validation, as we will get it from routes table
                // 'shift_id' => 'nullable|numeric|exists:shifts,id',
                'pickup_point_id' => 'required|numeric|exists:pickup_points,id',
                'fee_id' => 'nullable|numeric|exists:transportation_fees,id',
                'route_vehicle_id' => 'required|numeric|exists:route_vehicles,id',
                'amount' => 'nullable|numeric',
                'mode' => 'nullable|in:1,2',
                'cheque_no' => 'required_if:mode,2',
                'include_amount' => 'nullable|numeric|in:0,1',
            ],
            [
                'user_id.required' => 'Please select a user.',
                'user_id.exists' => 'Selected user does not exist.',
                // 'shift_id.exists' => 'Selected shift does not exist.',
                'pickup_point_id.required' => 'Please select a pickup point.',
                'pickup_point_id.exists' => 'Selected pickup point does not exist.',
                'fee_id.exists' => 'Selected fee does not exist.',
                'route_vehicle_id.required' => 'Please select a vehicle route.',
                'route_vehicle_id.exists' => 'Selected route vehicle does not exist.',
                'mode.in' => 'Invalid payment mode selected.',
                'cheque_no.required_if' => 'Cheque number is required when payment mode is Cheque.',
                'include_amount.in' => 'Include amount must be either 0 or 1.',
            ]
        );

        if ($validator->fails()) {
            ResponseService::validationError($validator->errors()->first());
        }

        try {
            DB::beginTransaction();
            $sessionYear = $this->cache->getDefaultSessionYear();
            $today = now();
            $existingPayment = TransportationPayment::where('user_id', $request->user_id)
                ->whereNotNull('route_vehicle_id')
                ->where('session_year_id', $sessionYear->id)
                ->where('expiry_date', '>=', $today)
                ->where('status', 'paid')
                ->first();

            if ($existingPayment) {
                ResponseService::errorResponse('This user already has an active paid record in the current session year.');
            }

            // Get the RouteVehicle and its related Route (to get shift_id from routes table)
            $routeVehicle = RouteVehicle::with(['vehicle', 'route'])->where('id', $request->route_vehicle_id)->first();

            // Get shift_id from the related route
            $shiftId = $routeVehicle && $routeVehicle->route ? $routeVehicle->route->shift_id : null;

            $assignedCounts = TransportationPayment::selectRaw('route_vehicle_id, COUNT(*) as assigned_students')
                ->where('route_vehicle_id', $request->route_vehicle_id)
                ->where('status', 'paid')
                ->where('expiry_date', '>=', $today)
                ->groupBy('route_vehicle_id')->first();

            if (!empty($assignedCounts)) {
                if (!empty($routeVehicle) && ((int) $routeVehicle->vehicle->capacity - (int) $assignedCounts->assigned_students) == 0) {
                    ResponseService::errorResponse("No seats left in this vehicle");
                }
            }

            if ($request->fee_id) {
                $transportationFee = TransportationFee::where('id', $request->fee_id)->first();
                $expiryDate = null;
                if ($transportationFee) {
                    if (!empty($transportationFee->duration)) {
                        $expiryDate = now()->addDays($transportationFee->duration);
                    }
                }

                $mode = (int) $request->mode;
                $paymentTransactionData = [
                    'user_id' => $request->user_id,
                    'amount' => $request->amount,
                    'payment_gateway' => $mode === 1
                        ? 'cash'
                        : ($mode === 2 ? 'cheque' : null),
                    'order_id' => $request->cheque_no,
                    'payment_status' => 'succeed',
                    'type' => 'transportation_fee',
                    'school_id' => Auth::user()->school_id
                ];

                $paymentTransaction = PaymentTransaction::create($paymentTransactionData);
            } else {
                $expiryDate = $sessionYear->end_date;
            }

            $transportationPaymentData = [
                'route_vehicle_id' => $request->route_vehicle_id,
                'shift_id' => $shiftId, // Use shift_id from routes table
                'pickup_point_id' => $request->pickup_point_id,
                'user_id' => $request->user_id,
                'payment_transaction_id' => $paymentTransaction->id ?? null,
                'transportation_fee_id' => $request->fee_id ?? null,
                'amount' => $request->amount ?? 0,
                'paid_at' => now(),
                'session_year_id' => $sessionYear->id,
                'status' => 'paid',
                'expiry_date' => $expiryDate ?? null,
                'include_amount' => $request->include_amount ?? null,
            ];

            $dividedAmount = 0;
            if ($request->include_amount !== null && $request->include_amount == 1) {
                // Create StaffSalary record
                $startDate = now();

                // Calculate number of distinct months covered
                $months = $startDate->diffInMonths($expiryDate) + 1;

                // Divide amount equally per month
                $dividedAmount = $months > 0
                    ? round(($request->amount ?? 0) / $months, 2)
                    : ($request->amount ?? 0);
            }
            $transportationPaymentData['included_amount'] = $dividedAmount;

            TransportationPayment::create($transportationPaymentData);

            $title = "Transportation assigned";
            $body = "Your transportation has been assigned successfully.";
            $type = 'Transportation';

            $studentData = Students::where('user_id', $request->user_id)
                ->pluck('guardian_id', 'id')
                ->toArray();

            if (!empty($studentData)) {

                // Student exists → get child_id and guardian_id
                $childId = array_key_first($studentData);
                $guardianId = $studentData[$childId];

                // Notification to Student
                $studentPayload = buildPayloads(
                    [$request->user_id],
                    $title,
                    $body,
                    $type,
                    ['user_id' => $request->user_id]
                );

                // Notification to Guardian
                $guardianPayload = buildPayloads(
                    [$guardianId],
                    $title,
                    $body,
                    $type,
                    ['guardian_id' => $guardianId, 'child_id' => $childId, 'user_id' => $request->user_id]
                );

                // Send both
                sendBulk(array_merge($studentPayload, $guardianPayload));
            } else {

                // User is NOT a student (teacher/staff)
                $staffPayload = buildPayloads(
                    [$request->user_id],
                    $title,
                    $body,
                    $type,
                    ["user_id" => $request->user_id]   // No child_id
                );

                sendBulk($staffPayload);
            }

            DB::commit();
            ResponseService::successResponse('Data stored successfully');
        } catch (Throwable $e) {
            DB::rollBack();
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> offlineEntryStore');
            ResponseService::errorResponse();
        }
    }

    public function feeReceipt($id)
    {

        ResponseService::noFeatureThenRedirect('Transportation Module');
        ResponseService::noAnyPermissionThenRedirect(['transportationRequests-receipt']);

        try {

            $TransportationPayment = TransportationPayment::where('status', 'paid')->with('pickupPoint', 'transportationFee', 'paymentTransaction')->where('id', $id)->first();

            $student = $this->user->builder()->role('Student')->select('id', 'first_name', 'last_name')
                ->with([
                    'student' => function ($query) {
                        $query->select('id', 'class_section_id', 'user_id', 'guardian_id', 'admission_no')->with([
                            'class_section' => function ($query) {
                                $query->select('id', 'class_id', 'section_id', 'medium_id')->with('class:id,name', 'section:id,name', 'medium:id,name');
                            }
                        ]);
                    }
                ])->where('id', $TransportationPayment->user_id)->first();

            $school = $this->cache->getSchoolSettings();

            $data = explode("storage/", $school['horizontal_logo'] ?? '');
            $school['horizontal_logo'] = end($data);

            if ($school['horizontal_logo'] == null) {
                $systemSettings = $this->cache->getSystemSettings();
                $data = explode("storage/", $systemSettings['horizontal_logo'] ?? '');
                $school['horizontal_logo'] = end($data);
            }

            $schoolSettings = $this->cache->getSchoolSettings();

            $pdf = Pdf::loadView('transportation-request.fee_receipt', compact('school', 'TransportationPayment', 'student', 'schoolSettings'));
            return $pdf->stream('transportation-fees-receipt.pdf');
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e, 'TransportationRequestController -> feeReceipt');
            ResponseService::errorResponse();
        }
    }
}