# DfProforma Shopware — Pro forma quotes with client acceptance and auto-conversion

> What DfProforma does Shopware 6.7 knows how to issue invoices, delivery notes, and credit notes — but not pro forma quotes. Yet in nearly all B2B contexts (industrial equipment, business…

- Page: <https://www.datafirefly.com/en/documentation/dfproforma-shopware-pro-forma-quote/>
- Language: en
- Last updated: 2026-07-01
- Other languages: [fr](https://www.datafirefly.com/documentation/dfproforma-devis-shopware/index.md), [es](https://www.datafirefly.com/es/documentation/dfproforma-shopware-presupuesto-proforma/index.md), [de](https://www.datafirefly.com/de/documentation/dfproforma-shopware-proforma-angebot/index.md), [it](https://www.datafirefly.com/it/documentation/dfproforma-shopware-preventivo-proforma/index.md), [pl](https://www.datafirefly.com/pl/documentation/dfproforma-devis-shopware/index.md), [nl](https://www.datafirefly.com/nl/documentation/dfproforma-devis-shopware/index.md), [pt](https://www.datafirefly.com/pt/documentation/dfproforma-devis-shopware/index.md)
- Index: <https://www.datafirefly.com/en/documentation/llms.txt>

## What DfProforma does

Shopware 6.7 knows how to issue invoices, delivery notes, and credit notes — but not pro forma quotes. Yet in nearly all B2B contexts (industrial equipment, business services, public procurement, bid-based sales), the client must receive a formal document they accept _before_ the order becomes firm.

DfProforma fills this gap without workarounds: a real native Shopware document type `df_proforma`, its own `PF{n}` number range, a branded Twig PDF template, a standalone client acceptance workflow with a public URL signed HMAC-SHA256, and automatic conversion to a firm order as soon as the underlying transaction transitions to paid.

**In a nutshell:** the sales rep generates a quote from the order detail in the admin, sends it by email, the client clicks the link, accepts online without a Shopware account, pays, and the quote automatically switches to _Converted_ status. No manual click post-payment.

## Requirements

- **Shopware** 6.7.0 or higher (the plugin is not backward-compatible with 6.6 due to the document system refactor)
- **PHP** 8.2 minimum
- **MySQL** 8.0+ or **MariaDB** 10.6+
- Active Shopware message workers (recommended for automatic expiration handling)
- Configured Shopware Mail Service (working SMTP for transactional sends)

## Installation

### 1. Upload the plugin

From the Shopware administration, go to _Extensions → My extensions → Upload extension_, and select the `DfProforma-1.0.6.zip` file.

### 2. Install and activate

In the extensions list, click _Install_ then _Activate_. Shopware migrations run automatically and create:

- The `df_proforma` SQL table with indexes on `order_id`, `status`, `public_token`
- The `df_proforma` document type in `document_type`
- The `document_df_proforma` number range in configurable `PF{n}` format
- The base transactional email template for generation, sending, and acceptance

### 3. Rebuild the administration

The plugin ships a Vite admin module that extends the order detail (`sw-order-detail-base`). You must rebuild the global administration bundle for the _Pro forma_ tab to appear:

```
bin/build-administration.sh
bin/console cache:clear
```

**Skipping this step** is the leading cause of the "Pro forma tab does not appear on the order detail" issue — remember to rebuild after every plugin update.

## Configuration

### Global settings

From _Extensions → My extensions → DfProforma → Configure_, you access the following settings:

- **Default validity** — Number of days the acceptance link remains valid (30 by default). Each quote can override this value individually.
- **Auto-conversion on payment** — Enabled by default. Disable it if you want to keep manual control of the _Accepted → Converted_ transition.
- **Sender name** — Name shown as sender of transactional emails (default: the sales-channel name).
- **PDF brand accent** — Accent color used in the shipped PDF template (header band, separator line, status badge).

### Per-sales-channel configuration

The settings above can be overridden per sales channel in _Settings → Sales channels → [your channel] → Plugin configuration_. Useful if you run multiple stores with different validity policies (for example 30 days for B2C, 60 days for B2B).

## Generate a pro forma quote

### From the order detail (admin)

1. Open an order in _Orders → Overview_
2. Click the _Pro forma_ tab (next to _Documents_)
3. Click _Generate pro forma quote_
4. The PDF is created, the quote appears in the list with a `PF-…` number and status _Draft_

### From the admin API

Three endpoints are exposed to integrate generation into your external workflows:

```
POST /api/_action/df-proforma/generate
Body: { "orderId": "…" }

POST /api/_action/df-proforma/mark-sent
Body: { "proformaId": "…" }

GET  /api/_action/df-proforma/by-order/{orderId}
```

Standard Shopware OAuth2 admin authentication. Useful to hook DfProforma into an external CRM or an automation pipeline.

## Send a quote to the client

### Transactional email

From the quote list (Pro forma tab on the order detail), click the envelope icon next to the quote. The module:

1. Sets the quote to _Sent_ status (with timestamp)
2. Sends an email to the client via the Shopware Mail Service, using the `df_proforma_sent` template, in the sales-channel language
3. Attaches the quote PDF and includes the public acceptance URL
4. Emits the `ProformaGeneratedEvent` event (Flow Builder trigger)

### Public acceptance URL

Every sent quote carries a URL of the form:

```
https://your-shop.com/proforma/accept/{token}
```

The token is encrypted and signed HMAC-SHA256 with the Shopware secret key (`APP_SECRET` / `kernel.secret`). Impossible to forge or guess. The URL expires after the quote validity (30 days by default).

## Public client acceptance page

The client opens the URL — **no Shopware account required** (the page bypasses standard customer account authentication). They see:

- A clean order summary: lines, prices, VAT, totals, terms
- A primary _Accept this quote_ button
- A secondary _Decline with reason_ button
- The displayed validity ("Valid until 20/06/2026")

On acceptance:

- Signature timestamped to the millisecond and persisted in the database
- Client IP address persisted as proof
- Status changes to _Accepted_ with the transition history marked
- `ProformaAcceptedEvent` event dispatched to Flow Builder

On rejection, the _Reason_ field is mandatory — useful for your sales reps who can reach out with a counter-proposal.

**Customization:** the acceptance page uses standard Storefront Twig blocks and inherits your theme. You can override `@DfProforma/storefront/page/account/proforma/quote.html.twig` from your theme.

## Status workflow

Six statuses cover the entire lifecycle of a quote:

- **Draft** — Quote created but not yet sent to the client
- **Sent** — Email sent, awaiting client response
- **Accepted** — Client clicked _Accept_ on the public page
- **Declined** — Client clicked _Decline_ with a reason
- **Expired** — Validity exceeded without response (automatic transition via scheduled task)
- **Converted** — Underlying order paid, automatic conversion

Each transition is persisted with second-level timestamp, actor identifier, trigger type (_sales rep_, _client_, _system_, _payment_) and JSON payload for free metadata. You can reconstruct the exact history of any quote at any time.

## Auto-conversion on payment

The module registers a Subscriber on the `order_transaction.state.paid` event from Shopware's state machine. When a transaction transitions to _paid_ (Stripe, bank transfer, PayPal, etc.), the Subscriber:

1. Finds all pro forma quotes in _Accepted_ status attached to the order
2. Switches them to _Converted_ status
3. Marks the transition history with trigger type _payment_

No human intervention, no cron, no delay. Your sales reports remain consistent effortlessly.

To disable auto-conversion (if your team prefers manual control), uncheck the option in _Plugin configuration → Auto-conversion on payment_.

## Flow Builder — Business Events

Two standard Shopware Business Events are emitted by the module:

- `ProformaGeneratedEvent` — On quote generation (implements `BusinessEventInterface`)
- `ProformaAcceptedEvent` — On client acceptance (implements `BusinessEventInterface`)

Both appear automatically in the native Shopware Flow Builder trigger list. You can hook any Flow action onto them:

- Slack notification to the sales team on acceptance
- Internal summary email to the responsible sales rep
- Webhook to your CRM (HubSpot, Salesforce, Pipedrive…)
- Custom field update on the customer (for example, `quote-accepted` tag)
- Mobile push notification via a third-party service

No intervention in the module's code required — everything is configured from _Settings → Shop → Flow Builder_.

## PDF template customization

The quote PDF is rendered via `DocumentFileRendererRegistry` (Shopware 6.7's new file rendering system), from the Twig template `@DfProforma/documents/proforma.html.twig` shipped with the module.

To customize it, create a custom plugin or override it from your theme following Shopware's standard Twig template hierarchy:

```
custom/plugins/YourTheme/src/Resources/views/documents/proforma.html.twig
```

The shipped template exposes these identified Twig blocks:

- Header with logo and company details
- Customer block
- Order line items summary
- HT/TTC totals with VAT breakdown
- Summary strip at the bottom (PF number, issue and expiry dates)
- _PRO FORMA_ watermark
- Brand accent configurable via `config.accentColor`

Variables available in the template: `order` (OrderEntity loaded with all its associations), `config` (document config including `documentNumber`, `documentDate`, `validUntil`, `validityDays`), `context`.

## Multilingual

FR, EN, DE, and ES are shipped by default, storefront and admin snippets. To add more languages, create a snippet file per locale in `src/Resources/snippet/` following Shopware's standard convention.

The transactional email templates are also multilingual: the correct template is selected automatically based on the customer's sales-channel language at send time. You can customize templates per language from _Settings → Shop → Email templates_.

## API — available admin endpoints

```
POST   /api/_action/df-proforma/generate         # Generate a quote for an order
POST   /api/_action/df-proforma/mark-sent        # Mark as sent (manual transition)
GET    /api/_action/df-proforma/by-order/{id}    # List an order's quotes
```

`df_proforma` entities are also accessible via the standard Shopware DAL API (`/api/df-proforma`) for search, export, or advanced integrations.

## Uninstall

By default, business data is preserved on uninstall (_Keep User Data_ option enabled). You thus preserve the audit history of issued quotes, useful for compliance and commercial traceability.

To force complete removal (`df_proforma` table, document type, number range, email template), disable the _Keep User Data_ option in the uninstall prompt.

**Recommendation:** keep data by default. Only force removal if you're certain you'll never need to access quote history for commercial, accounting, or legal reasons.

## Troubleshooting

### The Pro forma tab doesn't appear on the order detail

The administration bundle was not rebuilt after installation. Run `bin/build-administration.sh` then `bin/console cache:clear`.

### Error "Unable to find a document generator with type df_proforma"

The renderer's service tag is not correct — this bug was fixed since 1.0.2. Make sure you're using a plugin version ≥ 1.0.2.

### Error "Call to undefined method Context::getSalesChannelId()"

Historical cause: old signature of the `RenderedDocument` constructor. Fixed in 1.0.4. Update to 1.0.6.

### Twig error "Cannot rewind a generator that was already run"

Fixed in 1.0.6 — the template was adapted to not consume a `|filter()`-generated generator twice.

### The client receives the email but the acceptance URL returns a 404

Verify that the Storefront sales-channel is properly registered as a domain of the sales channel used for the order. The public route `/proforma/accept/{token}` is registered on the Storefront — it does not work if you access the URL via the admin domain.

### Automatic expiration doesn't work

Verify that Shopware _message workers_ are running in the background (`bin/console messenger:consume` or via a supervisor like systemd/supervisord). Expiration goes through the standard Shopware message queue.

## Support and updates

12 months of updates included (Shopware compatibility, bug fixes, minor feature additions). Email support in French and English within 24 business hours. PHP source code delivered as plain text, PSR-4 compliant, auditable and modifiable.

For any question or report: [contact@datafirefly.com](https://www.datafirefly.com/contact/).
