Files
HRM-System/docs/PLAN-different-biometrics.md

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.exe running branch-agent/sync.js) connected to the local ZKTeco device via TCP.
  • Location Identification: The local agent's .env is configured with a specific BRANCH_ID representing that physical branch.
  • Push Endpoint: The agent pushes punches to the central Laravel API at /api/zkteco/sync (handled by AttendanceSyncController@sync).
  • Storage: Punches are saved as raw records in the biometric_attendances database 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 AttendanceRecord is stored with a single branch_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

  1. 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)
  2. During synchronization:
    • The punches are grouped together under employee 201 for the work date.
    • Chronological sort: 09:00 (from Branch A) and 18:00 (from Branch B).
    • The resulting AttendanceRecord will have:
      • clock_in = 09:00
      • clock_out = 18:00
      • branch_id = Branch A (the Clock In location).

Scenario B: Duplicate check-ins at multiple branches

  • Since the database restricts duplicates based on biometric_emp_id and punch_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_attendances table with correct branch_ids.
  • Grouping Verification: Run BiometricAttendanceController@syncAll (or simulate via API) and verify that the punches from different branches are grouped into a single AttendanceRecord.
  • Chronological Order Verification: Ensure the earliest timestamp becomes the clock_in and the latest becomes the clock_out, even if they occurred on different devices/branches.
  • Branch Assignment Verification: Verify that the branch_id on the generated AttendanceRecord matches the branch of the earliest punch (the clock-in location).