Skip to main content

Labor Items

Overview​

Labor items represent crew members with automatic calculation of soft costs (payroll taxes) and auto costs (vehicle depreciation, fuel, insurance). The system also auto-calculates dependent roles like Operations Coordinator and Labor Foreman.

Endpoints​

List Labor Items​

GET /api/labor/scope/:scopeId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Create Labor Item​

POST /api/labor
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"scopeId": "uuid",
"role": "Skilled Laborer",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 25.00,
"days": 5
}

Roles: ADMIN, ESTIMATOR

Required Fields:

  • scopeId (string, UUID) - Parent scope
  • role (string) - Job role (e.g., "Skilled Laborer", "Operator")
  • quantity (number) - Number of workers
  • hoursPerDay (number) - Hours worked per day
  • ratePerHour (number) - Hourly wage
  • days (number) - Duration in days

Update Labor Item​

PUT /api/labor/:id
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"quantity": 6,
"days": 7
}

Roles: ADMIN, ESTIMATOR


Delete Labor Item​

DELETE /api/labor/:id
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR

Automatic Calculations​

Base Cost​

dollarPerDay = quantity × hoursPerDay × ratePerHour
baseCost = dollarPerDay × days

Soft Costs (Payroll Taxes)​

ficaRate = 0.0765      // 7.65% (Social Security + Medicare)
futaRate = 0.006 // 0.6% (Federal Unemployment)
sutaRate = 0.0175 // 1.75% (State Unemployment)

ficaAmount = baseCost × ficaRate
futaAmount = baseCost × futaRate
sutaAmount = baseCost × sutaRate

Auto Costs (Vehicle Expenses)​

For roles that require company vehicles:

autoDepreciationRate = 0.03    // 3%
autoFuelRate = 0.02 // 2%
autoInsuranceRate = 0.01 // 1%

autoDepreciation = baseCost × autoDepreciationRate
autoFuel = baseCost × autoFuelRate
autoInsurance = baseCost × autoInsuranceRate

autoCosts = autoDepreciation + autoFuel + autoInsurance

Total Cost​

totalCost = baseCost + fica + futa + suta + autoCosts

Dependent Roles (Read-Only)​

Certain roles are automatically calculated based on skilled labor hours. These appear in the UI but cannot be directly edited.

Operations Coordinator​

skilledLaborHours = Σ (quantity × hoursPerDay × days) for "Skilled Laborer" role
opsCoordinatorHours = Math.min(skilledLaborHours × 0.05, 40)
  • 5% of skilled labor hours
  • Capped at 40 hours

Labor Foreman​

totalLaborers = Σ quantity for all labor items
laborForemanCount = Math.ceil(totalLaborers / 10)
  • 1 foreman per 10 laborers

Junior Foreman​

totalLaborers = Σ quantity for all labor items
juniorForemanCount = Math.ceil(totalLaborers / 5)
  • 1 junior foreman per 5 laborers

Data Model​

interface LaborItem {
id: string // UUID
scopeId: string // Parent scope UUID
role: string // Job role
quantity: number // Number of workers
hoursPerDay: number // Daily hours
ratePerHour: number // Hourly wage
days: number // Duration

// Calculated Values (read-only)
dollarPerDay: number
totalCost: number
autoCosts: {
depreciation: number
fuel: number
insurance: number
} | null
softCosts: {
fica: number
futa: number
suta: number
}

// Relations
scope: Scope
}

Common Labor Roles​

  • Skilled Laborer - General construction worker
  • Operator - Heavy equipment operator
  • Carpenter - Framing/formwork specialist
  • Concrete Finisher - Concrete finishing specialist
  • Foreman - Crew supervisor
  • Junior Foreman - Assistant supervisor
  • Operations Coordinator - Project coordination (auto-calculated)

Example: 4-Person Crew for 5 Days​

{
"role": "Skilled Laborer",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 25.00,
"days": 5
}

Calculation:

  • Base: 4 × 8 × $25 × 5 = $4,000
  • FICA (7.65%): $306
  • FUTA (0.6%): $24
  • SUTA (1.75%): $70
  • Auto Costs (6%): $240
  • Total: $4,640

Gantt Mode​

When bid.laborBiddingMode = "GANTT", labor is tracked at bid level with timeline-based allocation. Labor types are placed on a day-based timeline and costs distribute automatically to overlapping scopes.

List Bid Labor​

GET /api/bids/:bidId/bid-labor
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Create Gantt Labor​

POST /api/bids/:bidId/bid-labor
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborType": "Skilled Labor",
"quantity": 4,
"hoursPerDay": 8,
"ratePerHour": 27.63,
"startDay": 3,
"durationDays": 10
}

Roles: ADMIN, ESTIMATOR

Required Fields:

  • laborType (string) - Labor role name
  • quantity (number) - Number of workers
  • hoursPerDay (number) - Hours per worker per day
  • ratePerHour (number) - Hourly wage
  • startDay (number) - Start day (1-based)
  • durationDays (number) - Duration in days

Update Gantt Labor​

PATCH /api/bids/:bidId/bid-labor/:laborId
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"startDay": 5,
"durationDays": 8,
"quantity": 6
}

Roles: ADMIN, ESTIMATOR


Delete Gantt Labor​

DELETE /api/bids/:bidId/bid-labor/:laborId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR


Batch Update Labor​

POST /api/bids/:bidId/bid-labor/batch
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"create": [
{
"laborType": "Operator",
"quantity": 2,
"hoursPerDay": 8,
"ratePerHour": 29.84,
"startDay": 1,
"durationDays": 5
}
],
"update": [
{
"id": "uuid",
"startDay": 3,
"durationDays": 8
}
],
"delete": ["uuid1", "uuid2"]
}

Roles: ADMIN, ESTIMATOR


Switch Labor Bidding Mode​

PATCH /api/bids/:bidId/bid-labor/mode
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborBiddingMode": "GANTT"
}

Roles: ADMIN, ESTIMATOR

warning

Switching modes will clear all existing labor items for the selected mode. Use /mode/confirm to proceed.


Confirm Labor Mode Switch​

POST /api/bids/:bidId/bid-labor/mode/confirm
Cookie: sAccessToken=...; sRefreshToken=...
Content-Type: application/json

{
"laborBiddingMode": "GANTT"
}

Roles: ADMIN, ESTIMATOR


Get Labor Distributions​

GET /api/bids/:bidId/bid-labor/distributions
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Get Scope Labor Allocations​

GET /api/bids/:bidId/bid-labor/scope/:scopeId
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Get Labor Coverage Details​

GET /api/bids/:bidId/bid-labor/:laborId/coverage
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR, PM


Recalculate Labor Distributions​

POST /api/bids/:bidId/bid-labor/recalculate
Cookie: sAccessToken=...; sRefreshToken=...

Roles: ADMIN, ESTIMATOR

Gantt Mode Cost Calculations​

// Base cost
totalCost = quantity × hoursPerDay × ratePerHour × durationDays

// Distribute across scopes based on timeline overlap
for each scope {
overlapDays = calculateOverlap(labor.startDay, labor.durationDays, scope.timeline)
percentage = overlapDays / totalOverlapDays
allocatedCost = totalCost × percentage
}

Gantt Mode Data Model​

interface BidLabor {
id: string
bidId: string
laborType: string
quantity: number
hoursPerDay: number
ratePerHour: number
startDay: number
durationDays: number
totalCost: number
distributions: LaborDistribution[]
}

interface LaborDistribution {
scopeId: string
scopeName: string
allocatedDays: number
percentage: number
allocatedCost: number
}