has('status') && $request->status != '') { $query->byStatus($request->status); } // Filter berdasarkan teknisi jika ada if ($request->has('id_teknisi') && $request->id_teknisi != '') { $query->where('id_teknisi', $request->id_teknisi); } // Filter berdasarkan tanggal if ($request->has('tanggal_dari') && $request->tanggal_dari != '') { $query->whereDate('tanggal_kasbon', '>=', $request->tanggal_dari); } if ($request->has('tanggal_sampai') && $request->tanggal_sampai != '') { $query->whereDate('tanggal_kasbon', '<=', $request->tanggal_sampai); } // Sorting $sortBy = $request->get('sort_by', 'created_at'); $sortOrder = $request->get('sort_order', 'desc'); $query->orderBy($sortBy, $sortOrder); // Pagination $perPage = $request->get('per_page', 15); $kasbons = $query->paginate($perPage); // Statistik (Disederhanakan untuk efisiensi) $totalKasbon = Kasbon::count(); $totalNominal = Kasbon::sum('jumlah_kasbon'); $kasbonLunas = Kasbon::where('status', 'lunas')->count(); $kasbonBelumLunas = Kasbon::where('status', 'belum_lunas')->count(); $totalNominalBelumLunas = Kasbon::where('status', 'belum_lunas')->sum('jumlah_kasbon'); // Daftar teknisi untuk dropdown modal & filter $teknisis = Teknisi::orderBy('nama')->get(); // Untuk API response if ($request->expectsJson()) { return response()->json([ 'success' => true, 'data' => $kasbons, 'message' => 'Data kasbon berhasil diambil' ]); } return view('Admin.Gaji.Kasbon', compact( 'kasbons', 'totalKasbon', 'totalNominal', 'kasbonLunas', 'kasbonBelumLunas', 'totalNominalBelumLunas', 'teknisis' )); } /** * Show the form for creating a new resource. * * @return View */ public function create(): View { $statusOptions = Kasbon::getStatusOptions(); return view('Admin.Gaji.create-kasbon', compact('statusOptions')); // DIPERBAIKI: path view } /** * Store a newly created resource in storage. * * @param Request $request * @return RedirectResponse|JsonResponse */ public function store(Request $request) { \Illuminate\Support\Facades\Log::info('Kasbon Store Request Received', $request->all()); $validator = Validator::make($request->all(), [ 'id_teknisi' => 'required|integer|min:1', 'jumlah_kasbon' => 'required|numeric|min:0', 'tanggal_kasbon' => 'required|date', 'status' => 'nullable|in:lunas,belum_lunas', 'keterangan' => 'nullable|string|max:100' ], [ 'id_teknisi.required' => 'ID Teknisi harus diisi', 'id_teknisi.integer' => 'ID Teknisi harus berupa angka', 'jumlah_kasbon.required' => 'Jumlah kasbon harus diisi', 'jumlah_kasbon.numeric' => 'Jumlah kasbon harus berupa angka', 'jumlah_kasbon.min' => 'Jumlah kasbon minimal 0', 'tanggal_kasbon.required' => 'Tanggal kasbon harus diisi', 'tanggal_kasbon.date' => 'Format tanggal kasbon tidak valid', 'status.required' => 'Status harus dipilih', 'status.in' => 'Status harus lunas atau belum_lunas', 'keterangan.max' => 'Keterangan maksimal 500 karakter' ]); $validator->after(function ($validator) use ($request) { $jumlah = (float) $request->input('jumlah_kasbon'); $idTeknisi = $request->input('id_teknisi'); $tanggal = $request->input('tanggal_kasbon'); if ($jumlah > 0 && $jumlah <= 500000) { // Aturan 1: Minimal Rp 200.000 untuk Kasbon Rutin if ($jumlah < 200000) { $validator->errors()->add('jumlah_kasbon', 'Jumlah kasbon rutin minimal Rp 200.000. Di atas Rp 500.000 dianggap pinjaman besar.'); } // Aturan 2: Maksimal 2 kali kasbon rutin dalam 1 minggu kalender if ($idTeknisi && $tanggal) { try { $date = Carbon::parse($tanggal); $startOfWeek = $date->copy()->startOfWeek()->toDateString(); $endOfWeek = $date->copy()->endOfWeek()->toDateString(); $kasbonCount = Kasbon::where('id_teknisi', $idTeknisi) ->where('jumlah_kasbon', '<=', 500000) ->whereBetween('tanggal_kasbon', [$startOfWeek, $endOfWeek]) ->count(); if ($kasbonCount >= 2) { $validator->errors()->add('tanggal_kasbon', 'Teknisi ini sudah mencapai batas maksimal 2 kali kasbon rutin dalam minggu ini (Senin - Minggu).'); } } catch (\Exception $e) { // Let built-in date validator handle formatting errors } } } }); if ($validator->fails()) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'errors' => $validator->errors(), 'message' => $validator->errors()->first() ], 422); } return redirect()->back()->withErrors($validator)->withInput(); } try { $data = $validator->validated(); // Map keterangan ke keperluan (database schema) if (isset($data['keterangan'])) { $data['keperluan'] = $data['keterangan']; unset($data['keterangan']); } // Set default status jika tidak ada if (!isset($data['status'])) { $data['status'] = Kasbon::STATUS_BELUM_LUNAS; } $kasbon = Kasbon::create($data); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'data' => $kasbon, 'message' => 'Kasbon berhasil ditambahkan' ], 201); } return redirect()->route('kasbon.index')->with('success', 'Kasbon berhasil ditambahkan'); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error('Kasbon Store Error', [ 'message' => $e->getMessage(), 'data' => $request->all(), 'trace' => $e->getTraceAsString() ]); if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Terjadi kesalahan saat menyimpan data: ' . $e->getMessage() ], 500); } return redirect()->back()->with('error', 'Terjadi kesalahan saat menyimpan data: ' . $e->getMessage())->withInput(); } } /** * Display the specified resource. * * @param int $id * @param Request $request * @return View|JsonResponse */ public function show(int $id, Request $request) { try { $kasbon = Kasbon::findOrFail($id); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'data' => $kasbon, 'message' => 'Data kasbon berhasil diambil' ]); } return view('Admin.Gaji.show-kasbon', compact('kasbon')); // DIPERBAIKI: path view } catch (\Exception $e) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Kasbon tidak ditemukan' ], 404); } return redirect()->route('kasbon.index')->with('error', 'Kasbon tidak ditemukan'); } } /** * Show the form for editing the specified resource. * * @param int $id * @return View|RedirectResponse */ public function edit(int $id, Request $request) { try { $kasbon = Kasbon::with('teknisi')->findOrFail($id); $statusOptions = Kasbon::getStatusOptions(); $teknisis = Teknisi::orderBy('nama')->get(); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'kasbon' => $kasbon, 'statusOptions' => $statusOptions, 'teknisis' => $teknisis ]); } return view('Admin.Gaji.edit-kasbon', compact('kasbon', 'statusOptions', 'teknisis')); } catch (\Exception $e) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Kasbon tidak ditemukan' ], 404); } return redirect()->route('kasbon.index')->with('error', 'Kasbon tidak ditemukan'); } } /** * Update the specified resource in storage. * * @param Request $request * @param int $id * @return RedirectResponse|JsonResponse */ public function update(Request $request, int $id) { $validator = Validator::make($request->all(), [ 'id_teknisi' => 'required|integer|min:1', 'jumlah_kasbon' => 'required|numeric|min:0', 'tanggal_kasbon' => 'required|date', 'status' => 'nullable|in:lunas,belum_lunas', 'keterangan' => 'nullable|string|max:100' ], [ 'id_teknisi.required' => 'ID Teknisi harus diisi', 'id_teknisi.integer' => 'ID Teknisi harus berupa angka', 'jumlah_kasbon.required' => 'Jumlah kasbon harus diisi', 'jumlah_kasbon.numeric' => 'Jumlah kasbon harus berupa angka', 'jumlah_kasbon.min' => 'Jumlah kasbon minimal 0', 'tanggal_kasbon.required' => 'Tanggal kasbon harus diisi', 'tanggal_kasbon.date' => 'Format tanggal kasbon tidak valid', 'status.required' => 'Status harus dipilih', 'status.in' => 'Status harus lunas atau belum_lunas', 'keterangan.max' => 'Keterangan maksimal 500 karakter' ]); $validator->after(function ($validator) use ($request, $id) { $jumlah = (float) $request->input('jumlah_kasbon'); $idTeknisi = $request->input('id_teknisi'); $tanggal = $request->input('tanggal_kasbon'); if ($jumlah > 0 && $jumlah <= 500000) { // Aturan 1: Minimal Rp 200.000 untuk Kasbon Rutin if ($jumlah < 200000) { $validator->errors()->add('jumlah_kasbon', 'Jumlah kasbon rutin minimal Rp 200.000. Di atas Rp 500.000 dianggap pinjaman besar.'); } // Aturan 2: Maksimal 2 kali kasbon rutin dalam 1 minggu kalender if ($idTeknisi && $tanggal) { try { $date = Carbon::parse($tanggal); $startOfWeek = $date->copy()->startOfWeek()->toDateString(); $endOfWeek = $date->copy()->endOfWeek()->toDateString(); $kasbonCount = Kasbon::where('id_teknisi', $idTeknisi) ->where('id_kasbon', '!=', $id) ->where('jumlah_kasbon', '<=', 500000) ->whereBetween('tanggal_kasbon', [$startOfWeek, $endOfWeek]) ->count(); if ($kasbonCount >= 2) { $validator->errors()->add('tanggal_kasbon', 'Teknisi ini sudah mencapai batas maksimal 2 kali kasbon rutin dalam minggu ini (Senin - Minggu).'); } } catch (\Exception $e) { // Let built-in date validator handle formatting errors } } } }); if ($validator->fails()) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'errors' => $validator->errors(), 'message' => $validator->errors()->first() ], 422); } return redirect()->back()->withErrors($validator)->withInput(); } try { $kasbon = Kasbon::findOrFail($id); $data = $validator->validated(); // Map keterangan ke keperluan if (isset($data['keterangan'])) { $data['keperluan'] = $data['keterangan']; unset($data['keterangan']); } $kasbon->update($data); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'data' => $kasbon, 'message' => 'Kasbon berhasil diupdate' ]); } return redirect()->route('kasbon.index')->with('success', 'Kasbon berhasil diupdate'); } catch (\Exception $e) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Kasbon tidak ditemukan atau terjadi kesalahan' ], 404); } return redirect()->route('kasbon.index')->with('error', 'Kasbon tidak ditemukan atau terjadi kesalahan'); } } /** * Remove the specified resource from storage. * * @param Request $request * @param int $id * @return RedirectResponse|JsonResponse */ public function destroy(Request $request, int $id) { try { $kasbon = Kasbon::findOrFail($id); $kasbon->delete(); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'message' => 'Kasbon berhasil dihapus' ]); } return redirect()->route('kasbon.index')->with('success', 'Kasbon berhasil dihapus'); } catch (\Exception $e) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Kasbon tidak ditemukan atau terjadi kesalahan' ], 404); } return redirect()->route('kasbon.index')->with('error', 'Kasbon tidak ditemukan atau terjadi kesalahan'); } } /** * Get kasbon statistics * * @param Request $request * @return JsonResponse */ public function statistics(Request $request): JsonResponse { try { $totalKasbon = Kasbon::count(); $totalJumlahKasbon = Kasbon::sum('jumlah_kasbon'); $kasbonLunas = Kasbon::lunas()->count(); $kasbonBelumLunas = Kasbon::belumLunas()->count(); $totalJumlahLunas = Kasbon::lunas()->sum('jumlah_kasbon'); $totalJumlahBelumLunas = Kasbon::belumLunas()->sum('jumlah_kasbon'); return response()->json([ 'success' => true, 'data' => [ 'total_kasbon' => $totalKasbon, 'total_jumlah_kasbon' => $totalJumlahKasbon, 'kasbon_lunas' => $kasbonLunas, 'kasbon_belum_lunas' => $kasbonBelumLunas, 'total_jumlah_lunas' => $totalJumlahLunas, 'total_jumlah_belum_lunas' => $totalJumlahBelumLunas, ], 'message' => 'Statistik kasbon berhasil diambil' ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Terjadi kesalahan saat mengambil statistik' ], 500); } } /** * Update status kasbon to lunas * * @param Request $request * @param int $id * @return RedirectResponse|JsonResponse */ public function markAsLunas(Request $request, int $id) { try { $kasbon = Kasbon::findOrFail($id); $kasbon->update(['status' => Kasbon::STATUS_LUNAS]); if ($request->expectsJson()) { return response()->json([ 'success' => true, 'data' => $kasbon, 'message' => 'Kasbon berhasil diubah menjadi lunas' ]); } return redirect()->back()->with('success', 'Kasbon berhasil diubah menjadi lunas'); } catch (\Exception $e) { if ($request->expectsJson()) { return response()->json([ 'success' => false, 'message' => 'Kasbon tidak ditemukan' ], 404); } return redirect()->back()->with('error', 'Kasbon tidak ditemukan'); } } }