CMMS on CAP-686: UI sample vs built

Branch CAP-686 (with development merged) in backendApi, capExpertApp and dbMigrations, compared with the Captain V11 UI sample · checked 14 Oct 2026

Status: most of the CMMS tab is built and runs on real data. CAP-686 brings the CMMS tab back on Inventory and wires every surface to real API endpoints. Of the 17 pieces in the UI sample, 9 are built, 5 are partial and 3 are not built. There is one schema migration.

Before merge: regenerate the swagger client (the local copy is out of date, so the front end won’t compile without it), fix the bugs listed under Issues found, and add tests. The branch has none.

9 · 5 · 3of the 17 UI-sample pieces built · partial · not built
1 migration1 new table (work_order_activities), 13 new columns on work_orders, and new columns on 3 other tables
26 endpoints9 work orders · 13 CMMS · 4 parts POs; plus 6 CMMS reports on the report pipeline
0 testsno spec files on the branch

About the UI sample. The Vercel site is a design sample, not built from our code. Every number in it is mock data. Here it is only the target design that our code is compared against.

On this page

UI sample vs CAP-686, piece by piece

Built in place   Built, differs in place with a deliberate difference   Partial part of it   Not built nothing yet

#UI sample pieceStatusOn CAP-686
1CMMS tab under InventoryBuiltVisible to accounts with the existing camp.manage_inventory.biomed feature, which existing accounts already have. Deep link ?view=biomed; a work-order notification opens ?view=biomed&wo=<id>.
2KPI tilesPartialTotal Assets (OEM / HTM), Contract Coverage %, PM Overdue (within 30 / 60 days), High-Risk Assets (≥ 40%). The fifth tile is Open Work Orders instead of No WO History. Every tile drills down when clicked. All tiles count only assets that have a work order, not the whole fleet.
3Warranty banner with Review coverage optionsBuilt, differsAlso counts service contracts: “N OEM warranties and N service contracts ending in the next 90 days”. The button opens the asset list filtered to expiring coverage.
4Assets / Work Orders toggle and the Grouped accordionsPartial4 groups: PM Overdue, High-Risk, HTM Covered, OEM Covered. There is no No WO History group. Each group shows 8 rows, then “+N more”. Assets list columns: Tag, Asset, Facility, Coverage, Coverage ends, PM, Replace.
5Work order table: WO #, Asset, Type, Status, Stage, Assigned, Facility, DueBuiltAll 8 columns, with the 3-dot stage. Server-side paging and sorting.
6Saved viewsBuilt, differsAll, Critical, Escalated, Assigned to me, This week’s PM, plus an extra Unassigned view. They are real filters: Critical means Urgent, Escalated, or overdue and not closed. Assigned to me means the signed-in user.
7Type and Status filters, search, “Filtered by” chipsBuiltSingle-select header filters. Search covers WO number, description, asset, model, manufacturer, serial, assignee and facility.
8Work order drawerPartialBuilt: stage stepper, asset tag and location (facility, floor, department, room), requester, details, PM schedule with the electrical safety result, SLA window, service contract, activity timeline, parts and materials, Add to calendar, Re-assign, Update Status. Uptime YTD is not built. “Last-seen location” is the asset’s assigned location, not a sighting.
9Update StatusPartialStatus, stage, “Closed because” (completed / cancelled) and a note, which goes into the timeline. Of the quick actions, only Order part is built, and it only opens the drawer’s parts section. User manual and Intended use are not built.
10New Work OrderBuilt, differsAsset search (top 6), free-text asset, facility, type, priority, due date, description. It adds PM and contract cards, a warning when the asset already has an open WO, and a revenue-driver warning. Assigned To is optional: Unassigned, the 3 least-loaded techs, or any other tech from a paged list. Tech chips show open and active counts, not Light / Steady / Heavy.
11Parts & POsNot builtNo screen; the old sub-tab was removed. The /parts-pos API is still there, and parts show read-only in the drawer.
12Team Stats panelPartialSLA at risk, open / active / done this week, WOs closed per day over 7 days, tech cards, team performance (avg close time, first-fix rate, PM on-time, backlog) against fixed targets, and backlog by type. No Light / Steady / Heavy label. A Manage button for user managers.
13Intake AssetBuiltAll fields, with a floor → department → room picker. Creates a real inventory row.
14Retire AssetsBuiltPaste or CSV, up to 500 IDs. A live preview warns about an active contract, open WOs, recalls and overdue PM. Records retired_at / retired_by plus a history row. The “schedule a reminder” and “email the list” options are not built.
15Generate ReportBuiltAll 6 reports on the existing report pipeline, in the same formats as the sample: Joint Commission (PDF, XLSX), DNV GL (PDF, XLSX), State Survey (PDF), 90-day PM compliance (PDF, XLSX, CSV), FDA recalls (PDF, XLSX), contract coverage (XLSX, CSV).
16“Viewing as” role switcherNot builtAccess is controlled by the feature flag instead.
17/cmms-dashboardNot builtNo page. Team Stats covers part of it: first-fix rate, PM on-time, average close time and team load. Missing: SLA compliance %, PM completion %, labor cost per hour (no labor data in the schema), and the action feed.
—Asset check (nurse, read-only)Not builtIt was added on this branch and then removed. GET /cmms/asset-check remains, used only by New Work Order.
—CMMS map (not in the sample)BuiltThe Map view on the CMMS tab shows facilities whose assets have work orders.

Values

The work-order values now match the UI sample. The migration converts any existing rows: Repair → CM, Calibration → IN, In Progress → Open at First Touch, Completed or Cancelled → Closed with a reason.

FieldValuesvs UI sample
TypePM, CM, IN, Install, Project, Other (default PM)Same
StatusOpen, Under Customer, Escalated, ClosedSame
StageAssigned, First Touch, Resolution (default Assigned)Same
PriorityUrgent, High, Medium, LowSame
Closed reasoncompleted, cancelledNew; the sample has none
Activity actioncreated, assigned, reassigned, stage_changed, status_changed, note, escalated, closed, reopenedMatches the sample’s timeline
WO numberWO-{accountId}-00001Sample uses WO-2401
PO statusOrdered, Backordered, Delivered, CancelledSample has no Cancelled

Database

Full table changes and structures: CMMS tables →

One migration, 20261009104038-cmms-tab.js, runs in a single transaction and has a full down. It was applied cleanly to the local database on 14 Oct. No later development migration touches the risk functions it rewrites.

TableChange
work_order_activitiesNew. The drawer’s activity timeline, append-only.
work_orders13 new columns: stage, closer, requester, SLA and typed-in asset. Values converted as above.
usersis_technician, technician_title, technician_capacity. Users who already have work orders assigned are flagged.
service_contractscoverage_source: oem, third_party or self. Empty means derive it from vendor vs manufacturer.
camp_inventory_itemsretired_at, retired_by. The risk function returns Retired for assets the bulk retire marks.
parts_posUnchanged. vendor_name is still free text.

work_orders (30 columns)

ColumnTypeNullDefaultNotes
idintegernoserialPK
account_idintegernoFK → accounts
inventory_idintegeryesFK → camp_inventory_items (the asset), on delete set null
asset_name newvarchar(255)yesthe asset as typed; the only reference when there is no inventory link
work_order_numbervarchar(64)nounique per account
typevarchar(48)no'PM'PM, CM, IN, Install, Project, Other
priorityvarchar(24)no'Medium'Urgent, High, Medium, Low
statusvarchar(24)no'Open'Open, Under Customer, Escalated, Closed
stage newvarchar(32)no'Assigned'Assigned, First Touch, Resolution
assignee_user_idintegeryesFK → users (the technician)
opened_attimestamptznonow
due_attimestamptzyes
closed_attimestamptzyes
closed_reason newvarchar(32)yescompleted or cancelled
closed_by newintegeryesFK → users, on delete set null
requested_by_user_id newintegeryesFK → users, on delete set null
requested_by_name newvarchar(150)yes
requested_by_title newvarchar(150)yes
requested_by_email newvarchar(255)yes
requested_by_phone newvarchar(50)yes
sla_hours newintegeryeshours from opened_at until the SLA window closes (API accepts 1–720)
escalated_at newtimestamptzyes
escalation_target newvarchar(120)yesfor example “OEM · Level 2 Tech”
descriptiontextyes
notestextyes
created_byintegeryesFK → users
updated_by newintegeryesFK → users, on delete set null
created_at, updated_attimestamptznonow
deleted_attimestamptzyessoft delete

work_order_activities (new, 9 columns)

ColumnTypeNullDefaultNotes
idbigintnoserialPK
account_idintegernoFK → accounts, on delete cascade
work_order_idintegernoFK → work_orders, on delete cascade
actionvarchar(32)nocreated, assigned, reassigned, stage_changed, status_changed, note, escalated, closed, reopened
from_value, to_valuetextyesthe value before and after
notetextyes
actor_user_idintegeryesFK → users, on delete set null
created_attimestamptznonow

The analytics role reads both work_orders and work_order_activities per account only.

parts_pos (unchanged, 19 columns)

ColumnTypeNullDefaultNotes
idintegernoserialPK
account_idintegernoFK → accounts
po_numbervarchar(64)nounique per account
part_numbervarchar(128)yes
descriptiontextyes
vendor_namevarchar(255)yesfree text; there is no vendors table
quantityintegerno1
unit_costdecimal(12,2)yes
statusvarchar(24)no'Ordered'Ordered, Backordered, Delivered, Cancelled
ordered_attimestamptznonow
expected_at, received_attimestamptzyes
linked_inventory_idintegeryesFK → camp_inventory_items, on delete set null
linked_work_order_idintegeryesFK → work_orders, on delete set null
notestextyes
created_byintegeryesFK → users
created_at, updated_attimestamptznonow
deleted_attimestamptzyessoft delete

New columns on other tables

ColumnTypeNullDefaultNotes
users.is_technicianbooleannofalseshows the user in technician pickers and Team Stats
users.technician_titlevarchar(100)yesfor example “Sr. Biomed Tech”; falls back to the job title
users.technician_capacitysmallintno6open + active WOs that count as a full load; stored but not used by any endpoint yet
service_contracts.coverage_sourcevarchar(20)yesoem, third_party, self
camp_inventory_items.retired_attimestamptzyeswhen the CMMS retire archived the asset
camp_inventory_items.retired_byintegeryesFK → users, on delete set null

Reused, not new

API endpoints

All are in the primary role, behind sign-in plus the camp.manage_inventory.biomed feature. All are backed by the database; none is stubbed.

Work orders

EndpointOn this branchWhat it does
GET /work-orderschangedPaged list: search, saved view, type, status, stage, priority, assignee, facility, due range, sorting
POST /work-orderschangedCreate. Typed-in asset, requester and SLA fields. Safe numbering under a per-account lock
GET /work-orders/:idnewDrawer data: requester, SLA, asset and location, PM, contract, parts, timeline (newest 200)
POST /work-orders/:id/statusnewChange status (and stage), with a closed reason and a note
POST /work-orders/:id/assignnewAssign or reassign a technician; sends a notification
POST /work-orders/:id/stagenewChange the stage
POST /work-orders/:id/notesnewAdd a note to the timeline
PATCH /work-orders/:idchangedEdit fields
POST /work-orders/:id/closechangedClose as completed

CMMS (all new)

EndpointWhat it does
POST /cmms/summaryKPI tiles, banner counts and group totals
POST /cmms/assetsAsset list or one group’s rows, paged
POST /cmms/mapFacilities for the map view
POST /cmms/assets/searchNew Work Order’s asset search (top 6)
GET /cmms/team-statsTeam Stats panel
GET /cmms/techniciansTechnicians with open / active / done, least loaded first, paged
GET /cmms/technicians/candidatesUsers who can be made technicians (user managers only)
GET /cmms/technicians/:userIdOne technician
PUT /cmms/technicians/:userIdTurn technician on or off and set the title (user managers only)
POST /cmms/intakeCreate an asset from the Intake dialog
POST /cmms/retire/previewMatch up to 500 IDs and return warnings
POST /cmms/retireRetire the matched assets and record the audit
GET /cmms/asset-checkOne asset’s PM, contract, open WOs, recalls and risk, for New Work Order

Parts POs and reports

Front end

Issues found

#IssueEffectWhere
1Swagger client is out of dateThe front end imports CMMS client code that isn’t generated yet. It won’t compile until npm run swagger runs against a CAP-686 API.capExpertApp src/swagger
2No testsNo spec files for cmms, work-orders or parts-pos. One comment even refers to a spec that doesn’t exist.backendApi
3No WO History is always 0Every CMMS query first keeps assets that have a work order, then this count asks for assets with none. The UI no longer shows it; the API still returns it.cmms.service.ts:214, :266; cmms.assets.sql.ts:32-33
4KPIs cover only assets with a work orderA new account, or one with few WOs, sees near-empty tiles, banner and groups. Coverage % and PM Overdue are not for the whole fleet.cmms.service.ts
5A search of only spaces breaks the SQLPOST /cmms/assets/search with q = “  ” builds an empty WHEN and errors.cmms.service.ts:459-472
6DNV GL report total is mislabelledThe count of overdue assets appears under “Days Overdue”.dnv-pm-compliance.report.ts:66
7Status, assign, stage and note changes are not in a transactionA failure can leave the change without its timeline row, or the row without the change.work-orders.service.ts:209-284
8Retire stamps every matched IDAssets the bulk retire skipped still get retired_at and a history row, so “retired” can overcount. Retired components get no stamp. An ID shared by two assets matches only the first.cmms.service.ts:663-728
9Team Stats rules“SLA at risk” means Escalated or due today: it ignores sla_hours, which the drawer uses, and already-overdue rows. Only the first 20 technicians are listed.cmms.service.ts:533-569
10PO numbering can collideIt is still the count plus one with no lock (work orders now lock). Unchanged on this branch.parts-pos.service.ts:170-173
11Intake can create a manufacturerA free-text manufacturer becomes a new global manufacturer row when no “Other” exists.cmms.service.ts:629
12LeftoversThe unused PartsPosDataService and five unused data-service methods. Stale comments: “five-bucket”, “No WO History” in the tour, “demo” mentions. Hardcoded team targets (5 days, 75%, 90%, 8).capExpertApp, backendApi

The UI sample

Captured from the Vercel UI sample on 14 Oct 2026. It is a design reference, not our code. Its data is mock, and some of its buttons do nothing.

CMMS tab in the UI sample: KPI tiles, warranty banner, saved views and the work order table
CMMS tab. KPI tiles, warranty banner, saved views, work order table with Stage.
Work order detail drawer in the UI sample
Work order drawer. Stage, location, requester, SLA, contract, activity, parts.
New Work Order dialog in the UI sample
New Work Order. Asset search, type and priority, technician picker with load.
Update Status dialog in the UI sample
Update Status. Status, note, quick actions.
Team Stats panel in the UI sample
Team Stats. SLA at risk, per-tech load, team performance.
Generate Report dialog in the UI sample
Generate Report. Six compliance and operations reports.
Intake New Asset dialog in the UI sample
Intake Asset. Add a newly installed device.
Retire Assets dialog in the UI sample
Retire Assets. Batch retirement with warnings and audit.
CMMS Dashboard in the UI sample
/cmms-dashboard (not built). At a glance, performance tracking, action feed.
Asset check page in the UI sample: is this device safe to use
Asset check (not built). The nurse’s read-only “Is this device safe to use?”.

History

DateTicketChange
May 2026CAP-15First CMMS: work_orders and parts_pos tables, their APIs, and a CMMS view with four sub-tabs
12 May 2026CAP-2003CMMS tab hidden “until the feature is rebuilt”
Oct 2026CAP-686The rebuild: schema migration, CMMS and work-order APIs, six reports, the new CMMS tab, drawer and dialogs, technicians in user management. development merged in.