Skip to main content

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:

  1. The order exists
  2. A build for this order doesn't already exist (orderId @unique on the Build model)

Once created, the build starts in pending status.


Build Status Lifecycle

pending → in_progress → testing → completed

paused

failed (transient)
StatusNotes
pendingBuild created but not started
in_progressAssembly underway; startedAt is set automatically
pausedTemporarily paused (parts wait, etc.)
testingQA / burn-in phase
completedBuild done; finishedAt set; triggers booklet generation
failedTransient — staff fix the issue and transition back. Not mapped to fulfillment

Auto-timestamps

BuildsService.update() sets:

  • startedAt = now when transitioning to in_progress (only if not already set)
  • finishedAt = now when transitioning to completed or failed

Fulfillment Status Coupling

When build status changes, BuildsService directly updates Order.fulfillmentStatus via BUILD_TO_FULFILLMENT:

Build statusOrder fulfillment status
in_progressbuilding
testingtesting
completedready
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.

SlotPurpose
coverPhotoMain/hero shot of the completed build
mboIoPhotoMotherboard I/O panel
gpuIoPhotoGPU I/O panel
caseIoPhotoCase front-panel I/O
psuPhotoPSU area / cable management
powerBtnPhotoPower button area
panelAPhotoLeft panel interior
panelBPhotoRight panel interior / cable side
foamPhotoPackaging foam layout
thermalPhotoThermal compound application

thermalPhoto is optional — it is excluded from the packaging gate check.

Packaging Gate

Before fulfillmentStatus → packaging is allowed, OrdersService.update() verifies:

  1. The build has all 9 required photo slots filled (all except thermalPhoto)
  2. build.qaChecklist is 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:

  1. Deletes the existing attachment for that slot (if any) from storage and DB
  2. Uploads the new file to builds/photos/ prefix in Hetzner Object Storage
  3. Updates the build's photo slot field with the new Attachment ID

QA Form

QA data stored on the Build model:

FieldTypeNotes
qaResultBoolean?Pass/fail; null until QA is performed
qaNotesString?Free-text notes
qaChecklistJson?Structured checklist items (required for packaging gate)
qaPassedAtDateTime?When QA was marked as passed
qaSignedOffByIdString?Staff user who signed off

System Config & Test Data

Additional JSON fields on Build:

FieldTypeNotes
systemConfigJson?Full system config snapshot (OS, drivers, etc.)
benchmarksJson?Benchmark run results; each may have a photoAttachmentId
stressTestsJson?Stress test results
temperaturesmultiple Float?cpuIdleTemp, cpuLoadTemp, gpuIdleTemp, gpuLoadTemp, ambientTemp

TestRun model

Structured test run records (TestRun Prisma model) linked to a build:

FieldNotes
typebenchmark, stress_test, memory_test, thermal_check, storage_test, stability_test, incident
statuspending, running, passed, failed, skipped
scoreNumeric score (nullable)
notesFree text
photoAttachmentIdOptional screenshot

Booklet Generation Trigger

When BuildsService.update() transitions build status to completed:

  1. booklets.generateBooklets(buildId) is called
  2. This is fire-and-forget — the API response does not wait for PDF generation
  3. Two PDFs are generated: Quick Start Guide and Technical Dossier
  4. PDFs are uploaded to Hetzner Object Storage
  5. Attachment records are created and their IDs saved to Build.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 typeUsage
startBuild begins
parts_receivedParts arrived
assemblyAssembly phase
cable_managementCable management
os_installOS installation
software_setupDriver/software setup
testingTesting phase
packagingPackaging
shippedShipped
milestoneGeneric milestone
updateGeneric update

Build Event Log

BuildEventLog records freeform events during the build:

Event typeUsage
noteGeneral notes
incidentSomething went wrong
correctionA fix was applied
observationNoteworthy observation
measurementA reading or measurement
issueIdentified issue
fixApplied fix
infoInformational

Each event has severity: info, warning, error, critical.