feat(attendance): add geofence monitoring and policy controls
This commit is contained in:
@@ -41,6 +41,7 @@ The gateway keeps backend services separate internally, but frontend should trea
|
||||
- `GET /client/coverage`
|
||||
- `GET /client/coverage/stats`
|
||||
- `GET /client/coverage/core-team`
|
||||
- `GET /client/coverage/incidents`
|
||||
- `GET /client/hubs`
|
||||
- `GET /client/cost-centers`
|
||||
- `GET /client/vendors`
|
||||
@@ -114,6 +115,7 @@ The gateway keeps backend services separate internally, but frontend should trea
|
||||
- `POST /staff/profile/setup`
|
||||
- `POST /staff/clock-in`
|
||||
- `POST /staff/clock-out`
|
||||
- `POST /staff/location-streams`
|
||||
- `PUT /staff/availability`
|
||||
- `POST /staff/availability/quick-set`
|
||||
- `POST /staff/shifts/:shiftId/apply`
|
||||
@@ -159,7 +161,87 @@ These are exposed as direct unified aliases even though they are backed by `core
|
||||
- `roleId` on `POST /staff/shifts/:shiftId/apply` is the concrete `shift_roles.id` for that shift, not the catalog role definition id.
|
||||
- `accountType` on `POST /staff/profile/bank-accounts` accepts either lowercase or uppercase and is normalized by the backend.
|
||||
- File upload routes return a storage path plus a signed URL. Frontend uploads the file directly to storage using that URL.
|
||||
- Verification routes are durable in the v2 backend and were validated live through document, attire, and certificate upload flows.
|
||||
- Verification upload and review routes are live and were validated through document, attire, and certificate flows. Do not rely on long-lived verification history durability until the dedicated persistence slice is landed in `core-api-v2`.
|
||||
- Attendance policy is explicit. Reads now expose `clockInMode` and `allowClockInOverride`.
|
||||
- `clockInMode` values are:
|
||||
- `NFC_REQUIRED`
|
||||
- `GEO_REQUIRED`
|
||||
- `EITHER`
|
||||
- For `POST /staff/clock-in` and `POST /staff/clock-out`:
|
||||
- send `nfcTagId` when clocking with NFC
|
||||
- send `latitude`, `longitude`, and `accuracyMeters` when clocking with geolocation
|
||||
- send `overrideReason` only when the worker is bypassing a geofence failure and the shift/hub allows overrides
|
||||
- `POST /staff/location-streams` is for the background tracking loop after a worker is already clocked in.
|
||||
- `GET /client/coverage/incidents` is the review feed for geofence breaches, missing-location batches, and clock-in overrides.
|
||||
- `POST /client/coverage/late-workers/:assignmentId/cancel` is the client-side recovery action when lateness is confirmed by incident evidence or elapsed grace time.
|
||||
- Raw location stream payloads are stored in the private v2 bucket; SQL only stores the summary and incident index.
|
||||
|
||||
### Clock-in request example
|
||||
|
||||
```json
|
||||
{
|
||||
"shiftId": "uuid",
|
||||
"sourceType": "GEO",
|
||||
"deviceId": "iphone-15-pro",
|
||||
"latitude": 37.4221,
|
||||
"longitude": -122.0841,
|
||||
"accuracyMeters": 12,
|
||||
"overrideReason": "Parking garage entrance is outside the marked hub geofence",
|
||||
"capturedAt": "2026-03-16T09:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Location-stream batch example
|
||||
|
||||
```json
|
||||
{
|
||||
"shiftId": "uuid",
|
||||
"sourceType": "GEO",
|
||||
"deviceId": "iphone-15-pro",
|
||||
"points": [
|
||||
{
|
||||
"capturedAt": "2026-03-16T09:15:00.000Z",
|
||||
"latitude": 37.4221,
|
||||
"longitude": -122.0841,
|
||||
"accuracyMeters": 12
|
||||
},
|
||||
{
|
||||
"capturedAt": "2026-03-16T09:30:00.000Z",
|
||||
"latitude": 37.4301,
|
||||
"longitude": -122.0761,
|
||||
"accuracyMeters": 20
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"source": "background-workmanager"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Coverage incidents response shape
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"incidentId": "uuid",
|
||||
"assignmentId": "uuid",
|
||||
"shiftId": "uuid",
|
||||
"staffName": "Ana Barista",
|
||||
"incidentType": "OUTSIDE_GEOFENCE",
|
||||
"severity": "CRITICAL",
|
||||
"status": "OPEN",
|
||||
"clockInMode": "GEO_REQUIRED",
|
||||
"overrideReason": null,
|
||||
"message": "Worker drifted outside hub geofence during active monitoring",
|
||||
"distanceToClockPointMeters": 910,
|
||||
"withinGeofence": false,
|
||||
"occurredAt": "2026-03-16T09:30:00.000Z"
|
||||
}
|
||||
],
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 6) Why this shape
|
||||
|
||||
|
||||
Reference in New Issue
Block a user