Skip to content
Q
QuoteNode

Wiki

Offer Lifecycle & State Machine

Deep dive into QuoteNode's offer status transitions, validation rules, and audit trail.

Offer Lifecycle & State Machine

Every offer in QuoteNode follows a strict, validated lifecycle. The system enforces valid transitions and rejects any attempt to move an offer to an invalid state.

Status Definitions

StatusDescriptionEditableTerminal
DRAFTOffer is being prepared. Not yet visible to the client.YesNo
SENTOffer has been delivered to the client via email or public link.NoNo
OPENEDClient has viewed the public link at least once.NoNo
NEGOTIATIONClient has replied with a question or counter-proposal.NoNo
ACCEPTEDClient has confirmed the order.NoYes
REJECTEDClient has declined the offer.NoYes
EXPIREDThe validity period has passed without a decision.NoYes
ARCHIVEDManually archived by the salesperson.NoYes

Transition Rules

DRAFT ──────────► SENT
  │                 │
  │                 ▼
  │              OPENED
  │                 │
  │                 ├──► NEGOTIATION
  │                 │        │
  │                 ├──► ACCEPTED (terminal)
  │                 ├──► REJECTED (terminal)
  │                 └──► EXPIRED  (terminal)

  └──► ARCHIVED (terminal)

Valid transitions

  • DRAFT → SENT — when the salesperson sends the offer via email or generates a public link
  • SENT → OPENED — when the client first views the public link (automatic)
  • OPENED → NEGOTIATION — when the client sends a reply message
  • OPENED → ACCEPTED — when the client clicks “Accept” on the public page
  • OPENED → REJECTED — when the client clicks “Decline” on the public page
  • SENT/OPENED/NEGOTIATION → EXPIRED — when the valid_until date passes (automatic, checked by scheduled job)
  • DRAFT → ARCHIVED — when the salesperson manually archives an unsent offer
  • NEGOTIATION → ACCEPTED — when the client accepts after negotiation
  • NEGOTIATION → REJECTED — when the client rejects after negotiation

Invalid transitions (rejected by state machine)

  • SENT → DRAFT (cannot un-send)
  • ACCEPTED → anything (terminal state)
  • REJECTED → anything (terminal state)
  • EXPIRED → anything (terminal state, but can be cloned)
  • ARCHIVED → anything (terminal state)

What Happens at Each Transition

DRAFT → SENT

  1. System validates that the offer has at least one line item
  2. An immutable snapshot is created (JSON document capturing the entire offer state)
  3. If sending by email: email is queued with PDF attachment and/or public link
  4. If generating a public link: a 256-bit random token is created (only the SHA-256 hash is stored)
  5. Status changes to SENT
  6. Timeline event is recorded on the customer
  7. Audit log entry is written

SENT → OPENED

  1. Client accesses the public link URL
  2. Token hash is matched in the database
  3. Status changes to OPENED (only on first view; subsequent views increment the counter)
  4. Web event is recorded: timestamp, IP (anonymizable), user agent, country (GeoIP)
  5. Salesperson receives a notification (if configured)

OPENED → ACCEPTED

  1. Client clicks “Accept” on the public page
  2. Rate limiting and bot detection are validated
  3. Optional order intent data is captured (name, email, company, message)
  4. Status changes to ACCEPTED
  5. Salesperson receives immediate notification
  6. Timeline event is recorded

Snapshots

A snapshot is a complete, immutable record of the offer at the moment it was sent:

  • Customer data (company name, address, contact person)
  • All line items (products, quantities, prices, discounts, VAT rates)
  • Calculated totals (subtotal, shipping, VAT breakdown, grand total)
  • Exchange rate used (for multi-currency offers)
  • Branding configuration (logo, colors, trading terms)
  • Template settings (which columns are visible, price display mode)

Snapshots are never modified. If an offer is resent after changes, a new snapshot version is created with an incremented version number.

PDFs are always generated from snapshots, never from live offer data. This guarantees that the PDF always matches exactly what the client was presented.

Audit Trail

Every status transition writes an immutable audit log entry containing:

  • User ID and role of the actor (or “system” for automatic transitions)
  • Source IP address
  • Previous status and new status
  • Timestamp (server-side)
  • Optional comment (e.g., rejection reason)

The audit log is append-only. Entries cannot be edited, deleted, or truncated — even by administrators.

Offer Operations After Terminal States

Terminal offers (ACCEPTED, REJECTED, EXPIRED, ARCHIVED) cannot be modified, but they can be used as the basis for new offers:

  • Clone — creates a new DRAFT with the same items and prices
  • Extend Validity — clones with a new expiration date
  • Update Prices — clones and recalculates from current catalog prices and FX rates
  • Use as Template — clones without customer or prices (blank template)

These operations always create a new offer with a new ID and DRAFT status. The original offer remains unchanged.

Last reviewed: Recently