search) { $query->whereHas('employee', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%') ->orWhere('nip', 'like', '%' . $request->search . '%'); }); } if ($request->period) { $query->where('period', $request->period); } if ($request->status) { $query->where('status', $request->status); } $payrolls = $query->latest()->get()->map(fn($p) => [ 'id' => $p->id, 'period' => $p->period, 'net_salary' => $p->net_salary, 'status' => $p->status, 'employee_name' => $p->employee?->name ?? '-', 'employee_nip' => $p->employee?->nip ?? '-', ]); return Inertia::render('admin/payrolls/index', [ 'payrolls' => $payrolls, 'filters' => $request->only(['search', 'period', 'status']), ]); } public function create() { $employees = Employee::with(['position', 'department']) ->orderBy('name') ->get() ->map(fn($e) => [ 'id' => $e->id, 'name' => $e->name, 'nip' => $e->nip, 'position' => [ 'id' => $e->position?->id, 'name' => $e->position?->name, 'basic_salary' => $e->position?->basic_salary ?? 0, ], 'department' => [ 'name' => $e->department?->name, ], ]); return Inertia::render('admin/payrolls/create', [ 'employees' => $employees, ]); } public function store(Request $request) { $validated = $request->validate([ 'employee_id' => 'required|exists:employees,id', 'period' => [ 'required', 'date_format:Y-m', Rule::unique('payrolls')->where(fn ($query) => $query->where('employee_id', $request->employee_id)), ], 'basic_salary' => 'required|integer|min:0', 'details' => 'nullable|array', 'details.*.name' => 'required|string|max:100', 'details.*.type' => 'required|in:bonus,deduction', 'details.*.amount' => 'required|integer|min:0', ], [ 'employee_id.required' => 'Karyawan wajib dipilih.', 'employee_id.exists' => 'Karyawan tidak ditemukan.', 'period.required' => 'Periode gaji wajib diisi.', 'period.date_format' => 'Format periode harus YYYY-MM.', 'period.unique' => 'Payroll karyawan untuk periode ini sudah ada.', 'basic_salary.required' => 'Gaji pokok wajib diisi.', 'basic_salary.integer' => 'Gaji pokok harus berupa angka bulat.', 'basic_salary.min' => 'Gaji pokok tidak boleh negatif.', 'details.array' => 'Detail komponen harus berupa array.', 'details.*.name.required' => 'Nama komponen wajib diisi.', 'details.*.name.max' => 'Nama komponen maksimal 100 karakter.', 'details.*.type.required' => 'Tipe komponen wajib dipilih.', 'details.*.type.in' => 'Tipe komponen hanya boleh bonus atau potongan.', 'details.*.amount.required' => 'Nominal komponen wajib diisi.', 'details.*.amount.integer' => 'Nominal komponen harus berupa angka bulat.', 'details.*.amount.min' => 'Nominal komponen tidak boleh negatif.', ]); $net = $validated['basic_salary']; foreach ($validated['details'] ?? [] as $item) { $net += $item['type'] === 'bonus' ? $item['amount'] : -$item['amount']; } Payroll::create([ 'employee_id' => $validated['employee_id'], 'period' => $validated['period'], 'basic_salary' => $validated['basic_salary'], 'details' => $validated['details'] ?? [], 'net_salary' => max(0, $net), 'status' => 'pending', ]); return redirect('/admin/payrolls') ->with('success', 'Payroll berhasil di-generate.'); } public function show(Payroll $payroll) { $payroll->load('employee.position', 'employee.department'); return Inertia::render('admin/payrolls/show', [ 'payroll' => [ 'id' => $payroll->id, 'period' => $payroll->period, 'basic_salary' => $payroll->basic_salary, 'details' => $payroll->details ?? [], 'net_salary' => $payroll->net_salary, 'status' => $payroll->status, 'created_at' => $payroll->created_at->format('d F Y'), 'employee' => [ 'name' => $payroll->employee?->name, 'nip' => $payroll->employee?->nip, 'position' => $payroll->employee?->position?->name, 'department' => $payroll->employee?->department?->name, ], ], ]); } public function edit(Payroll $payroll) { $payroll->load('employee.position', 'employee.department'); return Inertia::render('admin/payrolls/edit', [ 'payroll' => [ 'id' => $payroll->id, 'period' => $payroll->period, 'basic_salary' => $payroll->basic_salary, 'details' => $payroll->details ?? [], 'net_salary' => $payroll->net_salary, 'status' => $payroll->status, 'employee' => [ 'id' => $payroll->employee?->id, 'name' => $payroll->employee?->name, 'nip' => $payroll->employee?->nip, 'position' => $payroll->employee?->position?->name, 'department' => $payroll->employee?->department?->name, ], ], ]); } public function update(Request $request, Payroll $payroll) { $validated = $request->validate([ 'basic_salary' => 'required|integer|min:0', 'details' => 'nullable|array', 'details.*.name' => 'required|string|max:100', 'details.*.type' => 'required|in:bonus,deduction', 'details.*.amount' => 'required|integer|min:0', ], [ 'basic_salary.required' => 'Gaji pokok wajib diisi.', 'basic_salary.integer' => 'Gaji pokok harus berupa angka bulat.', 'basic_salary.min' => 'Gaji pokok tidak boleh negatif.', 'details.*.name.required' => 'Nama komponen wajib diisi.', 'details.*.type.required' => 'Tipe komponen wajib dipilih.', 'details.*.type.in' => 'Tipe hanya boleh bonus atau potongan.', 'details.*.amount.required' => 'Nominal wajib diisi.', 'details.*.amount.min' => 'Nominal tidak boleh negatif.', ]); $net = $validated['basic_salary']; foreach ($validated['details'] ?? [] as $item) { $net += $item['type'] === 'bonus' ? $item['amount'] : -$item['amount']; } $payroll->update([ 'basic_salary' => $validated['basic_salary'], 'details' => $validated['details'] ?? [], 'net_salary' => max(0, $net), ]); return redirect()->route('admin.payrolls.show', $payroll) ->with('success', 'Data payroll berhasil diperbarui.'); } /** * Update hanya field status payroll (menunggu / telah_dikirim). */ public function updateStatus(Request $request, Payroll $payroll) { $validated = $request->validate([ 'status' => ['required', Rule::in(['pending', 'paid'])], ], [ 'status.required' => 'Status wajib dipilih.', 'status.in' => 'Status hanya boleh pending atau paid.', ]); $payroll->update(['status' => $validated['status']]); $label = $validated['status'] === 'paid' ? 'Telah Dikirim' : 'Menunggu'; return back()->with('success', "Status payroll diubah menjadi '{$label}'."); } }