From 0216c572cfc43342c4a86a1723a2511d48a997ed Mon Sep 17 00:00:00 2001 From: jouel88 Date: Sun, 29 Mar 2026 11:13:50 +0700 Subject: [PATCH] perbaikan --- app/Exports/LaporanIzinExport.php | 4 + app/Exports/LaporanLemburExport.php | 4 + app/Exports/LaporanPresensiExport.php | 4 + .../Api/PengumumanApiController.php | 20 ++ .../Controllers/Api/PresensiController.php | 76 ++++++- .../Controllers/Api/SubmissionController.php | 27 ++- app/Http/Controllers/PegawaiController.php | 26 ++- .../Controllers/PengajuanIzinController.php | 16 +- app/Http/Controllers/SuratIzinController.php | 28 ++- app/Models/Presensi.php | 11 + database/seeders/DatabaseSeeder.php | 1 + database/seeders/JenisIzinSeeder.php | 11 +- database/seeders/JenisKompensasiSeeder.php | 27 +++ database/seeders/PresensiStatusSeeder.php | 29 ++- database/seeders/StatusPengajuanSeeder.php | 11 +- resources/views/cuti/index.blade.php | 214 ++++++------------ resources/views/dashboard/index.blade.php | 12 +- resources/views/jadwal/create.blade.php | 84 +++++-- resources/views/lembur/index.blade.php | 22 +- resources/views/notifikasi/index.blade.php | 14 +- resources/views/pegawai/index.blade.php | 8 +- resources/views/pegawai/show.blade.php | 23 +- resources/views/permission/index.blade.php | 9 +- resources/views/tukar-shift/index.blade.php | 8 +- routes/api.php | 9 + routes/web.php | 1 + 26 files changed, 458 insertions(+), 241 deletions(-) create mode 100644 database/seeders/JenisKompensasiSeeder.php diff --git a/app/Exports/LaporanIzinExport.php b/app/Exports/LaporanIzinExport.php index 128c80f1..cc545511 100644 --- a/app/Exports/LaporanIzinExport.php +++ b/app/Exports/LaporanIzinExport.php @@ -110,6 +110,10 @@ public function registerEvents(): array $lastRow = $this->data->count() + 3; $dataRange = "A3:I{$lastRow}"; + $sheet->getParent()->getDefaultStyle()->getFont() + ->setName('Calibri') + ->setSize(10); + // Merge judul $sheet->mergeCells('A1:I1'); diff --git a/app/Exports/LaporanLemburExport.php b/app/Exports/LaporanLemburExport.php index 5e9291bc..8312d060 100644 --- a/app/Exports/LaporanLemburExport.php +++ b/app/Exports/LaporanLemburExport.php @@ -150,6 +150,10 @@ public function registerEvents(): array $lastRow = count($this->pegawai) + 6; $dataStart = 7; + $sheet->getParent()->getDefaultStyle()->getFont() + ->setName('Calibri') + ->setSize(10); + $sheet->mergeCells('A1:I1'); $sheet->mergeCells('A2:I2'); $sheet->mergeCells('A3:I3'); diff --git a/app/Exports/LaporanPresensiExport.php b/app/Exports/LaporanPresensiExport.php index f065fcdc..330e8ef2 100644 --- a/app/Exports/LaporanPresensiExport.php +++ b/app/Exports/LaporanPresensiExport.php @@ -154,6 +154,10 @@ public function registerEvents(): array $lastRow = count($this->rekap) + 6; $dataStart = 7; + $sheet->getParent()->getDefaultStyle()->getFont() + ->setName('Calibri') + ->setSize(10); + // Merge judul $sheet->mergeCells('A1:K1'); $sheet->mergeCells('A2:K2'); diff --git a/app/Http/Controllers/Api/PengumumanApiController.php b/app/Http/Controllers/Api/PengumumanApiController.php index ec6bc9fd..a704275a 100644 --- a/app/Http/Controllers/Api/PengumumanApiController.php +++ b/app/Http/Controllers/Api/PengumumanApiController.php @@ -22,6 +22,7 @@ public function index() 'description' => $item->isi, 'tanggal' => $item->tanggal?->format('Y-m-d'), 'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'Admin', + 'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'Admin', 'avatar_url' => $item->pembuat?->foto, ]; }); @@ -31,4 +32,23 @@ public function index() return ApiResponse::error('Gagal memuat pengumuman: ' . $e->getMessage(), 500); } } + + public function show($id) + { + try { + $item = Pengumuman::with('pembuat.jabatan')->findOrFail($id); + + return ApiResponse::success([ + 'id' => $item->id_pengumuman, + 'title' => $item->judul, + 'description' => $item->isi, + 'tanggal' => $item->tanggal?->format('Y-m-d'), + 'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'Admin', + 'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'Admin', + 'avatar_url' => $item->pembuat?->foto, + ], 'Detail pengumuman berhasil dimuat'); + } catch (\Exception $e) { + return ApiResponse::error('Pengumuman tidak ditemukan', 404); + } + } } diff --git a/app/Http/Controllers/Api/PresensiController.php b/app/Http/Controllers/Api/PresensiController.php index bace4996..5a7cfa4a 100644 --- a/app/Http/Controllers/Api/PresensiController.php +++ b/app/Http/Controllers/Api/PresensiController.php @@ -105,6 +105,61 @@ public function index() ]); } + public function checkRadius(Request $request) + { + try { + $request->validate([ + 'latitude' => 'required|numeric', + 'longitude' => 'required|numeric', + ]); + + $user = Auth::user(); + $kantor = $user->kantor; + + if (!$kantor) { + return response()->json([ + 'status' => 'error', + 'message' => 'Data kantor belum disetting.', + ], 400); + } + + $distance = $this->calculateDistance( + $request->latitude, + $request->longitude, + $kantor->latitude, + $kantor->longitude, + ); + + $isWithinRadius = $distance <= $kantor->radius; + + return response()->json([ + 'status' => 'success', + 'data' => [ + 'is_within_radius' => $isWithinRadius, + 'distance' => round($distance, 2), + 'radius' => $kantor->radius, + ] + ]); + } catch (\Exception $e) { + return response()->json([ + 'status' => 'error', + 'message' => $e->getMessage(), + ], 500); + } + } + + private function calculateDistance($lat1, $lon1, $lat2, $lon2) + { + $earthRadius = 6371000; + $dLat = deg2rad($lat2 - $lat1); + $dLon = deg2rad($lon2 - $lon1); + $a = sin($dLat / 2) * sin($dLat / 2) + + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * + sin($dLon / 2) * sin($dLon / 2); + $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); + return $earthRadius * $c; + } + public function store(Request $request) { try { @@ -138,11 +193,16 @@ public function history(Request $request) { try { $userId = Auth::id(); - $history = Presensi::where('id_user', $userId) - ->latest('tanggal') - ->paginate(10); + $month = $request->get('month', Carbon::now('Asia/Jakarta')->month); + $year = $request->get('year', Carbon::now('Asia/Jakarta')->year); - $enriched = $history->getCollection()->map(function ($item) use ($userId) { + $history = Presensi::where('id_user', $userId) + ->whereMonth('tanggal', $month) + ->whereYear('tanggal', $year) + ->latest('tanggal') + ->get(); + + $enriched = $history->map(function ($item) use ($userId) { $jadwal = \App\Models\JadwalKerja::with('shift') ->where('id_user', $userId) ->where('tanggal', $item->tanggal) @@ -220,9 +280,11 @@ public function history(Request $request) ]; }); - $history->setCollection($enriched); - - return ApiResponse::success($history); + return ApiResponse::success([ + 'data' => $enriched->values(), + 'month' => (int) $month, + 'year' => (int) $year, + ]); } catch (\Exception $e) { return ApiResponse::error($e->getMessage(), 500); diff --git a/app/Http/Controllers/Api/SubmissionController.php b/app/Http/Controllers/Api/SubmissionController.php index 4906c7c5..b34af713 100644 --- a/app/Http/Controllers/Api/SubmissionController.php +++ b/app/Http/Controllers/Api/SubmissionController.php @@ -30,14 +30,23 @@ public function store(\App\Http\Requests\StorePengajuanIzinRequest $request) $jenisIzin = JenisIzin::find($request->id_jenis_izin); if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { - // Validasi sisa cuti + $tanggalMulai = Carbon::parse($request->tanggal_mulai); + $tanggalSelesai = Carbon::parse($request->tanggal_selesai); + $jumlahHari = $tanggalMulai->diffInDays($tanggalSelesai) + 1; + if ($user->sisa_cuti <= 0) { return ApiResponse::error('Sisa cuti Anda sudah habis (0 hari). Pengajuan cuti tidak dapat dilanjutkan.', 400); } - $tanggalMulai = Carbon::parse($request->tanggal_mulai); - $minDate = Carbon::now()->addDays(7)->startOfDay(); + if ($jumlahHari > 3) { + return ApiResponse::error('Pengajuan cuti maksimal 3 hari berturut-turut dalam satu pengajuan.', 400); + } + if ($jumlahHari > $user->sisa_cuti) { + return ApiResponse::error("Sisa cuti Anda tidak mencukupi. Sisa: {$user->sisa_cuti} hari, diajukan: {$jumlahHari} hari.", 400); + } + + $minDate = Carbon::now()->addDays(7)->startOfDay(); if ($tanggalMulai->lt($minDate)) { return ApiResponse::error('Pengajuan Cuti minimal H-7!', 400); } @@ -255,7 +264,7 @@ public function update(\App\Http\Requests\UpdatePengajuanIzinRequest $request, $ public function history(Request $request) { try { - $query = PengajuanIzin::with(['jenisIzin', 'statusPengajuan']) + $query = PengajuanIzin::with(['jenisIzin', 'statusPengajuan', 'suratIzin']) ->where('id_user', Auth::id()); if ($request->has('status')) { @@ -275,6 +284,16 @@ public function history(Request $request) $history = $query->orderBy('created_at', 'desc')->paginate(10); + $history->getCollection()->transform(function ($item) { + $item->bukti_file_url = $item->bukti_file + ? asset('storage/' . $item->bukti_file) + : null; + $item->has_surat = $item->suratIzin !== null; + $item->id_surat = $item->suratIzin?->id_surat; + unset($item->suratIzin); + return $item; + }); + return ApiResponse::success($history); } catch (\Exception $e) { diff --git a/app/Http/Controllers/PegawaiController.php b/app/Http/Controllers/PegawaiController.php index 8ab6bc17..2aee1a8a 100644 --- a/app/Http/Controllers/PegawaiController.php +++ b/app/Http/Controllers/PegawaiController.php @@ -200,12 +200,22 @@ public function show($id) $historyPoin = $historyTambah->concat($historyKurang)->sortByDesc('tanggal')->take(10); - $riwayatPresensi = \App\Models\Presensi::with(['status', 'jadwal.shift']) + $riwayatPresensi = \App\Models\Presensi::with(['status']) ->where('id_user', $id) ->whereDate('tanggal', '>=', now()->subDays(30)->toDateString()) ->orderBy('tanggal', 'desc') ->get(); + $jadwalKerja = \App\Models\JadwalKerja::with('shift') + ->where('id_user', $id) + ->whereIn('tanggal', $riwayatPresensi->pluck('tanggal')) + ->get() + ->keyBy('tanggal'); + + foreach ($riwayatPresensi as $presensi) { + $presensi->setRelation('jadwal', $jadwalKerja->get($presensi->tanggal)); + } + $riwayatIzin = \App\Models\PengajuanIzin::with('jenisIzin') ->where('id_user', $id) ->whereDate('tanggal_mulai', '>=', now()->subDays(30)->toDateString()) @@ -238,4 +248,18 @@ public function destroy($id) return redirect()->route('pegawai.index')->with('success', 'Status pegawai telah diubah menjadi Non-Aktif (Resigned). Histori data tetap tersimpan.'); } + public function resetPassword($id) + { + $pegawai = User::findOrFail($id); + + $defaultPassword = 'Mpg123!'; + $pegawai->update([ + 'password' => Hash::make($defaultPassword), + ]); + + $pegawai->tokens()->delete(); + + return redirect()->route('pegawai.show', $id) + ->with('success', "Password {$pegawai->nama_lengkap} berhasil direset ke default (Mpg123!)."); + } } diff --git a/app/Http/Controllers/PengajuanIzinController.php b/app/Http/Controllers/PengajuanIzinController.php index 01c564cc..d4eb0441 100644 --- a/app/Http/Controllers/PengajuanIzinController.php +++ b/app/Http/Controllers/PengajuanIzinController.php @@ -65,7 +65,18 @@ public function store(Request $request) $jenisIzin = JenisIzin::find($request->id_jenis_izin); if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { - $diffParams = Carbon::parse($request->tanggal_mulai, 'Asia/Jakarta')->diffInDays(Carbon::now('Asia/Jakarta')); + $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."); + } if (Carbon::parse($request->tanggal_mulai, 'Asia/Jakarta')->diffInDays(Carbon::now('Asia/Jakarta')) < 7) { return back()->withInput()->with('error', 'Pengajuan Cuti minimal H-7!'); @@ -207,7 +218,8 @@ public function approve($id) if ($izin->id_jenis_izin == 2) { $user = \App\Models\User::find($izin->id_user); $jumlahHari = Carbon::parse($izin->tanggal_mulai)->diffInDays(Carbon::parse($izin->tanggal_selesai)) + 1; - $user->decrement('sisa_cuti', $jumlahHari); + $newSisa = max(0, ($user->sisa_cuti ?? 0) - $jumlahHari); + $user->update(['sisa_cuti' => $newSisa]); } DB::commit(); diff --git a/app/Http/Controllers/SuratIzinController.php b/app/Http/Controllers/SuratIzinController.php index 3e89deb7..4ce41f6f 100644 --- a/app/Http/Controllers/SuratIzinController.php +++ b/app/Http/Controllers/SuratIzinController.php @@ -64,10 +64,13 @@ public function show($id) $canApprove = false; $tahapApproval = null; - if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isGlobalAdmin)) { + // Cegah user yang sama menandatangani di dua tahap berbeda + $sudahApprove = $surat->approvals->contains('id_approver', $user->id); + + if (!$sudahApprove && $surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isGlobalAdmin)) { $canApprove = true; $tahapApproval = 1; - } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isGlobalAdmin)) { + } elseif (!$sudahApprove && $surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isGlobalAdmin)) { $canApprove = true; $tahapApproval = 2; } @@ -95,6 +98,15 @@ public function approve(Request $request, $id) $tahap = null; + // Cegah approver yang sama menandatangani di dua posisi berbeda + $sudahApprove = ApprovalSurat::where('id_surat', $surat->id_surat) + ->where('id_approver', $user->id) + ->exists(); + + if ($sudahApprove) { + return redirect()->back()->with('error', 'Anda sudah menyetujui surat ini sebelumnya dan tidak dapat menandatangani di posisi lain.'); + } + if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isGlobalAdmin)) { $tahap = 1; } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isGlobalAdmin)) { @@ -176,6 +188,15 @@ public function reject(Request $request, $id) $tahap = null; + // Cegah approver yang sama menandatangani di dua posisi berbeda + $sudahApprove = ApprovalSurat::where('id_surat', $surat->id_surat) + ->where('id_approver', $user->id) + ->exists(); + + if ($sudahApprove) { + return redirect()->back()->with('error', 'Anda sudah menyetujui surat ini sebelumnya dan tidak dapat menolak di posisi lain.'); + } + if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isGlobalAdmin)) { $tahap = 1; } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isGlobalAdmin)) { @@ -258,7 +279,8 @@ private function prosesPersetujuanFinal(SuratIzin $surat) if ($izin->id_jenis_izin == 2) { $user = \App\Models\User::find($izin->id_user, ['*']); $jumlahHari = Carbon::parse($izin->tanggal_mulai)->diffInDays(Carbon::parse($izin->tanggal_selesai)) + 1; - $user->decrement('sisa_cuti', $jumlahHari); + $newSisa = max(0, ($user->sisa_cuti ?? 0) - $jumlahHari); + $user->update(['sisa_cuti' => $newSisa]); } } } diff --git a/app/Models/Presensi.php b/app/Models/Presensi.php index bbe600ac..aff97931 100644 --- a/app/Models/Presensi.php +++ b/app/Models/Presensi.php @@ -94,4 +94,15 @@ public function user() { return $this->belongsTo(User::class, 'id_user'); } + + public function status() + { + return $this->belongsTo(StatusPengajuan::class, 'id_status', 'id_status'); + } + + public function jadwal() + { + return $this->hasOne(JadwalKerja::class, 'id_user', 'id_user') + ->whereColumn('jadwal_kerja.tanggal', 'presensi.tanggal'); + } } \ No newline at end of file diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d4c08fec..2d1d30a7 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -20,6 +20,7 @@ public function run(): void JenisIzinSeeder::class, StatusPengajuanSeeder::class, JenisPenguranganSeeder::class, + JenisKompensasiSeeder::class, // 4. Data dummy (opsional, comment jika tidak diperlukan di production) // PresensiSeeder::class, diff --git a/database/seeders/JenisIzinSeeder.php b/database/seeders/JenisIzinSeeder.php index 1bd27cf2..8fbb2378 100644 --- a/database/seeders/JenisIzinSeeder.php +++ b/database/seeders/JenisIzinSeeder.php @@ -9,11 +9,16 @@ class JenisIzinSeeder extends Seeder { public function run(): void { - $types = ['Sakit', 'Cuti', 'Izin']; + $types = [ + ['id_jenis_izin' => 1, 'nama_izin' => 'Sakit'], + ['id_jenis_izin' => 2, 'nama_izin' => 'Cuti'], + ['id_jenis_izin' => 3, 'nama_izin' => 'Izin'], + ]; + foreach ($types as $type) { DB::table('jenis_izin')->updateOrInsert( - ['nama_izin' => $type], - ['updated_at' => now(), 'created_at' => now()] + ['id_jenis_izin' => $type['id_jenis_izin']], + ['nama_izin' => $type['nama_izin'], 'updated_at' => now(), 'created_at' => now()] ); } } diff --git a/database/seeders/JenisKompensasiSeeder.php b/database/seeders/JenisKompensasiSeeder.php new file mode 100644 index 00000000..a356c6e3 --- /dev/null +++ b/database/seeders/JenisKompensasiSeeder.php @@ -0,0 +1,27 @@ + 1, 'nama_kompensasi' => 'Uang Lembur'], + ['id_kompensasi' => 2, 'nama_kompensasi' => 'Tambahan Poin'], + ]; + + foreach ($kompensasi as $k) { + JenisKompensasi::updateOrCreate( + ['id_kompensasi' => $k['id_kompensasi']], + ['nama_kompensasi' => $k['nama_kompensasi']] + ); + } + } +} diff --git a/database/seeders/PresensiStatusSeeder.php b/database/seeders/PresensiStatusSeeder.php index 2a4d4a5f..1cc64908 100644 --- a/database/seeders/PresensiStatusSeeder.php +++ b/database/seeders/PresensiStatusSeeder.php @@ -11,20 +11,33 @@ class PresensiStatusSeeder extends Seeder public function run(): void { $statuses = [ - 'Tepat Waktu', - 'Terlambat', - 'Izin', - 'Sakit', - 'Alpha', + ['id_status' => 1, 'nama_status' => 'Tepat Waktu'], + ['id_status' => 2, 'nama_status' => 'Terlambat'], + ['id_status' => 3, 'nama_status' => 'Izin'], + ['id_status' => 4, 'nama_status' => 'Sakit'], + ['id_status' => 5, 'nama_status' => 'Alpha'], ]; foreach ($statuses as $status) { DB::table('status_presensi')->updateOrInsert( - ['nama_status' => $status], - ['created_at' => now(), 'updated_at' => now()] + ['id_status' => $status['id_status']], + ['nama_status' => $status['nama_status'], 'created_at' => now(), 'updated_at' => now()] ); } - $this->command->info('Tabel status_presensi berhasil diisi!'); + $validasiStatuses = [ + ['id_status' => 1, 'nama_status' => 'Valid'], + ['id_status' => 2, 'nama_status' => 'Pending'], + ['id_status' => 3, 'nama_status' => 'Ditolak'], + ]; + + foreach ($validasiStatuses as $status) { + DB::table('status_validasi_presensi')->updateOrInsert( + ['id_status' => $status['id_status']], + ['nama_status' => $status['nama_status'], 'created_at' => now(), 'updated_at' => now()] + ); + } + + $this->command->info('Tabel status_presensi & status_validasi_presensi berhasil diisi!'); } } diff --git a/database/seeders/StatusPengajuanSeeder.php b/database/seeders/StatusPengajuanSeeder.php index ea25e546..6d4e81c7 100644 --- a/database/seeders/StatusPengajuanSeeder.php +++ b/database/seeders/StatusPengajuanSeeder.php @@ -9,11 +9,16 @@ class StatusPengajuanSeeder extends Seeder { public function run(): void { - $statuses = ['Disetujui', 'Pending', 'Ditolak']; + $statuses = [ + ['id_status' => 1, 'nama_status' => 'Pending'], + ['id_status' => 2, 'nama_status' => 'Disetujui'], + ['id_status' => 3, 'nama_status' => 'Ditolak'], + ]; + foreach ($statuses as $status) { DB::table('status_pengajuan')->updateOrInsert( - ['nama_status' => $status], - ['updated_at' => now(), 'created_at' => now()] + ['id_status' => $status['id_status']], + ['nama_status' => $status['nama_status'], 'updated_at' => now(), 'created_at' => now()] ); } } diff --git a/resources/views/cuti/index.blade.php b/resources/views/cuti/index.blade.php index 382a19f2..90eea139 100644 --- a/resources/views/cuti/index.blade.php +++ b/resources/views/cuti/index.blade.php @@ -11,18 +11,10 @@
-
-
- - - -
- -
+ -
- + Pegawai Sisa Cuti Aksi - + @forelse ($pegawai as $p) @@ -65,147 +57,79 @@ class="block w-full pl-10 pr-3 py-2 border border-slate-300 rounded-lg focus:rin @endforelse - @if($pegawai->hasPages()) -
- {{ $pegawai->links() }} -
- @endif + -