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;
|
namespace App\Http\Requests\Admin\HR;
|
||||||
|
|
||||||
|
use App\Models\LeaveRequest;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class LeaveRequestRequest extends FormRequest
|
class LeaveRequestRequest extends FormRequest
|
||||||
@ -13,8 +14,33 @@ public function authorize(): bool
|
|||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
$leaveRequest = $this->route('leaveRequest');
|
||||||
|
|
||||||
return [
|
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'],
|
'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) {
|
export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
@ -225,9 +232,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
|
|||||||
name="start_date"
|
name="start_date"
|
||||||
value={
|
value={
|
||||||
startDate
|
startDate
|
||||||
? startDate
|
? toLocalDateString(startDate)
|
||||||
.toISOString()
|
|
||||||
.split('T')[0]
|
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -248,9 +253,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
|
|||||||
name="end_date"
|
name="end_date"
|
||||||
value={
|
value={
|
||||||
endDate
|
endDate
|
||||||
? endDate
|
? toLocalDateString(endDate)
|
||||||
.toISOString()
|
|
||||||
.split('T')[0]
|
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -312,9 +315,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
|
|||||||
name="start_date"
|
name="start_date"
|
||||||
value={
|
value={
|
||||||
editingStartDate
|
editingStartDate
|
||||||
? editingStartDate
|
? toLocalDateString(editingStartDate)
|
||||||
.toISOString()
|
|
||||||
.split('T')[0]
|
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -337,9 +338,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
|
|||||||
name="end_date"
|
name="end_date"
|
||||||
value={
|
value={
|
||||||
editingEndDate
|
editingEndDate
|
||||||
? editingEndDate
|
? toLocalDateString(editingEndDate)
|
||||||
.toISOString()
|
|
||||||
.split('T')[0]
|
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user