Skip to content

Subcontractor Schema

Container: Subcontractors Partition Key: /projectId Document Type: subcontractor Last Updated: 2025-11-27


Overview

The Subcontractors container stores project-scoped subcontractor information and rates. Each subcontractor is unique within a project, identified by subcontractorCode.

When a subcontractor is assigned to an MTO item, their allInRate becomes the total estimation unit price, overriding normal labor + material + equipment calculations.


Schema Definition

Subcontractor

interface Subcontractor {
  // Document metadata
  id: string;              // Unique document ID (UUID)
  subcontractorId: string; // Same as id (for consistency)
  type: "subcontractor";   // Document type discriminator
  createdAt: string;       // ISO 8601 timestamp
  updatedAt: string;       // ISO 8601 timestamp

  // Required fields
  projectId: string;         // Project reference (PARTITION KEY)
  subcontractorCode: string; // Unique code within project (e.g., SUB-001)
  subcontractorName: string; // Display name

  // Pricing
  allInRate: number | null;  // All-in hourly/unit rate

  // Optional contact info
  description: string | null;
  contactPerson: string | null;
  contactEmail: string | null;
  contactPhone: string | null;
}

Field Details

Core Fields

Field Type Required Description
id string Yes Unique document identifier (UUID)
subcontractorId string Yes Same as id for API consistency
type literal Yes Always "subcontractor"
projectId string Yes Partition key - Project this subcontractor belongs to
subcontractorCode string Yes Unique identifier within project (e.g., SUB-001, ELEC-SUB-01)
subcontractorName string Yes Display name

Pricing Fields

Field Type Required Description
allInRate float No All-in rate (hourly or per unit). Must be >= 0 if provided.

Contact Fields

Field Type Required Description
description string No General description of subcontractor scope
contactPerson string No Primary contact name
contactEmail string No Contact email address
contactPhone string No Contact phone number

Project-Scoped Uniqueness

Subcontractors are unique within a project by subcontractorCode:

Project: PROJ-001
├── SUB-001 - ABC Electrical
├── SUB-002 - XYZ Mechanical
└── ELEC-SUB-01 - Delta Electric

Project: PROJ-002
├── SUB-001 - Different Company (same code, different project)
└── SUB-002 - Another Contractor

Uniqueness Constraint

  • (projectId, subcontractorCode) must be unique
  • The same subcontractorCode can exist in different projects
  • Application layer enforces uniqueness (Cosmos DB does not have unique constraints)

MTO Integration

Assignment to MTO Items

When a subcontractor is assigned to an MTO item:

  1. Set subcontractorCode on the MTO item
  2. Set subcontractorName for display (denormalized)
  3. Optionally set subcontractorCostAllInRate if using the subcontractor's rate

allInRate Usage in Calculations

The allInRate field represents a complete, all-inclusive rate from the subcontractor. When used in MTO calculations:

// In MTO calculation logic
if (subcontractorCostAllInRate !== null && subcontractorCostAllInRate > 0) {
  // Subcontractor all-in rate IS the total estimation price
  totalEstimationUnitPrice = subcontractorCostAllInRate;
} else {
  // Normal calculation: labor + material + equipment
  totalEstimationUnitPrice = laborUnitCost + totalMaterialUnitPrice + totalEquipmentUnitPrice;
}

MTO Fields for Subcontractor Assignment

MTO Field Type Description
subcontractorCode string Reference to subcontractor
subcontractorName string Denormalized name for display
subcontractorCostAllInRate float All-in rate copied from subcontractor (or manually set)

API Operations

Create Subcontractor

POST /api/projects/{projectId}/subcontractors
Content-Type: application/json

{
  "subcontractorCode": "SUB-001",
  "subcontractorName": "ABC Electrical",
  "allInRate": 125.50,
  "description": "Licensed electrical contractor",
  "contactPerson": "John Smith",
  "contactEmail": "john@abcelectric.com",
  "contactPhone": "555-123-4567"
}

Update Subcontractor

PATCH /api/projects/{projectId}/subcontractors/{subcontractorId}
Content-Type: application/json

{
  "allInRate": 130.00,
  "contactEmail": "newemail@abcelectric.com"
}

List Project Subcontractors

GET /api/projects/{projectId}/subcontractors

Import Subcontractors

POST /api/projects/{projectId}/subcontractors/import
Content-Type: application/json

{
  "projectId": "PROJ-001",
  "subcontractors": [
    {
      "subcontractorCode": "SUB-001",
      "subcontractorName": "ABC Electrical",
      "allInRate": 125.50
    },
    {
      "subcontractorCode": "SUB-002",
      "subcontractorName": "XYZ Mechanical",
      "allInRate": 95.00
    }
  ]
}

Query Patterns

Get All Subcontractors for Project

SELECT * FROM c
WHERE c.type = 'subcontractor'
  AND c.projectId = @projectId
ORDER BY c.subcontractorCode

Find Subcontractor by Code

SELECT * FROM c
WHERE c.type = 'subcontractor'
  AND c.projectId = @projectId
  AND c.subcontractorCode = @code

Get Subcontractors with Rates

SELECT c.subcontractorCode, c.subcontractorName, c.allInRate
FROM c
WHERE c.type = 'subcontractor'
  AND c.projectId = @projectId
  AND c.allInRate != null

Backend Schema Reference

File: /backend/app/schemas/subcontractor.py

class SubcontractorBase(BaseSchema):
    project_id: str = Field(alias="projectId")
    subcontractor_code: str = Field(alias="subcontractorCode")
    subcontractor_name: str = Field(alias="subcontractorName")
    all_in_rate: float | None = Field(default=None, alias="allInRate", ge=0)
    description: str | None = None
    contact_person: str | None = Field(default=None, alias="contactPerson")
    contact_email: str | None = Field(default=None, alias="contactEmail")
    contact_phone: str | None = Field(default=None, alias="contactPhone")

class Subcontractor(SubcontractorBase, BaseDocument):
    subcontractor_id: str = Field(alias="subcontractorId")
    type: Literal["subcontractor"] = Field(default="subcontractor")

Frontend Type Reference

File: /frontend/src/types/subcontractor.ts

interface Subcontractor {
  id: string;
  subcontractorId: string;
  type: 'subcontractor';
  projectId: string;
  subcontractorCode: string;
  subcontractorName: string;
  allInRate: number | null;
  description: string | null;
  contactPerson: string | null;
  contactEmail: string | null;
  contactPhone: string | null;
  createdAt: string;
  updatedAt: string;
}

Data Flow Example

Assigning Subcontractor to MTO

  1. User selects subcontractor in MTO assignment UI
  2. Frontend retrieves subcontractor details including allInRate
  3. MTO item is updated:
    {
      "subcontractorCode": "SUB-001",
      "subcontractorName": "ABC Electrical",
      "subcontractorCostAllInRate": 125.50
    }
    
  4. Backend recalculates totalEstimationUnitPrice:
  5. Since subcontractorCostAllInRate > 0, it becomes the total price
  6. Labor, material, and equipment calculations are bypassed

Rate Override

Users can manually override the rate on an MTO item: - subcontractorCostAllInRate on MTO can differ from allInRate on subcontractor - This allows for item-specific negotiated rates