Documentation

How data flows between sections

Five different parts of the product create work, and they all converge on one record — the work order. Follow that spine and you can explain almost any number in the system.

The work-order spine

Whatever the origin, the created work order carries a source field naming where it came from, a location_node_id, usually an asset_id, and a generated organization-scoped number. From that point every module reads the same record.

SOURCES Service request portal · approved → converted PM schedule recurring or meter due Inspection each failed checklist item Fleet trigger mileage · hours · breakdown Work order number · source · location asset · priority · status draft → assigned → closed DOWNSTREAM Asset history condition, downtime, cost Inventory ledger reservations and issues Compliance verification and evidence Audit trail immutable, exportable Always attached, whatever the source Organization scope · location-node path · append-only history events · SLA clock · notifications to followers · metrics

1. Request to work order

The most common path, and the one to demo first.

1

Submission — requester portal

A staff member creates a ServiceRequest against a location node and optionally an asset, with a category, description and priority. Status submitted. Followers can subscribe to it.

2

Triage — operations panel

A supervisor approves or rejects. Every decision is written as a ServiceRequestTriageEvent; clarification questions to the requester are RequesterMessage records visible on both sides.

3

Conversion — the hand-off

Only an approved request converts. The action copies organization, priority, location and asset onto a new work order with type=corrective and source=service_request, sets the request to converted, and links them one-to-one. Re-running it returns the existing work order rather than creating a second one.

4

Assignment — dispatch board

The supervisor assigns a technician, team or vendor and a planned window. Status moves to assigned and the work order appears in that technician's workbench and in the mobile app's changes feed.

5

Execution — workbench or mobile

Start, pause and resume are recorded with timestamps; paused minutes accumulate as hold time and are excluded from actual duration. Tasks, readings, notes, before/during/after photos and field evidence all attach to the work order. Blockers escalate to the supervisor.

6

Completion, approval, closure

Completion requires an action, a root cause, notes and whatever evidence the completion rules demand. The supervisor approves, then closes. Reopening creates a new history event rather than rewinding the old one.

7

Back to the requester

Resolution marks the originating request resolved. The requester confirms it (status confirmed) or reopens it (status reopened), and can leave or withdraw feedback. That is the loop closed.

2. Preventive maintenance

A PmSchedule targets an asset or a location node and carries a recurrence — daily through annual, custom, or meter-based — plus an optional checklist version and job plan.

  • The recurrence calculator produces the next due date. Meter schedules instead watch asset or fleet readings and fire when the interval is crossed.
  • Generation is keyed by (schedule, due key) and recorded in pm_schedule_generations with a unique constraint, so a retry, a double-click or two concurrent workers all collapse to a single work order.
  • The generated order has source=pm and inherits the schedule's target, team and job plan tasks.
  • Advancing the schedule after generation depends on its completion policy: fixed keeps the original cadence, from_completion re-bases the next due date on when the work actually finished.
  • PM compliance is measured as generated-versus-completed-on-time and surfaces in reports.

3. Inspections and corrective work

1

Template → version

Checklist templates are edited freely, then published into an immutable ChecklistTemplateVersion. Inspections always bind to a version, so editing a template never rewrites history.

2

Schedule → run

An inspection is scheduled against a location or asset and an inspector. Starting it opens a ChecklistRun; each answered item becomes a ChecklistRunResponse with a result of pass, partial, fail or not applicable.

3

Score and evidence

The scorer computes a weighted result live, so the inspector sees the outcome before submitting. Photos and a signature attach as InspectionEvidence.

4

Completion raises work

On completion, every failed item becomes one corrective work order, keyed idempotently by inspection and item position and linked through InspectionCorrectiveWork. The failed item's prompt becomes the work summary.

5

Triage → verification → evidence

Compliance triages failed items, and once the corrective work order is closed a verifier accepts or rejects the fix as a CorrectiveVerification. Accepted verifications and the inspection report feed ComplianceEvidence against an obligation.

4. Fleet triggers

Fleet vehicles are their own register but they borrow the maintenance spine. Recording an odometer or engine-hour reading, logging a defect, failing a checkout inspection or reporting an accident can raise a work order with type=fleet_maintenance and a source such as fleet_mileage or fleet_breakdown. Breakdowns and accidents are raised at urgent priority; the rest at medium.

Each trigger is stored with a trigger_key so the same 10,000 km milestone can never raise two orders. The vehicle must have a location node — that is what puts fleet work on the same board as building work.

5. Inventory and cost

  • Reserve ties stock to a specific work order before the technician travels.
  • Issue consumes it — against the reservation if there is one — and writes an immutable StockLedgerEntry that names the work order.
  • Return, receive, adjust and transfer are all further ledger entries; balances are derived, never stored as an editable number.
  • Issued parts plus approved labor entries are what the cost calculator rolls up onto the work order, and from there onto the asset's lifetime cost.
  • Falling below an item's reorder point produces a ReorderProposal; stocktake sessions reconcile counted against ledger quantities.
This is the answer to "why does this asset look expensive?" — every figure traces back to a ledger entry or a labor entry on a specific work order.

6. Mobile and the API

The Flutter app never talks to the web UI; it uses /api/v1 with a Sanctum token and mirrors the same lifecycle.

Concern How the API handles it
Sync GET /work-orders/changes returns what changed since a cursor, so a device coming back online catches up rather than refetching.
Duplicate submissions Every mutation runs behind an idempotency key, so a retried start/complete applies once.
Stale offline edits A staleness check rejects a mutation made against a version of the record the server has since moved past.
QR scanning GET /assets/scan/{token} resolves a printed asset label straight to the asset and its location path.
Evidence POST /work-orders/{id}/evidence uploads photos under a separate upload rate limit.

7. Tracing one record end to end

When a UAT participant asks "where did this come from?", follow the chain:

ServiceRequest  --(converted)-->  WorkOrder.service_request_id
PmSchedule      --(generation)-->  PmScheduleGeneration.work_order_id
Inspection      --(failed item)-->  InspectionCorrectiveWork.work_order_id
FleetAsset      --(trigger)----->  FleetMaintenanceTrigger.work_order_id

WorkOrder
  ├─ tasks, labor entries, notes, photos, field evidence, blockers, permits
  ├─ history events        (who moved it, when, from which status)
  ├─ stock ledger entries  (which parts, from which store)
  ├─ follow-up work orders (parent_work_order_id)
  └─ audit events          (immutable, exportable)

Every one of those links is a real foreign key you can check in the panel, in the API response, or with make psql during UAT.

Next: how to test it →