Field Service
Overview
The Field Service handles worker clock in/out, timesheet management, and GPS validation for field operations.
Base URL
- Development
- Production
http://localhost:5003/api
https://field.precisionsiteservices.com/api
Authentication
Cookie: sAccessToken=...; sRefreshToken=...
Clock In/Out
Clock In Worker
- Request
- Response
POST /api/timesheets/clock-in
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"workerId": "uuid",
"projectId": "uuid",
"scopeId": "uuid",
"pin": "1234",
"gpsLat": 29.4241,
"gpsLong": -98.4936
}
{
"id": "uuid",
"timesheetId": "TS-2025-001",
"clockInTime": "2025-01-29T08:00:00.000Z",
"message": "Worker clocked in successfully"
}
Roles: FOREMAN, ADMIN
Validations:
- ✅ PIN must match worker's PIN hash
- ✅ GPS coordinates must be within project geofence (if enabled)
- ✅ Worker cannot have active timesheet already
Clock Out Worker
- Request
- Response
POST /api/timesheets/clock-out
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"timesheetId": "uuid",
"gpsLat": 29.4241,
"gpsLong": -98.4936
}
{
"id": "uuid",
"clockOutTime": "2025-01-29T17:00:00.000Z",
"totalHours": 9.0,
"message": "Worker clocked out successfully"
}
Roles: FOREMAN, ADMIN
Auto-Calculations:
- Total hours
- Regular hours
- Overtime hours (if applicable)
Get Active Timesheets
- Request
- Response
GET /api/timesheets/active
Cookie: sAccessToken=...; sRefreshToken=...
[
{
"id": "uuid",
"workerId": "uuid",
"workerName": "John Smith",
"projectId": "uuid",
"projectName": "Shopping Center Foundation",
"clockInTime": "2025-01-29T08:00:00.000Z",
"elapsedHours": 3.5
},
...
]
Roles: FOREMAN, ADMIN
Timesheet Management
List Timesheets
- Request
- Response
GET /api/timesheets?projectId=uuid&status=APPROVED
Cookie: sAccessToken=...; sRefreshToken=...
[
{
"id": "uuid",
"timesheetId": "TS-2025-001",
"workerId": "uuid",
"workerName": "John Smith",
"projectId": "uuid",
"projectName": "Shopping Center Foundation",
"scopeId": "uuid",
"scopeName": "Foundation",
"clockInTime": "2025-01-29T08:00:00.000Z",
"clockOutTime": "2025-01-29T17:00:00.000Z",
"totalHours": 9.0,
"regularHours": 8.0,
"overtimeHours": 1.0,
"status": "APPROVED",
"createdAt": "2025-01-29T08:00:00.000Z"
},
...
]
Roles: FOREMAN, PM, ADMIN
Query Parameters:
projectId- Filter by projectworkerId- Filter by workerstatus- Filter by status (ACTIVE, PENDING_APPROVAL, APPROVED, REJECTED)startDate- Filter by date range startendDate- Filter by date range end
Get Timesheet
- Request
- Response
GET /api/timesheets/:id
Cookie: sAccessToken=...; sRefreshToken=...
{
"id": "uuid",
"timesheetId": "TS-2025-001",
"worker": {
"id": "uuid",
"name": "John Smith",
"employeeNumber": "EMP-001"
},
"project": {
"id": "uuid",
"jobNumber": "J-2025-001",
"jobName": "Shopping Center Foundation"
},
"scope": {
"id": "uuid",
"name": "Foundation"
},
"clockInTime": "2025-01-29T08:00:00.000Z",
"clockOutTime": "2025-01-29T17:00:00.000Z",
"clockInGPS": {
"lat": 29.4241,
"long": -98.4936,
"accuracy": 10.5
},
"clockOutGPS": {
"lat": 29.4242,
"long": -98.4937,
"accuracy": 8.2
},
"totalHours": 9.0,
"regularHours": 8.0,
"overtimeHours": 1.0,
"status": "APPROVED"
}
Roles: FOREMAN, PM, ADMIN
Update Timesheet
- Request
- Response
PUT /api/timesheets/:id
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"scopeId": "new-scope-uuid",
"notes": "Moved to different scope"
}
{
"id": "uuid",
"message": "Timesheet updated successfully"
}
Roles: FOREMAN, ADMIN
Delete Timesheet
- Request
- Response
DELETE /api/timesheets/:id
Cookie: sAccessToken=...; sRefreshToken=...
{
"message": "Timesheet deleted successfully"
}
Roles: ADMIN
Approve Timesheet
- Request
- Response
POST /api/timesheets/:id/approve
Cookie: sAccessToken=...; sRefreshToken=...
{
"id": "uuid",
"status": "APPROVED",
"message": "Timesheet approved successfully"
}
Roles: PM, ADMIN
Reject Timesheet
- Request
- Response
POST /api/timesheets/:id/reject
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"reason": "Incorrect hours logged"
}
{
"id": "uuid",
"status": "REJECTED",
"message": "Timesheet rejected"
}
Roles: PM, ADMIN
Workers
List Workers
- Request
- Response
GET /api/workers
Cookie: sAccessToken=...; sRefreshToken=...
[
{
"id": "uuid",
"name": "John Smith",
"employeeNumber": "EMP-001",
"status": "ACTIVE",
"slClStatus": "SL"
},
...
]
Roles: FOREMAN, PM, ADMIN
info
PIN hashes are NEVER returned in API responses for security.
Get Worker
- Request
- Response
GET /api/workers/:id
Cookie: sAccessToken=...; sRefreshToken=...
{
"id": "uuid",
"name": "John Smith",
"employeeNumber": "EMP-001",
"status": "ACTIVE",
"slClStatus": "SL",
"createdAt": "2025-01-01T00:00:00.000Z"
}
Roles: FOREMAN, PM, ADMIN
Create Worker
- Request
- Response
POST /api/workers
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"name": "Jane Doe",
"employeeNumber": "EMP-002",
"pin": "5678",
"status": "ACTIVE",
"slClStatus": "CL"
}
{
"id": "uuid",
"name": "Jane Doe",
"employeeNumber": "EMP-002",
"message": "Worker created successfully"
}
Roles: ADMIN
Required Fields:
name(string)employeeNumber(string, unique)pin(string, 4 digits) - Hashed on savestatus(enum: ACTIVE, INACTIVE)slClStatus(enum: SL, CL, BOTH)
Update Worker
- Request
- Response
PUT /api/workers/:id
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"name": "Jane D. Doe",
"status": "INACTIVE"
}
{
"id": "uuid",
"message": "Worker updated successfully"
}
Roles: ADMIN
Delete Worker
- Request
- Response
DELETE /api/workers/:id
Cookie: sAccessToken=...; sRefreshToken=...
{
"message": "Worker deleted successfully"
}
Roles: ADMIN
Update Worker PIN
- Request
- Response
PUT /api/workers/:id/pin
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json
{
"pin": "9999"
}
{
"message": "PIN updated successfully"
}
Roles: ADMIN
warning
PINs are hashed and never stored or returned in plain text.
GPS Validation
Geofence Check
Projects can enable geofence validation with a radius (meters):
const distance = calculateDistance(
worker.gpsLat, worker.gpsLong,
project.locationLat, project.locationLong
)
if (distance > project.geofenceRadius) {
throw new Error("GPS location outside project geofence")
}
Bypass: ADMIN role can bypass geofence validation.
GPS Fields
| Field | Type | Description |
|---|---|---|
| gpsLat | number | Latitude (required) |
| gpsLong | number | Longitude (required) |
| gpsAccuracy | number | Accuracy in meters (optional) |
Data Models
interface Timesheet {
id: string
timesheetId: string // TS-YYYY-###
workerId: string
projectId: string
scopeId: string | null
clockInTime: DateTime
clockOutTime: DateTime | null
clockInGPS: {
lat: number
long: number
accuracy: number | null
}
clockOutGPS: {
lat: number
long: number
accuracy: number | null
} | null
totalHours: number | null
regularHours: number | null
overtimeHours: number | null
status: TimesheetStatus
notes: string | null
createdAt: DateTime
updatedAt: DateTime
}
interface Worker {
id: string
name: string
employeeNumber: string // Unique
pinHash: string // NEVER returned in API
status: WorkerStatus
slClStatus: SLCLStatus
createdAt: DateTime
updatedAt: DateTime
}
enum TimesheetStatus {
ACTIVE
PENDING_APPROVAL
APPROVED
REJECTED
}
enum WorkerStatus {
ACTIVE
INACTIVE
}
enum SLCLStatus {
SL // Skilled Labor
CL // Common Labor
BOTH // Both
}
Role-Based Access
| Role | Permissions |
|---|---|
| FOREMAN | Clock in/out workers, view timesheets |
| PM | Approve/reject timesheets, view all |
| ADMIN | Full access, manage workers, bypass GPS |