> How to design RFQ and quote management in B2B: states, versioned quotes, price overrides, expiry rules, order conversion and a build checklist.
>
> Web page: https://balazscsorba.com/blog/b2b-rfq-quote-management · Language: English · Also available in: [Deutsch](https://balazscsorba.com/de/blog/b2b-rfq-quote-management.md) · [Magyar](https://balazscsorba.com/hu/blog/b2b-rfq-quote-management.md)
> Author: Balázs Csorba · Published: 2026-10-07 · Keywords: B2B quote management, RFQ workflow, request for quote e-commerce, quote to order conversion, quote versioning, quote expiry capture, Spryker quote request, negotiable quotes Adobe Commerce, quote approval workflow, quote data model

[Blog](https://balazscsorba.com/blog)/Web engineering

# RFQ and quote management in B2B: workflow, versioning and order conversion

How to design RFQ and quote management in B2B: states, versioned quotes, price overrides, expiry rules, order conversion and a build checklist.

[Balázs Csorba](https://balazscsorba.com/about)·October 7, 2026·12 min read

-   B2B quotes
-   RFQ workflow
-   Quote versioning
-   Order conversion
-   Quote expiry

![Three document versions in a row with a clock above the last.](https://balazscsorba.com/images/blog/b2b-rfq-quote-management/cover.webp?v=64a6dc0c5f)

## Key takeaways

-   An RFQ feature is a state machine on a document; decide the states, versions, expiry, permissions and conversion before you design screens.
-   Every change to an agreed field creates a new, immutable quote version, and old versions stay readable for the audit trail.
-   Price overrides belong on version lines with an author, role-based thresholds and a recorded approver; discounts are data, not form state.
-   Store validity on the version, enforce it at acceptance and again at conversion, and decide explicitly what expiry does.
-   Make conversion to an order idempotent on the quote and the accepted version, and keep LLM assistance around the document, never in pricing.

On this page

1.  [Is an RFQ feature a form or a state machine?](https://balazscsorba.com/#answer-rfq-is-a-workflow-not-a-form)
2.  [What is the lifecycle from cart to order?](https://balazscsorba.com/#lifecycle-cart-to-order)
3.  [What must a quote version contain?](https://balazscsorba.com/#versioning-and-negotiation)
4.  [Who may discount, and who may approve?](https://balazscsorba.com/#permissions-and-price-overrides)
5.  [What should happen when a quote expires?](https://balazscsorba.com/#expiry-and-re-quoting)
6.  [How does an accepted quote become an order?](https://balazscsorba.com/#conversion-to-order-and-erp)
7.  [What do the platforms actually ship?](https://balazscsorba.com/#platform-support)
8.  [What does the data model look like?](https://balazscsorba.com/#data-model)
9.  [Where does LLM assistance fit, and where does it not?](https://balazscsorba.com/#llm-assistance-later)
10.  [Build checklist: tables, states and tests](https://balazscsorba.com/#build-checklist)
11.  [Sources](https://balazscsorba.com/#sources)

Listen to this article

0:000:00

**An RFQ feature in B2B is a state machine on a document.** A buyer asks for a price on a cart, the seller answers with a number, the buyer counters, and at some point someone accepts. Teams usually start by building a form. Months later they discover that the quote lives in email, the price has no validity date, a seller changed a line after the buyer accepted it, and nobody can say which version the order was based on.

This post treats the quote as a versioned, permission-bound object with an expiry date, an audit trail and a conversion step into an order. It covers the lifecycle, the versioning rules, who may discount and approve, what expiry should do, and a data model with a build checklist. Pricing rules and configuration are a separate problem, covered in [headless product configurators for B2B](https://balazscsorba.com/blog/headless-product-configurator-b2b), and conversation handling is covered in [AI support for B2B shops](https://balazscsorba.com/blog/ai-assistant-b2b-shop-customer-support). The LLM side of quoting is in [LLM-assisted RFQ automation](https://balazscsorba.com/blog/llm-rfq-quote-automation). Here the focus is the platform and the workflow.

## Is an RFQ feature a form or a state machine?

**A request for quote is a state machine on a document, not a form that sends an email.** Five product decisions define it: the states a quote can be in, whether each change creates a version, who may change prices and by how much, when a quote stops being valid, and how an accepted quote becomes an order. Make those five decisions first and the screens follow. Leave them open and the feature gets rebuilt in email, spreadsheets and chat threads, with the same problems.

## What is the lifecycle from cart to order?

**The lifecycle runs from a cart to a draft quote, through negotiation and approval, to an order.** The names differ, but the shape is similar across platforms. Spryker lets a buyer create a request for quote from a shopping cart, add a note, define the delivery address and shipment method, and see the negotiation history. After submission the RFQ is in status `Draft`, and an agent can revise it and send it back. Buyers can also convert an RFQ back into a cart. [Spryker’s RFQ documentation](https://docs.spryker.com/docs/pbc/all/request-for-quote/latest/request-for-quote) describes these steps, but its last-updated date is old, so treat its field list as a starting point and check the release you run.

Adobe Commerce B2B uses a different vocabulary with a similar shape. A request appears as New, and the buyer can still change it until the seller opens it. The seller saves a draft or sends it, which puts it in Submitted, and then the seller cannot modify it until the buyer sends it back. The buyer sees Pending while the negotiation runs. [Adobe’s quotes documentation](https://experienceleague.adobe.com/en/docs/commerce-admin/b2b/quotes/quotes), updated in October 2026, is the most explicit on who can touch the quote in each status, which is the detail a permission matrix needs.

SAP Commerce Cloud’s Commerce Quotes module makes the same point from the other side. The quote is negotiated by both parties until an agreement is reached, and then the buyer checks out the quote and places an order. In the 2205 documentation the status depends on the role, so the buyer sees Submitted while the sales rep sees Requested for the same quote.

![Quote states from cart to order Five states from left to right: a cart becomes an RFQ, the buyer submits it, a revised version goes back and forth until the price is accepted, and the accepted quote is converted into an order. Expired quotes leave the loop and must be re-quoted as a new version.](https://balazscsorba.com/images/blog/b2b-rfq-quote-management/diagram-8.webp?v=0de008f46f)

The quote loop: a submitted quote is revised as many times as needed, each revision is a new version, and acceptance locks the price before conversion to an order.

The states to model are the union of those vocabularies: a cart-derived request, a draft that only the seller sees, a submitted state the buyer owns, a revised state with a new version, an accepted state with locked prices and shipping, a converted state that points at an order, and a terminal expired or cancelled state. Keep the internal names stable and map them to role-specific labels in the interface. A buyer and a sales rep looking at the same quote can see different words for the same fact, as SAP does, but the fact must be one record.

## What must a quote version contain?

**A quote version is an immutable snapshot of prices, quantities, shipping and comments at one negotiation step.** Versioning is what separates a quote from a discussion. Without it, the seller edits the price in place, the buyer accepts a number that no longer exists, and an audit question cannot be answered. The safe rule is that a change to an agreed field creates a new version, and old versions stay readable.

SAP makes this explicit: as a quote moves through negotiation, a copy of the quote is created for the next step in the workflow. Spryker treats quote request versions as data. Its data import specification describes a file that configures versions by reference, company user and status. Adobe keeps a buyer and seller status pair on the request and lets both sides change quantities, remove items, add line comments and adjust discounts before acceptance.

Each version needs at least these things: line quantities and unit prices; per-line discounts or overrides with their author; shipping method and cost; line comments; attachments such as specification documents; and the actor and timestamp of the change. Shopware’s quote entity model is a useful reference for the split. It separates the quote, a delivery record with shipping method and shipping dates, line items, a transaction record with the payment amount and an external reference, comments, employee assignments and documents. Its documentation says only the product line item type is supported currently, so a service or freight line needs your own model.

Re-quoting after expiry must not lose history. The new offer is a new version linked to the previous one, so the audit trail still shows the original price, the expiry and the reason for the new offer.

## Who may discount, and who may approve?

**Permissions decide who may discount and who may approve, and they belong in explicit configuration per company role, not in code paths.** Two governance dimensions show up across platforms. The first is value: how much may be discounted, and when a manager must approve. The second is budget: which cost center and which budget a quote draws on.

Spryker’s Approval Process requires a manager’s approval before checkout. It is configured per company role and starts when the cart total exceeds an amount set in the Buy up to grand total permission. Its Purchasing Control feature adds budgets and cost centers. Buyers assign them to a cart at checkout or to a quote request, and agents can do the same on a customer’s behalf. These are two separate checks, and a quote may need both.

Adobe takes a different route on discounts. During negotiation the seller can apply a line-item discount or a quote-level discount, and the documentation states that additional discounts cannot be added once a negotiated quote has been accepted and moves to checkout. That rule is worth copying: after acceptance the price is a contract term, not an editable field.

The rule to build is a price-override policy with thresholds. A rep may override a line within a band; above it a second role must approve; below a floor the system refuses. Store the policy as data per company or role, and record the approver on the line. Hard-coded percentages in the quote service are how a discount rule becomes a support ticket.

**Price is a stored field, not UI state**

A discount typed into a form is only a suggestion until it is saved on a version line with an author and a timestamp. Anything that changes a price after acceptance needs a new version and a new approval, never a silent update to the accepted one.

## What should happen when a quote expires?

**Expiry is a stored date on the version, enforced by a job, and it should end in a visible state rather than a silent one.** A quoted price is a promise for a period, and platforms handle that period differently. Adobe Commerce has a Default Expiration Period that can be set in days, weeks or months. Spryker agents can set the validity of an offer down to the minute. Odoo’s quotation documentation includes an expiration date field, and quotation templates can carry an expiration deadline.

The platform documentation I read does not settle what happens at expiry; the Adobe pages do not say whether a quote closes automatically or only warns. Decide it yourself. Three choices are common: lock the quote so the buyer can no longer accept it, reissue it as a new version with a fresh price that the seller must approve, or warn both sides before it lapses. A sensible default is a warning before expiry, a lock at expiry, and a one-click re-quote that creates a new version from the old one and keeps the old price visible in the audit trail.

Store validity on the version, never in the interface. If the browser decides whether a quote is still valid, a buyer can accept a stale price from an old tab, and a background job cannot tell the truth. The check at acceptance must read the stored timestamp, and the same check should run again at conversion, because an acceptance from last week is not a conversion from today.

## How does an accepted quote become an order?

**Conversion is the step where the negotiated document becomes a commercial order, and it must happen exactly once.** In a shop-first setup, the accepted quote goes through the normal checkout with its prices carried over. Adobe applies negotiated pricing at checkout and locks shipping and discounts. Spryker can convert an RFQ back into a cart, which then passes through the usual checkout, including any approval.

In an ERP-first setup, the accepted quote becomes a sales order in the ERP, and the shop only displays it. SAP’s documentation describes the buyer checking out the quote and placing an order. How contracts and recurring orders are created from an accepted quote depends on the ERP, and this article does not check that step.

Whichever path you choose, conversion needs an idempotency key, such as the quote id together with the accepted version number. Two clicks, or a retry after a timeout, must produce one order. The same reference should travel into the ERP sales order, so a retry finds the existing document instead of creating a second one.

## What do the platforms actually ship?

**The major platforms ship negotiation features, but they differ on line discounts, approval and expiry, so check the table before you choose.** Dates matter because these features move. Spryker’s RFQ page is dated, while its Purchasing Control and Approval Process pages are more recent. The SAP statuses cited here come from the 2205 documentation, and newer releases may restructure the module.

Shopware’s Quote Management sits in B2B Components, which belong to the Commercial extension and are available as of the Evolve plan, not in the Community Edition. A merchant can add comments and discounts to individual quote items, include shipping costs, and track changes through the quote, message and document history.

Pimcore’s open-source E-Commerce Framework bundle is end of life as a GPL version, and the framework has moved to the Enterprise Edition. A Pimcore shop that needs quotes is therefore most likely building them on Data Objects with Pimcore’s own versioning, or licensing the Enterprise bundle. I have no public price to compare. Product data and its ERP sync are a different problem, covered in [Pimcore ERP delta sync](https://balazscsorba.com/blog/pimcore-erp-delta-sync).

Platform

Negotiation and versions

Line discount by seller

Approval and budget

Expiry

Spryker

RFQ from cart; agent revises and sends back; versions as data

Rep can edit and overwrite prices

Approval Process per role; budgets and cost centers on RFQs

Validity set to the minute

SAP Commerce Cloud (2205 docs)

Copy of the quote per step; statuses differ by role

Not verified in the pages read

Separate thresholds for quote and seller approver

Not verified in the pages read

Adobe Commerce B2B

New, Submitted, Pending; both sides exchange changes

Yes: line-item or quote-level

Gated by admin role resources

Default Expiration Period in days, weeks or months

Shopware 6 Commercial

Quote management; history of quote, messages and documents

Yes: comments and discounts per quote item

Order Approval and Organization Units listed

Not verified in the pages read

Pimcore (custom)

Data Objects with Pimcore versioning

Your code and policy

Your code and policy

Your code and policy

## What does the data model look like?

**The data model has one stable quote, a version for each negotiation step, lines that belong to a version, and message and audit tables that point at the quote or the version.** A flat quote table with one price column cannot represent history. The model keeps the quote as the stable identity and puts everything that changes into a version.

![Quote data model: identity, versions, lines and the links to order and audit A quote header holds the stable identity and state. Each negotiation step is a quote_version row. Each version owns quote_line rows with the price, any override and discount. A version links to a quote_message thread and quote_attachment rows. quote_event is the audit table, linked to the quote, the shop order is referenced by a foreign key from the quote header, and the ERP sales order from the quote lines.](https://balazscsorba.com/images/blog/b2b-rfq-quote-management/diagram-36.webp?v=66a92837bb)

Quote data model: the quote header is the stable identity, each negotiation step is a version row, lines and overrides belong to a version, and messages, attachments and audit events point at the quote or version.

The overrides live on the version line. Keep the original list price beside the offered price, with the discount type and value, the author and the approval reference, so a reviewer sees the margin without reconstructing it. The quote header carries only the state and pointers to the current and the accepted version. Outward references go to the shop order on the commerce side and to the ERP sales order on the back-office side.

## Where does LLM assistance fit, and where does it not?

**LLM assistance belongs around the quote document, not inside its pricing logic.** Useful jobs are drafting the covering text of an offer from the structured quote, summarizing a long message thread for the next person who picks up the quote, translating a line comment, and flagging lines with missing data such as no unit, no delivery date or a quantity that differs from the request. Each output is a suggestion that a person reviews before the buyer sees it.

The boundary is firm. The assistant never sets a price, never applies a discount and never accepts a quote. Pricing authority stays with the policy from the permissions section, and the audit trail records whether a person approved an assistant draft. Spryker’s AI Commerce documentation lists a Back Office Assistant, an AI chat widget inside the Back Office, which shows assistants entering back-office work next to quoting. The negotiation itself stays a governed document. The model-side design for extracting requests and drafting replies is in [LLM-assisted RFQ automation](https://balazscsorba.com/blog/llm-rfq-quote-automation).

## Build checklist: tables, states and tests

**Build the states, the tables and the tests before the screens.** The checklist below is the minimum a team should agree on before development starts. Each item maps to one of the decisions earlier in the article.

1.  Define the states and the allowed transitions, and reject any transition that is not on the list.
2.  Create quote, quote\_version, quote\_line, quote\_message, quote\_attachment and quote\_event tables; never update a version row after it is accepted.
3.  Write the price-override policy as data per role, with thresholds, and record the approver on each line.
4.  Store validity on the version, and run an expiry job that locks quotes and emits an event.
5.  Make conversion idempotent on the quote id and the accepted version number.
6.  Test the full path: the buyer submits, the seller counters, the buyer accepts, one order is created, and a second accept returns the same order.
7.  Test the negative cases: an expired acceptance is refused, an override beyond the threshold is refused without approval, and a replayed conversion creates nothing new.

```
// Sketch: allowed transitions are data, and anything else is rejected
const allowed: Record<QuoteState, QuoteState[]> = {
  draft: ['submitted', 'cancelled'],
  submitted: ['revised', 'accepted', 'expired', 'cancelled'],
  revised: ['submitted', 'accepted', 'expired', 'cancelled'],
  accepted: ['converted'],
  expired: ['revised'],            // re-quoting creates a new version, never edits the old one
  converted: [],
  cancelled: [],
}

function transition(quote: Quote, to: QuoteState, actor: Actor) {
  if (!allowed[quote.state].includes(to)) throw new Error(`illegal ${quote.state} -> ${to}`)
  if (to === 'accepted' && quote.validUntil < new Date()) throw new Error('quote expired')
  return { ...quote, state: to, events: [...quote.events, { to, actor, at: new Date() }] }
}
```

A Playwright end-to-end test can drive the full path against a shop sandbox, and the negative cases belong in unit tests on the state machine. If you are building or auditing a B2B quote flow, see my [B2B e-commerce work](https://balazscsorba.com/expertise/b2b-ecommerce-developer).

## Sources

1.  [Spryker: request for quote feature](https://docs.spryker.com/docs/pbc/all/request-for-quote/latest/request-for-quote)
2.  [Spryker: quote request import file details](https://docs.spryker.com/docs/pbc/all/request-for-quote/latest/import-and-export-data/file-details-quote-request.csv)
3.  [Spryker: Approval Process feature overview](https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/approval-process-feature-overview)
4.  [Spryker: Purchasing Control feature overview](https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/purchasing-control-feature-overview)
5.  [Adobe Commerce B2B: negotiable quotes](https://experienceleague.adobe.com/en/docs/commerce-admin/b2b/quotes/quotes)
6.  [Adobe Commerce B2B: configure quotes](https://experienceleague.adobe.com/en/docs/commerce-admin/b2b/quotes/configure-quotes)
7.  [SAP Commerce: Commerce Quotes (2205 documentation)](https://help.sap.com/docs/SAP_COMMERCE/9d346683b0084da2938be8a285c0c27a/18f87a040b814781a459fe60fb28c912.html)
8.  [SAP Commerce: quote statuses (2205 documentation)](https://help.sap.com/docs/SAP_COMMERCE/4c33bf189ab9409e84e589295c36d96e/5958905dbb1e4e62b117f5448bb91e63.html)
9.  [Shopware: B2B Components](https://docs.shopware.com/en/shopware-6-en/commercial-features/b2b-components)
10.  [Shopware: quotes management entities and schema](https://developer.shopware.com/docs/products/extensions/b2b-components/quotes-management/concepts/entities-and-schema)
11.  [Odoo 20 documentation: quotation deadline](https://www.odoo.com/documentation/20.0/applications/sales/sales/sales_quotations/deadline.html)
12.  [Pimcore: E-Commerce Framework bundle (GPL version end of life)](https://github.com/pimcore/ecommerce-framework-bundle)

## Frequently asked questions

What is the difference between an RFQ and a quote in B2B commerce?

An RFQ, or request for quote, is the buyer’s request for prices on a cart or a list of items. The quote is the seller’s answer, which is then negotiated. Many platforms use one object for both and name the states differently, so model the whole negotiation as one document with versions.

Should a quote be edited in place or versioned?

Version it. Each change to an agreed field creates a new immutable version with an author and a timestamp. In-place edits make it impossible to prove which price the buyer accepted and which order was based on it.

How should discounts on quote lines be approved?

Use a policy stored as data per company role: a rep may override within a band, a second role approves above it, and anything below a floor is refused. Record the approver on the line. Adobe Commerce documents that additional discounts cannot be added once a negotiated quote moves on to checkout.

What should happen when a quote expires?

Decide it explicitly. Common options are locking the quote, reissuing it as a new version that needs approval, or warning both sides before expiry. Store the validity timestamp on the version and check it at acceptance and again at conversion.

Can an LLM help with quote management?

Yes, around the document: drafting the covering text, summarizing a message thread, or flagging missing line data. A person reviews every output. The assistant never sets prices, applies discounts or accepts a quote.

Written by Balázs Csorba

Senior fullstack & AI engineer in Styria, Austria – 10+ years of Vue, Nuxt, Node.js and PHP, now building tooling for AI agents.

[B2B e-commerce & PIM →](https://balazscsorba.com/expertise/b2b-ecommerce-developer)[About me →](https://balazscsorba.com/about)

## More articles

-   [Schema.org structured data for B2B shops: prices behind logins, units and variants](https://balazscsorba.com/blog/schema-org-structured-data-b2b-shop)
-   [An AI assistant for B2B shop support: tools, permissions and the human handoff](https://balazscsorba.com/blog/ai-assistant-b2b-shop-customer-support)
-   [Streaming AI chat in Vue and Nuxt: accessible by design](https://balazscsorba.com/blog/vue-ai-chat-ui-streaming-accessibility)
-   [An LLM translation pipeline for i18n: glossary, placeholders and review](https://balazscsorba.com/blog/llm-translation-pipeline-i18n)

## Sounds like what you need?

Tell me about your project or role – I’d love to hear from you.

[Get in touch](mailto:contact@balazscsorba.com) [Connect on LinkedIn](https://www.linkedin.com/in/balazs-csorba)
