feat: work order + spare parts HTTP API#79
Merged
Conversation
bbe6d66 to
90f403a
Compare
Adds the Work Order HTTP surface as thin wrappers over the existing things RPC plumbing: POST /auth/work-orders create (registerThing) GET /auth/work-orders list (listThings + type filter) GET /auth/work-orders/:id get (listThings + id+type filter) PATCH /auth/work-orders/:id update (updateThing) POST /auth/work-orders/:id/close close (updateThing status=closed) POST /auth/work-orders/:id/cancel cancel (updateThing status=cancelled) POST /auth/work-orders/:id/assign assign (updateThing assignedTo) GET /auth/work-orders/:id/audit history (getHistoricalLogs) Introduces a new RBAC resource work_order (r/rw) granted to operator/admin/repair_technician roles for write and to viewer roles for read. Strips the duplicated [HRPC_ERR]= prefix and bubbles structured rack errors verbatim. Skips capCheck when the gateway runs with --noauth so local curl-testing works through permissioned routes.
Adds filter shortcuts (q, assignee, creator, partId, status, type,
from, to) to GET /auth/work-orders alongside the existing mingo
?query passthrough; q is a case-insensitive regex $or against code
and info.issue, the rest map to info.* paths and run on the rack
the same way every other list does. Pages now return the
{data, totalCount, offset, limit, hasMore} envelope by calling
listThings and getThingsCount in parallel, and every shortcut is
wired into the cache key so cached pages do not bleed across
filter combinations.
Adds PUT /auth/spare-parts/:id which validates the supplied
workOrderId resolves to an open or in_progress WO, pushes the
underlying updateThing on the part rack with workOrderId injected
into info, and appends a denormalised entry to the WO's
partsMoves[] so the audit trail stays on the Work Order itself.
GET /auth/spare-parts/:id/repair-history mingo-queries every WO
that ever touched the part and returns each move row hydrated with
workOrderCode in the standard {data, totalCount, offset, limit,
hasMore} envelope.
Adds POST /auth/work-orders/:id/log which forwards to the existing saveThingComment RPC so the WO comments[] doubles as the work log. Adds POST/GET/DELETE /auth/work-orders/:id/files routes backed by @fastify/multipart on the gateway and the new storeWorkOrderFile/loadWorkOrderFile/removeWorkOrderFile pass- throughs on the ork; uploads validate mime + size, append metadata to WO.info.files via pushAction, and reject mutations on closed or cancelled WOs.
# Conflicts: # workers/lib/constants.js # workers/lib/server/index.js
POST /auth/spare-parts now creates the part and a paired Type-1 WO in
one call, returning {partId, workOrderId, workOrderCode}. POST
/auth/work-orders resolves the target part for both flows from
deviceIdentifier (id, code, serialNum, or macAddress) and rejects
unknown deviceTypes with ERR_INVALID_DEVICE_TYPE / unresolved parts
with ERR_PART_NOT_FOUND.
Adds an open `warranty: { vendor, fields }` shape to the create and
update body schemas; vendor-specific field validation lives in the
inventory worker so app-node stays agnostic of vendor differences.
Export accepts either the WO uuid or its IVI-* code in the path and a required ?format=pdf|csv|docx querystring. CSV flattens one row per parts-movement entry (WO header columns repeated). PDF is server-rendered via PDFKit and includes header, triage, work log, parts movements, file references, and a vendor-specific warranty section. format=docx returns 501 with ERR_EXPORT_FORMAT_NOT_IMPLEMENTED until phase 2.
Drops the pdfkit dependency and the PDF renderer. The export endpoint now serves CSV directly; pdf and docx both 501 with ERR_EXPORT_FORMAT_NOT_IMPLEMENTED, keeping the route shape stable for when the frontend renders PDF client-side.
Mirrors the new MINER_LOCATIONS enum from the inventory worker.
Mirrors the inventory worker constant.
…ters
Querystring shortcuts (location, status, q) translate to a mingo query
forwarded to the rack via listThings + getThingsCount — no app-node
post-filtering. q is a case-insensitive regex against code, info.serialNum,
and info.macAddress. WO things are excluded by default via type:{\$ne:...},
overridable by an explicit query.type. Cache key includes every filter
shortcut so combinations don't collide.
…to utils Both list handlers (work-orders + spare-parts) hand-rolled the same regex-escape + listThings/getThingsCount/pagination skeleton; collapses to a single utils helper used by both. Cache keys for the two GET list routes now canonicalize their JSON inputs via stableJsonString so semantically-equal queries share a cache slot instead of missing each other on key-order differences.
…o polling
App-node was polling listThings after each pushAction to surface the
rack-assigned thing code in the response — exactly the
'submit-and-poll-in-app-node' anti-pattern the codebase had already
rejected. registerSparePart now pre-generates partId/woId, fires both
registerThing pushActions in parallel, and returns
{partId, workOrderId, partActionId, workOrderActionId, errors}. Clients
read the eventually-assigned codes from GET /auth/actions/done/:id like
every other write in the app.
Removes waitForThing + sleep from utils and the two timeout constants
that only existed to tune it.
…to#10 tetherto#12 tetherto#20 tetherto#3 (atomicity) — updateSparePart now checks the part pushAction's errors[] before firing the WO partsMoves append. If the part write was rejected at push time, throws ERR_PART_UPDATE_PUSH_FAILED:<msg> with a 502 instead of silently enqueueing the WO append. Response shape also gains partActionId / workOrderActionId / workOrderAppendErrors so the FE can detect a post-push WO failure and trigger a manual reconcile. tetherto#6 (status casing) — WO status enum normalised to 'inProgress' so every info.* enum value follows the camelCase convention adopted for MINER_LOCATIONS. tetherto#8 (registerSparePart field sprawl) — drops the info.model→info.deviceModel→'unknown' fallback chain and the serialNum→macAddress→partId fallback. deviceModel and serialNum are now both required up front with explicit ERR_*_REQUIRED errors. tetherto#10 (action latency) — register/update responses include expectedActionLatencyMs sourced from ctx.conf.expectedActionLatencyMs (default 1000) so the FE can pace its /auth/actions/done/:id polling instead of guessing. tetherto#12 (export 501) — error message now carries the requested format (ERR_EXPORT_FORMAT_NOT_IMPLEMENTED:pdf / :docx) so the FE can tell which format was rejected without re-reading the request. tetherto#20 (pagination) — listThingsWithCount now slices the dedupe'd union down to the requested limit so multi-ork fan-out can't return more rows than the caller asked for. hasMore is now derived from offset + limit rather than offset + data.length. Multi-rack offset-pagination remains best-effort because each rack applies the offset locally; documented inline.
Mirrors the inventory rack revert — keeps existing 'in_progress' WO records valid for transitions.
Mirrors the inventory rack revert.
boris91
approved these changes
May 18, 2026
Updates the work-order file handlers to use the renamed generic file RPCs and tags each call with FILE_TYPES.WORK_ORDER so the rack can dispatch on it.
loadFile / removeFile are now called with { workOrderId, fileId } so the
rack resolves the blob descriptor from the work order it belongs to —
app-node no longer forwards a raw blobRef. deleteWorkOrderFile surfaces
the rack's { cleared } result as blobCleared so the HTTP caller can tell
whether the underlying blob was actually removed.
tekwani
reviewed
May 21, 2026
- resolve the work_order rack from the ork rack registry via listRacks (getWorkOrderRackId, cached on ctx) and drop the workOrderRackId config key — the rack id is discovered, not deployment config - relocate submitWorkOrderAction out of generic utils into the new server/lib/workOrders module alongside getWorkOrderRackId - add WORK_ORDER_TERMINAL_STATUSES constant, replacing the duplicated ['closed', 'cancelled'] literals across the WO handlers - workOrderExport: derive the CSV header and rows from one [name, extractor] schema instead of two hand-synced lists - rename pushOne -> pushSingleAction in registerSparePart
renderWorkOrderCsv builds the header from the work order's own properties — top-level code plus every info field, then each parts-movement entry's keys — so the CSV tracks the worker response schema with no hand-kept column list. Drops the CSV_HEADERS / WO_COLUMNS / MOVE_COLUMNS definitions.
Align the work order and spare parts file names with the repo convention (cooling.system.routes.js, energy.system.routes.js, etc.): camelCase basenames become dot-separated — spareParts.handlers.js -> spare.parts.handlers.js, workOrders.js -> work.orders.js, workOrderExport.js -> work.order.export.js. Updates every require() path, route registration in index.js, and test references.
paragmore
approved these changes
May 22, 2026
tekwani
approved these changes
May 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exposes the HTTP surface for Work Orders (CRUD, filtered list, audit, assign/close/cancel, work-log entries, multipart file upload/download, CSV export) and the coordinated spare-parts register/update/list/repair-history routes that pair every part move with a Work Order. Reuses the existing list-things / pushAction / actions-done plumbing throughout — clients poll
/auth/actions/done/:idfor async write results, paced by theexpectedActionLatencyMshint surfaced in every push response.