get('status'); $user = Auth::user(); $isGlobalAdmin = $user->isGlobalAdmin(); $isSuperAdmin = $user->isSuperAdmin(); $query = PengajuanIzin::with(['user', 'jenisIzin', 'statusPengajuan', 'suratIzin']); if (!$isSuperAdmin) { $isManager = $user->roles->contains('nama_role', 'manager'); if (!$isManager) { $query->where(function ($q) { $q->where('id_jenis_izin', '!=', JenisIzinEnum::CUTI) ->orWhereHas('suratIzin', function ($sq) { $sq->where('status_surat', '!=', StatusSurat::MENUNGGU_MANAJER); }); }); } if (!$isGlobalAdmin) { $query->whereHas('user', function ($q) use ($user) { $q->where('id_kantor', $user->id_kantor); }); } } if ($statusId) { $query->where('id_status', $statusId); } if ($request->has('search') && $request->search != '') { $query->whereHas('user', function ($q) use ($request) { $q->where('nama_lengkap', 'like', '%' . $request->search . '%'); }); } $izin = $query->orderBy('created_at', 'desc')->paginate(15); $statuses = StatusPengajuanModel::orderBy('id_status')->get(); $jenisIzinList = JenisIzin::all(); $usersQuery = User::with(['kantor', 'divisi', 'jabatan']) ->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan') ->where('status_aktif', 1) ->bukanSuperAdmin() ->orderBy('nama_lengkap', 'asc'); if (!$isGlobalAdmin) { $usersQuery->where('id_kantor', $user->id_kantor); } $users = $usersQuery->get(); $kantor = $isGlobalAdmin ? Kantor::all() : Kantor::where('id_kantor', $user->id_kantor)->get(); $divisi = Divisi::all(); return view('izin.index', compact('izin', 'statusId', 'statuses', 'jenisIzinList', 'users', 'kantor', 'divisi')); } public function store(StorePengajuanIzinRequest $request) { $jenisIzinData = JenisIzin::find($request->id_jenis_izin); $existingPending = PengajuanIzin::where('id_user', $request->id_user) ->where('id_jenis_izin', $request->id_jenis_izin) ->where('id_status', StatusPengajuan::PENDING) ->exists(); if ($existingPending) { $namaIzin = $jenisIzinData->nama_izin ?? 'Izin'; return back()->withInput()->with('error', "Pegawai masih memiliki pengajuan {$namaIzin} yang menunggu persetujuan."); } if ($jenisIzinData && $jenisIzinData->id_jenis_izin == JenisIzinEnum::CUTI) { $pegawai = User::find($request->id_user); $tanggalMulai = Carbon::parse($request->tanggal_mulai); $tanggalSelesai = Carbon::parse($request->tanggal_selesai); $jumlahHari = $tanggalMulai->diffInDays($tanggalSelesai) + 1; if ($jumlahHari > 3) { return back()->withInput()->with('error', 'Pengajuan cuti maksimal 3 hari berturut-turut dalam satu pengajuan.'); } if ($pegawai && $pegawai->sisa_cuti < $jumlahHari) { return back()->withInput()->with('error', "Sisa cuti pegawai tidak mencukupi. Sisa: {$pegawai->sisa_cuti} hari, diajukan: {$jumlahHari} hari."); } $minDate = Carbon::now('Asia/Jakarta')->addDays(7)->startOfDay(); if (Carbon::parse($request->tanggal_mulai, 'Asia/Jakarta')->lt($minDate)) { return back()->withInput()->with('error', 'Pengajuan Cuti minimal H-7!'); } } if ($jenisIzinData && $jenisIzinData->id_jenis_izin == JenisIzinEnum::SAKIT) { $tanggalMulai = Carbon::parse($request->tanggal_mulai); $jumlahHari = $tanggalMulai->diffInDays(Carbon::parse($request->tanggal_selesai)) + 1; $sudahSakitBulanIni = PengajuanIzin::where('id_user', $request->id_user) ->where('id_jenis_izin', JenisIzinEnum::SAKIT) ->whereIn('id_status', [StatusPengajuan::PENDING, StatusPengajuan::DISETUJUI]) ->whereMonth('tanggal_mulai', $tanggalMulai->month) ->whereYear('tanggal_mulai', $tanggalMulai->year) ->exists(); if (($jumlahHari > 1 || $sudahSakitBulanIni) && !$request->hasFile('bukti_file')) { return back()->withInput()->with('error', $jumlahHari > 1 ? 'Izin sakit lebih dari 1 hari wajib melampirkan Surat Keterangan Dokter (SKD).' : 'Sudah pernah izin sakit di bulan ini. Wajib melampirkan Surat Keterangan Dokter (SKD).' ); } } $tglMulai = $request->tanggal_mulai; $tglSelesai = $request->tanggal_selesai; $userId = $request->id_user; $overlappingIzin = PengajuanIzin::where('id_user', $userId) ->whereIn('id_status', [StatusPengajuan::PENDING, StatusPengajuan::DISETUJUI]) ->where(function ($q) use ($tglMulai, $tglSelesai) { $q->whereBetween('tanggal_mulai', [$tglMulai, $tglSelesai]) ->orWhereBetween('tanggal_selesai', [$tglMulai, $tglSelesai]) ->orWhere(function ($q2) use ($tglMulai, $tglSelesai) { $q2->where('tanggal_mulai', '<=', $tglMulai) ->where('tanggal_selesai', '>=', $tglSelesai); }); })->exists(); if ($overlappingIzin) { return back()->withInput()->with('error', 'Pegawai sudah memiliki pengajuan Izin/Cuti (Pending/Disetujui) pada rentang tanggal tersebut!'); } $existingPresensi = Presensi::where('id_user', $userId) ->whereBetween('tanggal', [$tglMulai, $tglSelesai]) ->whereNotNull('jam_masuk') ->exists(); if ($existingPresensi) { return back()->withInput()->with('error', 'Pegawai sudah tercatat melakukan absensi kehadiran pada rentang tanggal tersebut!'); } try { DB::beginTransaction(); $path = null; if ($request->hasFile('bukti_file')) { $path = $request->file('bukti_file')->store('uploads/izin', 'public'); } $izin = PengajuanIzin::create([ 'id_user' => $request->id_user, 'id_jenis_izin' => $request->id_jenis_izin, 'tanggal_mulai' => $request->tanggal_mulai, 'tanggal_selesai' => $request->tanggal_selesai, 'alasan' => $request->alasan, 'bukti_file' => $path, 'id_status' => StatusPengajuan::PENDING ]); if ($izin->id_jenis_izin == JenisIzinEnum::CUTI) { $user = User::with(['jabatan', 'divisi'])->find($request->id_user); $ttdAktif = TandaTangan::where('id_user', $user->id)->active()->first(); $tglMulai = Carbon::parse($request->tanggal_mulai)->translatedFormat('d F Y'); $tglSelesai = Carbon::parse($request->tanggal_selesai)->translatedFormat('d F Y'); $isiSurat = "Dengan hormat,\n\n" . "Saya yang bertanda tangan di bawah ini:\n" . "Nama: {$user->nama_lengkap}\n" . "NIK: {$user->nik}\n" . "Jabatan: " . ($user->jabatan->nama_jabatan ?? '-') . "\n" . "Divisi: " . ($user->divisi->nama_divisi ?? '-') . "\n\n" . "Dengan ini mengajukan Cuti mulai tanggal {$tglMulai} sampai dengan {$tglSelesai}.\n\n" . "Alasan: {$request->alasan}\n\n" . "Demikian surat ini saya buat dengan sebenar-benarnya. " . "Atas perhatian dan persetujuannya, saya ucapkan terima kasih."; SuratIzin::create([ 'id_izin' => $izin->id_izin, 'id_user' => $user->id, 'id_ttd_pengaju' => $ttdAktif?->id_tanda_tangan, 'isi_surat' => $isiSurat, 'status_surat' => StatusSurat::MENUNGGU_MANAJER, ]); } DB::commit(); $pegawai = User::find($request->id_user); $targetRole = ($izin->id_jenis_izin == JenisIzinEnum::CUTI) ? 'manager' : 'hrd'; app(NotifikasiService::class)->kirimKeRole( $targetRole, 'pengajuan_baru', 'Pengajuan Baru: ' . ($jenisIzinData->nama_izin ?? 'Izin'), ($pegawai->nama_lengkap ?? 'Pegawai') . ' mengajukan ' . ($jenisIzinData->nama_izin ?? 'izin') . '.', ['id_izin' => $izin->id_izin] ); return redirect()->back()->with('success', 'Pengajuan izin berhasil dibuat.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back()->with('error', 'Gagal membuat izin: ' . $e->getMessage()); } } public function approve($id) { try { DB::beginTransaction(); $izin = PengajuanIzin::with(['jenisIzin', 'user'])->findOrFail($id); $user = Auth::user(); // Security Check if (!$user->isGlobalAdmin() && $izin->user->id_kantor != $user->id_kantor) { return redirect()->back()->with('error', 'Anda tidak diizinkan menyetujui pengajuan dari kantor lain.'); } if ($izin->id_status !== StatusPengajuan::PENDING) { return redirect()->back()->with('error', 'Pengajuan ini sudah diproses sebelumnya.'); } if ($izin->id_jenis_izin == JenisIzinEnum::CUTI) { return redirect()->back()->with('error', 'Pengajuan Cuti harus disetujui melalui menu Surat Izin (Manajer → HRD).'); } $izin->update(['id_status' => StatusPengajuan::DISETUJUI]); $statusPresensiId = StatusPresensi::IZIN; if ($izin->id_jenis_izin == JenisIzinEnum::SAKIT) { $statusPresensiId = StatusPresensi::SAKIT; } $startDate = Carbon::parse($izin->tanggal_mulai, 'Asia/Jakarta'); $endDate = Carbon::parse($izin->tanggal_selesai, 'Asia/Jakarta'); while ($startDate->lte($endDate)) { $tanggal = $startDate->format('Y-m-d'); Presensi::updateOrCreate( [ 'id_user' => $izin->id_user, 'tanggal' => $tanggal ], [ 'id_status' => $statusPresensiId, 'jam_masuk' => null, 'jam_pulang' => null, 'id_validasi' => StatusValidasi::VALID, 'alasan_telat' => $izin->jenisIzin->nama_izin . ': ' . $izin->alasan ] ); $startDate->addDay(); } DB::commit(); app(NotifikasiService::class)->kirim( $izin->id_user, 'izin_disetujui', 'Pengajuan Izin Disetujui', 'Pengajuan ' . $izin->jenisIzin->nama_izin . ' Anda telah disetujui.', ['id_izin' => $izin->id_izin] ); broadcast(new PengajuanIzinUpdated( (int) $izin->id_user, (int) $izin->id_izin, 'disetujui', $izin->jenisIzin->nama_izin, 'Pengajuan ' . $izin->jenisIzin->nama_izin . ' disetujui.' )); return redirect()->back()->with('success', 'Izin disetujui & data presensi diperbarui.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back()->with('error', 'Gagal: ' . $e->getMessage()); } } public function reject(Request $request, $id) { $izin = PengajuanIzin::with(['user', 'jenisIzin'])->findOrFail($id); $user = Auth::user(); if (!$user->isGlobalAdmin() && $izin->user->id_kantor != $user->id_kantor) { return redirect()->back()->with('error', 'Anda tidak diizinkan menolak pengajuan dari kantor lain.'); } if ($izin->id_status !== StatusPengajuan::PENDING) { return redirect()->back()->with('error', 'Pengajuan ini sudah diproses sebelumnya.'); } if ($izin->id_jenis_izin == JenisIzinEnum::CUTI) { return redirect()->back()->with('error', 'Pengajuan Cuti harus ditolak melalui menu Surat Izin.'); } try { DB::beginTransaction(); $izin->update([ 'id_status' => StatusPengajuan::DITOLAK, 'alasan_penolakan' => $request->alasan_penolakan, ]); DB::commit(); app(NotifikasiService::class)->kirim( $izin->id_user, 'izin_ditolak', 'Pengajuan Izin Ditolak', 'Pengajuan ' . ($izin->jenisIzin->nama_izin ?? 'Izin') . ' Anda ditolak. Catatan: ' . ($request->alasan_penolakan ?? '-'), ['id_izin' => $izin->id_izin] ); broadcast(new PengajuanIzinUpdated( (int) $izin->id_user, (int) $izin->id_izin, 'ditolak', $izin->jenisIzin->nama_izin ?? 'Izin', 'Pengajuan ' . ($izin->jenisIzin->nama_izin ?? 'Izin') . ' ditolak.' )); return redirect()->back()->with('success', 'Pengajuan izin berhasil ditolak.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back()->with('error', 'Gagal menolak pengajuan: ' . $e->getMessage()); } } }