Consolidated state machines for every entity in oxFlow that has one. Pulls together what’s distributed across the entity specs into a single reference, resolves the provisional state list in foundation/concept-map.md §7, and pins down transitions, triggers, and cascade rules.

Related: foundation/concept-map.md · foundation/roles-permissions.md


1. Entities with state

EntityStatesTrigger model
TenderActive · Submitted · Won · Lost · ArchivedMixed (auto on publish, manual for terminal states)
EstimateIn Progress · Reviewed · Submitted · ArchivedMixed (derived from Items for Reviewed, auto on publish, cascade from Tender)
ItemUnpriced · Plugged · Priced · Reviewed · LockedMostly derived (from Worksheet + plug_rate); Reviewed is manual; Locked cascades from Estimate
Price BookActive · ArchivedAuto (scope_end_date expiry) or manual
Price Book AdjudicationDraft · AdjudicatedManual (Transfer step); auto-freeze from Estimate cascade
Subcontract Package(none — derived from latest Adjudication)Structural
Subcontract Package AdjudicationDraft · AdjudicatedManual (Transfer step); auto-freeze from Estimate cascade
Tender Programpending · success · failedAuto (parse outcome)
Publisher OutputDraft · PublishedManual (Publish action)

Entities without a state machine: Heading · Worksheet · Resource · Recipe · Variable · Calculation Block · Content Block instance · Worksheet Resource · Worksheet Recipe · Company · User · Unit · Categorization · Code · Reference Rate · Program Task · Rule · Submission Value.


2. Tender

Outermost container. Five states covering the lifecycle from opportunity through outcome.

StateMeaningTriggerWho
ActiveDefault on creation. Estimates being builtTender createdAuto
SubmittedEstimate has been published to the client; awaiting decisionPublisher Output transitions to Published on a child EstimateAuto
WonClient accepted. Submitted Estimate is kept; all others archivedManual transitionAdmin + Lead Estimator
LostClient rejected. All Estimates archivedManual transitionAdmin + Lead Estimator
ArchivedWithdrawn or not pursued. All Estimates archivedManual transitionAdmin + Lead Estimator

Transitions:

 ┌───────────────┐
 │ Active │
 └──┬─────────┬──┘
 │ │
 (publish) (withdraw)
 │ │
 ▼ ▼
 ┌───────────────┐
 │ Submitted │
 └─┬────┬────┬───┘
 │ │ │
 (won)(lost)(withdraw)
 │ │ │
 ▼ ▼ ▼
 ┌─────┐ ┌─────┐ ┌────────┐
 │ Won │ │Lost │ │Archived│
 └─────┘ └─────┘ └────────┘
 (terminal — no further transitions)

Cascade to Estimates:

Tender →Effect on child Estimates
WonThe one submitted Estimate is retained (stays Submitted); all other Estimates → Archived
LostAll Estimates → Archived
ArchivedAll Estimates → Archived

Constraints:

  • Terminal states (Won / Lost / Archived) are non-reversible via the state machine. Admin recovery only.
  • Won requires exactly one Submitted Estimate to exist; otherwise the transition is rejected.

3. Estimate

A priced build-up inside a Tender. Four states.

StateMeaningTriggerWho
In ProgressDefault. Editing allowed; Items being builtEstimate createdAuto
ReviewedAll Items = Reviewed; ready for submissionAutomatic when every Item reaches Reviewed statusAuto (derived)
SubmittedPublisher Output has been published; Estimate fully lockedPublisher publishesAuto (on publish)
ArchivedNo longer active; read-only, recoverableTender cascade or manualAuto / Manual

Transitions:

 ┌──────────────┐
 │ In Progress │
 └──┬────────┬──┘
 │ │
 (all Items Reviewed) (archive)
 │ │
 ▼ │
 ┌──────────┐│
 │ Reviewed ││
 └──┬───────┘│
 │ │
 (publish) │
 │ │
 ▼ ▼
 ┌──────────────────┐
 │ Submitted │
 └────────┬─────────┘
 │
 (Tender cascade)
 │
 ▼
 ┌──────────┐
 │ Archived │
 └──────────┘

Reviewed → In Progress can happen two ways:

  • Manual — Lead Estimator reopens for edits
  • Automatic — rate-change cascade drops any Item below Reviewed, which un-satisfies the “all Items = Reviewed” condition and reverts the Estimate to In Progress

Submit guard: the Submit action is blocked if any Item has status Unpriced or Plugged. All Items must be Priced, Reviewed, or Locked. Publisher Output preview is always accessible — only the final Submit action runs the gate.

Locked behaviour:

  • Submitted → read-only across all children (Items, Worksheets, Commercials, Adjudications)
  • Tender cascade moves Estimate → Archived (also read-only)
  • Only Admin can unlock (recovery scenario)

4. Item

The priceable unit. Five states; four of them derived, one manual, one cascaded.

StateMeaningTrigger
UnpricedEmpty Worksheet AND no plug_rate setDerived
Pluggedplug_rate set, Worksheet has no cost-contributing childrenDerived
PricedWorksheet has cost-contributing Resources, Recipes, or sub-ItemsDerived
ReviewedPriced + senior review complete, no blocking anomaliesManual (Lead Estimator / Admin)
LockedParent Estimate is Submitted; no edits allowedCascade from Estimate

Transitions:

  ┌───────────┐
  │ Unpriced  │
  └─────┬─────┘
        │ (set plug_rate)
        ▼
  ┌───────────┐
  │ Plugged   │
  └─────┬─────┘
        │ (add Worksheet Resource / Recipe — clears plug_rate)
        ▼
  ┌───────────┐
  │ Priced    │◄──────────┐
  └─────┬─────┘           │
        │                 │ (rate change cascade
        │ (mark reviewed) │  drops back to Priced)
        ▼                 │
  ┌───────────┐           │
  │ Reviewed  │───────────┘
  └─────┬─────┘
        │ (Estimate → Submitted)
        ▼
  ┌───────────┐
  │ Locked    │
  └───────────┘
   (Admin unlock only can reverse)

Note: Unpriced → Priced is also reachable directly (add Worksheet Resource / Recipe to an empty Worksheet with no plug_rate set).

Derivation rules:

  • plug_rate set ∧ Worksheet empty → Plugged
  • plug_rate null ∧ Worksheet empty → Unpriced
  • Worksheet has cost-contributing children → Priced (and plug_rate is auto-cleared — mutual exclusion)

Rate-change cascade: any rate edit on a Worksheet Resource (per-instance / apply-to-Estimate / fork — see rate-edit-mechanics.md) drops affected Items from Reviewed back to Priced. Senior review must be re-done.

Locked cascade:

  • Estimate → Submitted drives all Items → Locked
  • Estimate → Archived leaves Item state untouched structurally but the Items are read-only via Estimate’s read-only behaviour

5. Price Book

Two states. Auto-archives by date; manual archive also supported.

StateMeaningTriggerWho
ActiveCurrent, in-use rates; available for Worksheet Resource drag-inDefault on creation
ArchivedSuperseded or out-of-window; read-only; queryable by Anomaly ReviewAuto when scope_end_date < today, or manualAuto / Lead Estimator / Admin

Transitions:

Active ──(scope_end_date < today, auto)──▶ Archived
Active ──(manual archive)────────────────▶ Archived
Archived ──(unarchive)───────────────────▶ Active

Auto-archive: evaluated at query time. Archived Price Books don’t appear in the default drag-in picker. Existing Worksheet Resources that reference them continue to hold their snapshots.

Unarchive is infrequent — used when historical rates need to be reactivated for reference.


6. Price Book Adjudication

Two states. Follows the six-step workflow; see price-book-adjudication.md.

StateMeaningTriggerWho
DraftAssembling scope, importing returns, comparing, normalisingAdjudication createdEstimator · Lead Estimator · Admin
AdjudicatedAward applied — winning rates replaced on scoped ResourcesTransfer step completedSame

Transitions:

Draft ──(Transfer / Award)──▶ Adjudicated
Adjudicated ──(Re-open)─────▶ Draft (new round)

Round semantics: each re-open increments round_number and produces a new round. The scoped Resource set is updated again on award.

Re-open gate: re-open is blocked once the parent Estimate transitions to Submitted (or Won / Lost / Archived). Adjudications are frozen alongside the Estimate.


7. Subcontract Package Adjudication

Same state machine as Price Book Adjudication. See subcontract-package-adjudication.md.

StateMeaningTriggerWho
DraftScope set (Package + competing Subcontractors), returns importedAdjudication createdEstimator · Lead Estimator · Admin
AdjudicatedAward applied — system-generated Price Book created/updated; Subcontract Resources written; Worksheet Resources linkedTransfer stepSame

Transitions:

Draft ──(Transfer / Award)──▶ Adjudicated
Adjudicated ──(Re-open)─────▶ Draft (new round)

Round semantics: each round adjudicates the Package’s current Item membership. The system-generated Price Book persists across rounds (same ID; contents updated in place).

Item-membership gate: Items can be added to or removed from the parent Package only while the current Adjudication round is in Draft. Once Adjudicated, the Item set is frozen until re-opened.

Re-open gate: same as Price Book Adjudication — blocked once the parent Estimate is Submitted.


8. Subcontract Package — derived state

A Subcontract Package has no state attribute of its own. Its effective state is derived from its latest Adjudication:

Derived stateCondition
Draft (package)Latest Adjudication is Draft
Adjudicated (package)Latest Adjudication is Adjudicated

Item-membership editability follows directly: editable when Draft, frozen when Adjudicated.


9. Tender Program (parse lifecycle)

Three states reflecting the parse outcome of the uploaded MS Project file. Not a workflow state — a processing state.

StateMeaning
pendingFile uploaded; parse not yet complete
successTasks extracted; Program Tasks available for linking to Items
failedParse error (corrupt file, unsupported format); flagged in Anomaly Review with error details

Transitions:

pending ──(parse OK)──▶ success
pending ──(parse err)─▶ failed
failed ──(retry / re-upload — new record)

Re-uploading the .mpp creates a new Tender Program record (immutable history); Program Tasks are reconciled by ms_project_id. Only the latest successful Tender Program is active for linking.


10. Publisher Output

Two states. Only the latest Publisher Output is retained per Estimate.

StateMeaningTriggerWho
DraftCover letter, conditions, branding being edited; preview accessiblePublisher Output opened for editingAdmin · Lead Estimator
PublishedFinal artifact generated; parent Estimate transitions to SubmittedPublish action (gated by Submit check)Admin · Lead Estimator

Transitions:

Draft ──(Publish, Submit gate)──▶ Published

Published is terminal for that Publisher Output record — republishing replaces it with a new draft that, on Publish, supersedes the prior Output. No version history is retained.

Submit gate: Publish is blocked if any Item in the Estimate is Unpriced or Plugged. Preview is always accessible.


11. Cross-entity cascade summary

Single table showing how state changes ripple across the model.

TriggerCascade
Estimate → ReviewedNone (derived state only; no writes elsewhere)
Estimate → SubmittedAll child Items → Locked; Commercials Rules, Adjudications, Publisher Output frozen
Tender → WonOne submitted Estimate kept as Submitted; other Estimates → Archived; kept Estimate promoted to project (outside oxFlow)
Tender → LostAll Estimates → Archived
Tender → ArchivedAll Estimates → Archived
Worksheet Resource rate change (per-instance / apply-to-Estimate / fork)Affected Items: Reviewed → Priced. Estimate may revert Reviewed → In Progress if it was Reviewed
Adjudication → Adjudicated (PBA)Scoped Resource rates replaced in place; Worksheet Resources still carry old snapshots → Anomaly Review flags divergence
Adjudication → Adjudicated (SPA)System-generated Price Book created/updated; Items in Package get new Worksheet Resources; status cascade on Items (Unpriced/Plugged → Priced)
Price Book scope_end_date < todayPrice Book → Archived; excluded from default drag-in picker; existing Worksheet Resources unaffected (snapshots hold)
Worksheet content added to empty WorksheetParent Item: Unpriced/Plugged → Priced; plug_rate auto-cleared
plug_rate set on empty WorksheetParent Item: Unpriced → Plugged