diff --git a/app/Http/Requests/Admin/HR/LeaveRequestRequest.php b/app/Http/Requests/Admin/HR/LeaveRequestRequest.php index afca887..66c06d8 100644 --- a/app/Http/Requests/Admin/HR/LeaveRequestRequest.php +++ b/app/Http/Requests/Admin/HR/LeaveRequestRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests\Admin\HR; +use App\Models\LeaveRequest; use Illuminate\Foundation\Http\FormRequest; class LeaveRequestRequest extends FormRequest @@ -13,8 +14,33 @@ public function authorize(): bool public function rules(): array { + $leaveRequest = $this->route('leaveRequest'); + return [ - 'start_date' => ['required', 'date', 'after_or_equal:today'], + 'start_date' => [ + 'required', + 'date', + 'after:today', + function ($attribute, $value, $fail) use ($leaveRequest) { + $employee = $this->user()->employee; + + if (! $employee) { + return; + } + + $query = LeaveRequest::where('employee_id', $employee->id) + ->where('start_date', $value) + ->where('status', '!=', 'cancelled'); + + if ($leaveRequest) { + $query->where('id', '!=', $leaveRequest->id); + } + + if ($query->exists()) { + $fail('Anda sudah mengajukan cuti pada tanggal ini.'); + } + }, + ], 'end_date' => ['required', 'date', 'after_or_equal:start_date'], ]; } diff --git a/docs/leave-request-validation-fix.md b/docs/leave-request-validation-fix.md new file mode 100644 index 0000000..9f0cb4b --- /dev/null +++ b/docs/leave-request-validation-fix.md @@ -0,0 +1,35 @@ +# Fix: Validasi Pengajuan Cuti & Timezone + +## Tujuan +Memperbaiki 3 masalah pada modul pengajuan cuti: +1. Timezone bug — tanggal yang dipilih mundur 1 hari (user pilih 8, ke-save 7) +2. Tidak bisa mengajukan cuti untuk hari ini +3. Tidak bisa mengajukan cuti di tanggal yang sama lebih dari 1x + +## File yang Dibaca +- `resources/js/pages/admin/hr/leave-request/index.tsx` → Frontend form pengajuan cuti +- `app/Http/Requests/Admin/HR/LeaveRequestRequest.php` → Validasi backend +- `app/Services/Admin/HR/LeaveRequestService.php` → Service logic +- `app/Models/LeaveRequest.php` → Model dengan cast `date:Y-m-d` + +## Pola yang Ditemukan +- Frontend menggunakan `toISOString().split('T')[0]` untuk format tanggal → **PROBLEM**: `toISOString()` konversi ke UTC, sehingga waktu WIB (UTC+7) mundur 1 hari +- Validasi backend hanya `after_or_equal:today` → bisa submit untuk hari ini +- Tidak ada pengecekan duplikat tanggal cuti per employee + +## Perubahan + +### 1. Frontend — Fix Timezone (`index.tsx`) +- Tambah helper function `toLocalDateString()` yang menggunakan `getFullYear()`, `getMonth()`, `getDate()` (local time) alih-alih `toISOString()` (UTC) +- Ganti semua `toISOString().split('T')[0]` → `toLocalDateString(...)` + +### 2. Backend — Validasi Stricter (`LeaveRequestRequest.php`) +- `start_date`: `after_or_equal:today` → `after:today` (tidak bisa hari ini) +- Tambah custom closure rule: cek apakah employee sudah punya leave request dengan `start_date` yang sama (exclude status `cancelled` dan record sendiri saat update) +- Error message: "Anda sudah mengajukan cuti pada tanggal ini." + +## Cara Kerja +1. User pilih tanggal 8 Agustus → DatePicker simpan sebagai `Date` object (local time) +2. `toLocalDateString()` format sebagai `"2026-08-08"` tanpa konversi UTC +3. Backend validasi: pastikan tanggal > hari ini + belum ada cuti di tanggal yang sama +4. Service simpan ke DB dengan cast `date:Y-m-d` → tetap `"2026-08-08"` diff --git a/resources/js/pages/admin/hr/leave-request/index.tsx b/resources/js/pages/admin/hr/leave-request/index.tsx index 7336f23..2b847c7 100644 --- a/resources/js/pages/admin/hr/leave-request/index.tsx +++ b/resources/js/pages/admin/hr/leave-request/index.tsx @@ -52,6 +52,13 @@ type Props = { }; }; +function toLocalDateString(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) { const { can } = useCan(); const [createOpen, setCreateOpen] = useState(false); @@ -225,9 +232,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption name="start_date" value={ startDate - ? startDate - .toISOString() - .split('T')[0] + ? toLocalDateString(startDate) : '' } /> @@ -248,9 +253,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption name="end_date" value={ endDate - ? endDate - .toISOString() - .split('T')[0] + ? toLocalDateString(endDate) : '' } /> @@ -312,9 +315,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption name="start_date" value={ editingStartDate - ? editingStartDate - .toISOString() - .split('T')[0] + ? toLocalDateString(editingStartDate) : '' } /> @@ -337,9 +338,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption name="end_date" value={ editingEndDate - ? editingEndDate - .toISOString() - .split('T')[0] + ? toLocalDateString(editingEndDate) : '' } />