notificationHelper = $notificationHelper; $this->initCoordinatesFromConfig(); } private function initCoordinatesFromConfig(): void { $coordinate = config('coordinate.coordinate', '-7.604032330848524, 112.10176449791652'); [$lat, $lon] = array_map('trim', explode(',', $coordinate)); $this->latOfAttendance = (float) $lat; $this->lonOfAttendance = (float) $lon; } public function index() { $permissions = Permission::all(); return view('pages.perizinan.perizinan', compact('permissions')); } public function permissionAccept(Request $request, int $id): RedirectResponse { try { // session-auth user for web controller $user = $request->user(); if (!$user) { return redirect()->back()->with('error', 'Unauthorized'); } // find permission by id $permission = Permission::find($id); if (!$permission) { return redirect()->back()->with('error', 'Data perizinan tidak ditemukan'); } // permission must be processed only, if already accepted or rejected, cannot be processed again. if ($permission->status === 'diterima') { return redirect()->back()->with('warning', 'Perizinan sudah diterima sebelumnya'); } if ($permission->status === 'ditolak') { return redirect()->back()->with('warning', 'Perizinan sudah ditolak sebelumnya'); } $student = Student::find($permission->id_student); if (!$student) { return redirect()->back()->with('error', 'Data siswa tidak ditemukan'); } DB::transaction(function () use ($permission, $user, $request, $student) { // update status to accepted $permission->status = 'diterima'; $permission->approved_by = $user->id; // set approved by teacher id $permission->feedback = $request->input('feedback', null); // optional feedback message from teacher $permission->save(); // * create attendance record history to related date (only fill with schedule that has active academic period, and match with date permission). $permissionReason = $permission->reason; // contoh: 'sakit', 'izin', 'dispen', 'lainnya' if ($permissionReason === 'lainnya') { $permissionReason = 'izin'; } $permissionDate = Carbon::parse($permission->date_permission)->startOfDay(); $permissionTimePeriod = (int) $permission->time_period; $createdSchoolDays = 0; $currentDate = $permissionDate->copy(); // Count time_period as school days; weekend days are skipped and replaced by the next weekday. while ($createdSchoolDays < $permissionTimePeriod) { if ($currentDate->isWeekend()) { $currentDate->addDay(); continue; } $dayName = strtolower($currentDate->locale('id')->dayName); $dayIndex = $currentDate->dayOfWeekIso; // 1..7 // search related schedule with day_of_week same and active academic period $schedules = Schedule::query() ->where('id_class', $student->id_class) ->whereHas('academicPeriods', function ($q) { $q->where('is_active', 1); }) ->where(function ($q) use ($dayName, $dayIndex) { $q->where('day_of_week', $dayName) ->orWhere('day_of_week', (string) $dayIndex); }) ->get(); foreach ($schedules as $schedule) { AttendanceHistory::updateOrCreate( [ 'id_schedule' => $schedule->id, 'id_student' => $student->id, 'attendance_date' => $currentDate->toDateString(), ], [ 'id_class' => $schedule->id_class, 'period_number' => $schedule->period_start, 'coordinate' => $this->latOfAttendance . ', ' . $this->lonOfAttendance, 'status' => $permissionReason, ] ); } // update attendancehistorydaily to 'izin' // Cari berdasarkan id_student, id_class, dan tanggal dari created_at // Jika ada, update. Jika tidak ada, create $attendanceDaily = AttendanceHistoryDaily::whereDate('created_at', $currentDate->toDateString()) ->where('id_student', $student->id) ->where('id_class', $student->id_class) ->first(); if ($attendanceDaily) { $attendanceDaily->update([ 'status' => 'izin', 'picture' => '', ]); } else { AttendanceHistoryDaily::create([ 'id_student' => $student->id, 'id_class' => $student->id_class, 'created_at' => $currentDate, 'status' => 'izin', 'picture' => '', ]); } $createdSchoolDays++; $currentDate->addDay(); } }); // notification zone: send notification to related student after permission approved $userStudent = $student->user; if (!$userStudent) { return redirect()->back()->with('error', 'User siswa tidak ditemukan untuk notifikasi'); } $fcmToken = $userStudent->device_token; $title = 'Perizinan Disetujui'; $body = 'Permohonan izin Anda telah disetujui.'; // Push notification is optional; database notification must still be created. if (!empty($fcmToken)) { try { $template = $this->notificationHelper->templatePermissionApproval($fcmToken); $this->notificationHelper->send($template); $title = $template->title; $body = $template->body; } catch (\Throwable $th) { // Ignore push errors so approval flow and DB notification keep working. } } $createTemplate = $this->notificationHelper->createTemplateForStudent( (int) $userStudent->id, $title, $body, NotificationType::Permission, (int) $user->id ); Notification::create($createTemplate->toArray()); return redirect()->back()->with('success', 'Izin diterima untuk siswa ' . $student->name); } catch (\Exception $e) { return redirect()->back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } public function permissionReject(Request $request, int $id): RedirectResponse { try { // session-auth user for web controller $user = $request->user(); if (!$user) { return redirect()->back()->with('error', 'Unauthorized'); } // find permission by id $permission = Permission::find($id); if (!$permission) { return redirect()->back()->with('error', 'Data perizinan tidak ditemukan'); } // permission must be processed only, if already accepted or rejected, cannot be processed again. if ($permission->status === 'diterima') { return redirect()->back()->with('warning', 'Perizinan sudah diterima sebelumnya'); } if ($permission->status === 'ditolak') { return redirect()->back()->with('warning', 'Perizinan sudah ditolak sebelumnya'); } $student = Student::find($permission->id_student); if (!$student) { return redirect()->back()->with('error', 'Data siswa tidak ditemukan'); } DB::transaction(function () use ($permission, $user, $request) { // update status to rejected $permission->status = 'ditolak'; $permission->approved_by = $user->id; // set approved by teacher id $permission->feedback = $request->input('feedback', null); // optional feedback message from teacher $permission->save(); }); // notification zone: send notification to related student after permission rejected $userStudent = $student->user; if (!$userStudent) { return redirect()->back()->with('error', 'User siswa tidak ditemukan untuk notifikasi'); } $fcmToken = $userStudent->device_token; // Push notification is optional; database notification must still be created. if (!empty($fcmToken)) { try { $template = $this->notificationHelper->templatePermissionRejection( $fcmToken, $request->input('feedback', null) // optional custom message from teacher feedback ); $this->notificationHelper->send($template); $title = $template->title; $body = $template->body; } catch (\Throwable $th) { // Ignore push errors so rejection flow and DB notification keep working. $title = 'Perizinan Ditolak'; $body = 'Permohonan izin Anda telah ditolak.'; } } else { $title = 'Perizinan Ditolak'; $body = 'Permohonan izin Anda telah ditolak.'; } $createTemplate = $this->notificationHelper->createTemplateForStudent( (int) $userStudent->id, $title, $body, NotificationType::Permission, (int) $user->id ); Notification::create($createTemplate->toArray()); return redirect()->back()->with('success', 'Izin ditolak untuk siswa ' . $student->name); } catch (\Exception $e) { return redirect()->back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } }