4.4 KiB
4.4 KiB
PLAN: Biometric Handling Across Multiple Devices & Branches
This plan details how the current HRMS handles attendance when employees punch in/out on different biometric devices at different branches (having been enrolled in both).
🔍 Current System Architecture & Biometric Flow
The system handles multi-device, multi-branch biometric logins through the following workflow:
1. Data Ingestion (Branch to Central Server)
- Local Agents: Each branch has a local Windows Sync Agent (
sync-agent.exerunningbranch-agent/sync.js) connected to the local ZKTeco device via TCP. - Location Identification: The local agent's
.envis configured with a specificBRANCH_IDrepresenting that physical branch. - Push Endpoint: The agent pushes punches to the central Laravel API at
/api/zkteco/sync(handled byAttendanceSyncController@sync). - Storage: Punches are saved as raw records in the
biometric_attendancesdatabase table. The table stores:branch_id: The ID of the branch where the device/agent is running.biometric_emp_id: The employee's biometric ID.punch_time: The timestamp of the punch.punch_type: Punch state (e.g., 0 for Clock In, 1 for Clock Out).terminal_alias: Label indicating the branch (e.g.Branch Name (Agent)).sync_status: Set to'pending'upon ingestion.
2. Grouping & Work Date Mapping
When the administrator syncs biometric data (via BiometricAttendanceController@syncAll or manually in the UI):
- Shift-Aware Work Date Mapping: Raw punches are mapped to a specific work date using the employee's shift and a 4-hour grace window buffer (via
getWorkDateForPunch). This ensures that night shifts and early/late punches map to the correct operational workday rather than strictly the calendar date. - Aggregation Key: Punches are grouped across all branches by:
biometric_emp_id . '_' . $workDate
3. Chronological Selection (First/Last Punch Rule)
For the grouped records of a single employee on a given work date:
- All raw punches (regardless of the branch they originated from) are sorted chronologically by
punch_time. - Clock In: The earliest punch of that group is chosen as the official Clock In time.
- Clock Out: The latest punch of that group is chosen as the official Clock Out time (if there are multiple punches).
- Physical Branch Mapping: The final
AttendanceRecordis stored with a singlebranch_id, which is mapped to the branch of the first punch (the Clock In branch).
📋 Example Scenarios
Scenario A: Employee clocks in at Branch A and clocks out at Branch B
- Raw punches are saved in
biometric_attendances:- Punch 1:
biometric_emp_id = 201,branch_id = A, time =09:00(Clock In) - Punch 2:
biometric_emp_id = 201,branch_id = B, time =18:00(Clock Out)
- Punch 1:
- During synchronization:
- The punches are grouped together under employee
201for the work date. - Chronological sort:
09:00(from Branch A) and18:00(from Branch B). - The resulting
AttendanceRecordwill have:clock_in=09:00clock_out=18:00branch_id= Branch A (the Clock In location).
- The punches are grouped together under employee
Scenario B: Duplicate check-ins at multiple branches
- Since the database restricts duplicates based on
biometric_emp_idandpunch_time(identical timestamp down to the second), duplicate punches at different branches will both be saved in the database as long as they occur at different times. They will be resolved chronologically during grouping.
🏁 Verification Checklist
This checklist defines how to verify that this multi-branch biometric logic is functioning as designed:
- Ingestion Verification: Verify that raw punches from two different branch agents (e.g. Branch A and Branch B) are both successfully written to the
biometric_attendancestable with correctbranch_ids. - Grouping Verification: Run
BiometricAttendanceController@syncAll(or simulate via API) and verify that the punches from different branches are grouped into a singleAttendanceRecord. - Chronological Order Verification: Ensure the earliest timestamp becomes the
clock_inand the latest becomes theclock_out, even if they occurred on different devices/branches. - Branch Assignment Verification: Verify that the
branch_idon the generatedAttendanceRecordmatches the branch of the earliest punch (the clock-in location).