1. Purpose
A Tender is an external pricing opportunity — the outermost container in oxFlow. It captures the opportunity metadata (client, location, due date, win probability, status) and holds one or more Estimates (see estimate.md).
One client per Tender (BR-090); multi-client scenarios → separate Tenders.
Tender is tightly coupled to Estimate: Tender state cascades to Estimates (BR-094). See §4 for the cascade rules.
2. Attributes
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string (text) | ✅ | — | Tender name; e.g., “Acme Corp Office Refurb” |
number | string (free text) | ✅ | — | Internal tracking number (e.g., “TND-2026-042”). Not client-facing |
client_ref | string | ❌ | null | Client’s reference number (e.g., their RFT/RFP ID). For cross-reference |
client_id | Company ref | ✅ | — | The Client (Company with Client role). Exactly one per Tender (BR-090) |
location | string | ❌ | null | Project location / site address |
contract_start_date | date | ❌ | null | Planned contract commencement |
tender_due_date | date | ✅ | — | When the proposal is due to the client |
win_probability | enum | ❌ | null | Low / Medium / High. Reporting metadata; no behavioural impact |
status | enum | ✅ | Active | Active · Submitted · Won · Lost · Archived. See §4 |
categorization_options | many-to-many | ❌ | [] | Multi-select Categorization Option references (Tenders can be tagged against any Categorization whose scope includes Tender) |
tender_program_id | Tender Program ref | ❌ | null | Optional MS Project file upload (1:0..1, BR-100) |
notes | long text | ❌ | null | Free-form notes |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
3. Relationships
| Direction | Entity | Cardinality | Required | Notes |
|---|---|---|---|---|
| Outbound | Client (Company) | Tender M:1 Company | ✅ | Client role; one per Tender (BR-090) |
| Outbound | Tender Program | Tender 1:0..1 Tender Program | ❌ | Optional MS Project; at Tender level not Estimate (BR-100) |
| Inbound | Estimate | Tender 1:M Estimate | ✅ | One or more Estimates per Tender (BR-091) |
| Inbound | Categorization Option | Tender M:M Categorization Option | ❌ | Multi-select tagging for reporting |
4. Lifecycle States
| State | Meaning | Trigger | Manual/Auto | Role |
|---|---|---|---|---|
| Active | Initial state. Tender is being prepared; Estimates are being built | Tender creation | Auto | — |
| Submitted | One or more Estimates have been delivered to the client | Publisher publishes (BR-094 implies Submit transition) | Auto (on publish) | — |
| Won | Client has accepted the proposal. The submitted Estimate is promoted to project; all others archived (BR-094) | Client decision (manual transition) | Manual | Admin + Lead Estimator |
| Lost | Client has rejected the proposal. All Estimates archived (BR-094) | Client decision (manual transition) | Manual | Admin + Lead Estimator |
| Archived | Tender withdrawn or not pursued. All Estimates archived (BR-094) | Manual transition (from Active or Submitted) | Manual | Admin + Lead Estimator |
Role constraint: Manual Tender state transitions (Won, Lost, Archived) are restricted to Admin + Lead Estimator roles.
State transition diagram:
┌─────────────────────┐
│ Active │
│ (Estimates building)│
└────┬────────────┬───┘
│ │
(publish) (not pursued)
│ │
▼ ▼
┌─────────────────────────┐
│ Submitted │
│ (awaiting client reply) │
└────┬────────────┬───┬───┘
│ │ │
(client) (loss) (withdraw)
│ │ │
┌────────────▼─┐ ┌──────▼──▼───┐
│ Won │ │ Archived │
│ (promoting) │ │ (terminal) │
└──────────────┘ └─────────────┘
(terminal)
Terminal states: Won, Lost, Archived
Cascade on Tender state change (BR-094):
| Tender → | Effect on Estimates |
|---|---|
| Won | The one submitted Estimate is kept (remains Submitted). All other Estimates under the Tender are archived. Kept Estimate is promoted to project (outside Estimate scope). |
| Lost | All Estimates under the Tender transition to Archived |
| Archived | All Estimates under the Tender transition to Archived |
5. Validation / Invariants
- Client mandatory. Every Tender has exactly one Client (Company with Client role). BR-090.
- At least one Estimate. Every Tender has one or more Estimates (BR-091).
- State enum. Tender status ∈ {Active, Submitted, Won, Lost, Archived}.
- Cascade on Won. When Tender → Won, exactly one Estimate is kept (the submitted one); all others → Archived. If no submitted Estimate exists, this is an error state.
- Terminal states non-reversible. Once a Tender reaches Won/Lost/Archived, manual transitions are blocked.
- Tender Program optional.
tender_program_idmay be null (BR-100). - Unique identification.
numberis free-text and may collide; not enforced unique.
6. Worked Examples
Example 1 — Base Case, Alternative, and Strategy Estimates (three variants)
A Tender for a commercial office refurbishment with three submission strategies:
Tender: "Acme Corp Refurb"
number: TND-2026-042
client_id: <Company "Acme Corp">
location: "North Tower, 42 Main St"
tender_due_date: 2026-05-15
status: Active
Estimate A: "Base Case" (estimate_number: "1")
lead_estimator_id: <User "Alice">
status: In Progress
Contents: Standard scope, core trades, no premium finishes
Headings: Demolition, Structural, Fit-out, Services
Items: 45 schedule items + internal build-up
Estimate B: "Alternative Premium" (estimate_number: "alt")
lead_estimator_id: <User "Bob">
status: In Progress
Contents: Base scope + premium finishes, upgraded materials
Headings: same structure as A
Items: 48 schedule items
Estimate C: "Strategy Fast-Track" (estimate_number: "fast")
lead_estimator_id: <User "Charlie">
status: In Progress
Contents: Phased delivery; core works Phase 1, finishes Phase 2
Headings: Phase 1 structure, Phase 2 structure
Items: 52 items (broken across phases)
Tender state flow:
1. Active: Alice, Bob, Charlie build independently. No cross-collaboration at Item level (different branches).
2. Lead reviews → Estimate A & B → Reviewed state. Estimate C still In Progress.
3. Decision: Submit Estimate A (Base Case) to client → Publisher output generated → Tender → Submitted, Estimate A → Submitted (locked).
4. Estimates B & C remain In Progress (not submitted).
5. Client accepts Base Case → Tender → Won.
6. Cascade: Estimate A stays Submitted. Estimates B & C automatically → Archived.
Result: Tender complete. Estimate A (Base Case, locked) retained; B & C archived but recoverable.
Example 2 — Tender Lost; All Estimates Archived
A Tender for bridge works that fails to win:
Tender: "Interstate Bridge Retrofit"
number: TND-2026-015
client_id: <Company "State Highways Authority">
location: "Bridge span 7, Route 3"
tender_due_date: 2026-06-01
status: Active
Estimate "Main Bid" (estimate_number: "rev a")
lead_estimator_id: <User "David">
status: Submitted (locked) [was submitted yesterday]
Items: 120 items across 8 heading groups (earthworks, structures, finishes, etc.)
Tender state flow:
1. Estimate submitted → Tender → Submitted.
2. Waiting for client decision...
3. Client notifies: project awarded to competitor.
4. Tender → Lost (manual transition).
5. Cascade: Estimate → Archived.
6. Result: Tender now read-only. All Estimates read-only. Archive available for historical reference / retrospective.
Users can still view the Estimate and its Items (audit trail, learning), but cannot edit.