226 lines
8.7 KiB
PHP
226 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Employee;
|
|
use App\Models\Payroll;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\Rule;
|
|
use Inertia\Inertia;
|
|
|
|
class PayrollController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$query = Payroll::with('employee');
|
|
|
|
if ($request->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}'.");
|
|
}
|
|
}
|