feat: enhance leave request validation and fix timezone issues
This commit is contained in:
parent
14140c39cc
commit
4b70da6715
@ -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'],
|
||||
];
|
||||
}
|
||||
|
||||
35
docs/leave-request-validation-fix.md
Normal file
35
docs/leave-request-validation-fix.md
Normal file
@ -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"`
|
||||
@ -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)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user