68 lines
3.5 KiB
Markdown
68 lines
3.5 KiB
Markdown
# PLAN: Shift-Aware Biometric Routing and Double-Punch Protection
|
|
|
|
This plan documents the integration of shift-aware punch classification (In/Out routing for single punches) and the prevention of duplicate check-ins (double-punch filtering).
|
|
|
|
---
|
|
|
|
## 🏗️ Target Files
|
|
|
|
1. **Backend Controller:** `app/Http/Controllers/BiometricAttendanceController.php`
|
|
2. **Frontend UI Page:** `resources/js/pages/hr/biometric-attendance/index.tsx`
|
|
|
|
---
|
|
|
|
## 🛠️ Proposed Implementation Steps
|
|
|
|
### Phase 1: Main Dashboard UI Updates (Frontend)
|
|
Modify [index.tsx](file:///Users/dvapp/Documents/HRM/resources/js/pages/hr/biometric-attendance/index.tsx):
|
|
- Remove the `Total Entries` column from the `columns` array.
|
|
- Ensure the table renders clean and responsive rows without the total entries column.
|
|
|
|
### Phase 2: Double-Punch Filtering Logic (Backend)
|
|
Modify [BiometricAttendanceController.php](file:///Users/dvapp/Documents/HRM/app/Http/Controllers/BiometricAttendanceController.php):
|
|
- Add a helper method to filter out raw punches occurring within 60 seconds of the previous valid punch:
|
|
```php
|
|
private static function filterDoublePunches($dayEntries) {
|
|
$sorted = $dayEntries->sortBy('punch_time');
|
|
$filtered = collect();
|
|
|
|
foreach ($sorted as $entry) {
|
|
if ($filtered->isEmpty()) {
|
|
$filtered->push($entry);
|
|
} else {
|
|
$lastValid = $filtered->last();
|
|
$diffInSeconds = $entry->punch_time->diffInSeconds($lastValid->punch_time);
|
|
if ($diffInSeconds > 60) {
|
|
$filtered->push($entry);
|
|
}
|
|
}
|
|
}
|
|
return $filtered;
|
|
}
|
|
```
|
|
|
|
### Phase 3: Shift-Aware Single Punch Routing (Backend)
|
|
Modify [BiometricAttendanceController.php](file:///Users/dvapp/Documents/HRM/app/Http/Controllers/BiometricAttendanceController.php):
|
|
- Refactor the grouping map in `index` and the processing loop in `syncAll` to:
|
|
1. Filter out double-punches first using `filterDoublePunches`.
|
|
2. If the remaining entries count is exactly **1**:
|
|
- Check if the employee has a shift.
|
|
- If so, calculate the expected shift `start_time` and `end_time` (accounting for night shifts) on that work date.
|
|
- Calculate the distance from the punch to the shift start and shift end.
|
|
- If the punch is closer to the shift **end time**, set `clock_out` as the punch time, and `clock_in` as `null` (or `-`).
|
|
- Otherwise, set `clock_in` as the punch time, and `clock_out` as `null`.
|
|
- If no shift is found, default to treating the single punch as `clock_in`.
|
|
3. If the remaining entries count is **2 or more**:
|
|
- Treat the earliest punch as `clock_in` and the latest as `clock_out`.
|
|
|
|
---
|
|
|
|
## 🏁 Verification Checklist
|
|
|
|
- [ ] **Verify Column Removal:** Open the main biometric attendance page and check that the "Total Entries" column is removed.
|
|
- [ ] **Verify Double-Punch Filtering:** Push mock data containing two punches for the same employee 30 seconds apart (e.g. `08:30:00` and `08:30:30`). Verify that the dashboard groups this as a single punch at `08:30:00` and treats it as a Clock In.
|
|
- [ ] **Verify Shift-Aware Routing (Clock In):** Push a single punch at `08:45:00` for an employee with a `09:00 - 18:00` shift. Verify that the system assigns it as **Clock In**.
|
|
- [ ] **Verify Shift-Aware Routing (Clock Out):** Push a single punch at `17:45:00` for the same employee. Verify that the system assigns it as **Clock Out** and leaves Clock In blank.
|
|
- [ ] **Vite compilation:** Run `npm run build` to verify React compilation.
|
|
- [ ] **PHP syntax check:** Run `php -l` on the controller.
|