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
subcontractorCodecan 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:
- Set
subcontractorCodeon the MTO item - Set
subcontractorNamefor display (denormalized) - Optionally set
subcontractorCostAllInRateif 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¶
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¶
- User selects subcontractor in MTO assignment UI
- Frontend retrieves subcontractor details including
allInRate - MTO item is updated:
- Backend recalculates
totalEstimationUnitPrice: - Since
subcontractorCostAllInRate > 0, it becomes the total price - 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
Related Documentation¶
- Containers Overview - All database containers
- Material Takeoff Schemas - MTO item fields
- API Reference - Subcontractor API endpoints