Build & QA
Each order can have exactly one build. The build tracks the physical assembly process, QA form data, photos, test runs, and generates PDF booklets on completion.
Build Creation
POST /builds — staff only. Validates:
- The order exists
- A build for this order doesn't already exist (
orderId @uniqueon theBuildmodel)
Once created, the build starts in pending status.
Build Status Lifecycle
pending → in_progress → testing → completed
↓
paused
↓
failed (transient)
| Status | Notes |
|---|---|
pending | Build created but not started |
in_progress | Assembly underway; startedAt is set automatically |
paused | Temporarily paused (parts wait, etc.) |
testing | QA / burn-in phase |
completed | Build done; finishedAt set; triggers booklet generation |
failed | Transient — staff fix the issue and transition back. Not mapped to fulfillment |
Auto-timestamps
BuildsService.update() sets:
startedAt = nowwhen transitioning toin_progress(only if not already set)finishedAt = nowwhen transitioning tocompletedorfailed
Fulfillment Status Coupling
When build status changes, BuildsService directly updates Order.fulfillmentStatus via BUILD_TO_FULFILLMENT:
| Build status | Order fulfillment status |
|---|---|
in_progress | building |
testing | testing |
completed | ready |
failed | (not updated — transient) |
After fulfillmentStatus → ready, staff manually advance through packaging → shipped → completed via PATCH /orders/:id.
Photo Slots
10 named photo slots per build. Each slot stores an Attachment ID.
| Slot | Purpose |
|---|---|
coverPhoto | Main/hero shot of the completed build |
mboIoPhoto | Motherboard I/O panel |
gpuIoPhoto | GPU I/O panel |
caseIoPhoto | Case front-panel I/O |
psuPhoto | PSU area / cable management |
powerBtnPhoto | Power button area |
panelAPhoto | Left panel interior |
panelBPhoto | Right panel interior / cable side |
foamPhoto | Packaging foam layout |
thermalPhoto | Thermal compound application |
thermalPhoto is optional — it is excluded from the packaging gate check.
Packaging Gate
Before fulfillmentStatus → packaging is allowed, OrdersService.update() verifies:
- The build has all 9 required photo slots filled (all except
thermalPhoto) build.qaChecklistis not empty
This prevents shipping a build without completing photo documentation and QA.
Uploading Photos
POST /builds/:id/photos/:slot — multipart file upload. The service:
- Deletes the existing attachment for that slot (if any) from storage and DB
- Uploads the new file to
builds/photos/prefix in Hetzner Object Storage - Updates the build's photo slot field with the new
AttachmentID
QA Form
QA data stored on the Build model:
| Field | Type | Notes |
|---|---|---|
qaResult | Boolean? | Pass/fail; null until QA is performed |
qaNotes | String? | Free-text notes |
qaChecklist | Json? | Structured checklist items (required for packaging gate) |
qaPassedAt | DateTime? | When QA was marked as passed |
qaSignedOffById | String? | Staff user who signed off |
System Config & Test Data
Additional JSON fields on Build:
| Field | Type | Notes |
|---|---|---|
systemConfig | Json? | Full system config snapshot (OS, drivers, etc.) |
benchmarks | Json? | Benchmark run results; each may have a photoAttachmentId |
stressTests | Json? | Stress test results |
temperatures | multiple Float? | cpuIdleTemp, cpuLoadTemp, gpuIdleTemp, gpuLoadTemp, ambientTemp |
TestRun model
Structured test run records (TestRun Prisma model) linked to a build:
| Field | Notes |
|---|---|
type | benchmark, stress_test, memory_test, thermal_check, storage_test, stability_test, incident |
status | pending, running, passed, failed, skipped |
score | Numeric score (nullable) |
notes | Free text |
photoAttachmentId | Optional screenshot |
Booklet Generation Trigger
When BuildsService.update() transitions build status to completed:
booklets.generateBooklets(buildId)is called- This is fire-and-forget — the API response does not wait for PDF generation
- Two PDFs are generated: Quick Start Guide and Technical Dossier
- PDFs are uploaded to Hetzner Object Storage
Attachmentrecords are created and their IDs saved toBuild.quickStartBookletId/Build.technicalDossierBookletId
See Booklets for generation details and failure modes.
Manual Regeneration
POST /builds/:id/booklets/regenerate — staff can trigger regeneration at any time. This is also fire-and-forget.
Build Timeline
BuildTimelineStep records create a chronological log of build milestones:
| Step type | Usage |
|---|---|
start | Build begins |
parts_received | Parts arrived |
assembly | Assembly phase |
cable_management | Cable management |
os_install | OS installation |
software_setup | Driver/software setup |
testing | Testing phase |
packaging | Packaging |
shipped | Shipped |
milestone | Generic milestone |
update | Generic update |
Build Event Log
BuildEventLog records freeform events during the build:
| Event type | Usage |
|---|---|
note | General notes |
incident | Something went wrong |
correction | A fix was applied |
observation | Noteworthy observation |
measurement | A reading or measurement |
issue | Identified issue |
fix | Applied fix |
info | Informational |
Each event has severity: info, warning, error, critical.