# Print your renderings

# Creating Report Print Tasks

<div style="text-align: justify;">

A `ReportPrintTask` represents a single print job — a PDF queued for delivery to a physical printer. VeloxFactory does not communicate with printers directly. Instead, it creates the task record, optionally notifies a separate print service via WebSocket, and waits for the service to report back. This page covers how tasks are created, how the status lifecycle works, and how to handle retries.

[![report-print-task.index.png](https://docs.veloxfactory.kiwi-software.dev/uploads/images/gallery/2026-05/scaled-1680-/report-print-task-index.png)](https://docs.veloxfactory.kiwi-software.dev/uploads/images/gallery/2026-05/report-print-task-index.png)

---

<h3 id="three-ways" style="color: #203671; margin-top: 2.2em;">Three Ways to Create a Print Task</h3>

<h4 style="color: #203671; margin-top: 1.4em;">1. As Part of a Render Request</h4>

The most common path: set `createPrintTask: true` in the render request body, provide a `printerName`, and VeloxFactory renders the report and dispatches it to the printer in a single call. No second request needed.

```json
POST /api/v1/report-config/A5_KanBan/render

{
  "outputType": "base64",
  "parameters": { "P_ARTICLE_NUMBER": "4561287-154" },
  "data": [ { ... } ],
  "createHistoryRecord": true,
  "createPrintTask": true,
  "printerName": "WarehousePrinter01",
  "numberOfCopies": 1,
  "broadcastId": "Standard"
}
```

<h4 style="color: #203671; margin-top: 1.4em;">2. From a History Record</h4>

A task can be dispatched from any existing `ReportHistoryRecord` — without re-rendering the report. VeloxFactory uses the PDF stored in the history record and creates a new print task from it:

```
POST /api/v1/report-history-record/{id}/print
```

```json
{
  "printerName": "WarehousePrinter01",
  "numberOfCopies": 2
}
```

This is the standard reprint path. See [The concept of Report History Records](#) for details.

<h4 style="color: #203671; margin-top: 1.4em;">3. Standalone via the Print Task API</h4>

Print tasks can also be created directly — independently of any render or history record. The `POST /api/v1/report-print-task` endpoint accepts any PDF as a Base64 string, making it possible to use the VeloxFactory print infrastructure for documents that were not produced by VeloxFactory at all.

```json
POST /api/v1/report-print-task

{
  "fileName":       "delivery_note_5521.pdf",
  "fileBase64":     "JVBERi0xLjQ...",
  "printerName":    "WarehousePrinter01",
  "numberOfCopies": 1,
  "broadcastId":    "Standard"
}
```

`reportConfig` and `reportHistoryRecord` are both optional on this endpoint — the task is created without either relation if they are not provided.

---

<h3 id="data-model" style="color: #203671; margin-top: 2.2em;">The Data Model</h3>

<table style="width: 100%; border-collapse: collapse;">
  <thead>
    <tr style="border-top: 1px solid #e6e8ef; border-bottom: 1px solid #e6e8ef;">
      <th style="text-align: left; padding: 6px 10px; white-space: nowrap;">Field</th>
      <th style="text-align: left; padding: 6px 10px;">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>traceId</code></td>
      <td style="padding: 6px 10px;">Unique identifier. Shared with the linked history record when the task was created via a render request. For reprints, a derived trace ID is generated (original + short random suffix).</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportConfig</code></td>
      <td style="padding: 6px 10px;">The <code>ReportConfig</code> the printed PDF was generated from. Optional — not present for standalone tasks.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportHistoryRecord</code></td>
      <td style="padding: 6px 10px;">The linked <code>ReportHistoryRecord</code>. Optional — not present for standalone tasks.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>printerName</code></td>
      <td style="padding: 6px 10px;">The name of the target printer, as the print service expects it.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>numberOfCopies</code></td>
      <td style="padding: 6px 10px;">Number of copies passed to the print service. VeloxFactory always renders once — the print service is responsible for duplication. Defaults to <code>1</code>.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>broadcastId</code></td>
      <td style="padding: 6px 10px;">WebSocket channel ID. If set at creation time, VeloxFactory broadcasts a <code>ReportPrintTaskCreated</code> event via Laravel Reverb. Omit to use polling instead.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>outputFileName</code></td>
      <td style="padding: 6px 10px;">The filename of the PDF queued for printing.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>status</code></td>
      <td style="padding: 6px 10px;">Current state of the task. See below.</td>
    </tr>
    <tr>
      <td style="padding: 6px 10px; white-space: nowrap;"><code>errorMessage</code></td>
      <td style="padding: 6px 10px;">Failure detail reported by the print service. <code>null</code> unless status is <code>error</code>.</td>
    </tr>
  </tbody>
</table>

---

<h3 id="status-lifecycle" style="color: #203671; margin-top: 2.2em;">Status Lifecycle</h3>

Every print task starts as `pending`. The print service picks it up, executes the job, and reports the result back to VeloxFactory via the API:

<table style="width: 100%; border-collapse: collapse;">
  <thead>
    <tr style="border-top: 1px solid #e6e8ef; border-bottom: 1px solid #e6e8ef;">
      <th style="text-align: left; padding: 6px 10px; white-space: nowrap;">Status</th>
      <th style="text-align: left; padding: 6px 10px;">Set by</th>
      <th style="text-align: left; padding: 6px 10px;">Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px;"><span style="color: #203671; font-weight: 600;">pending</span></td>
      <td style="padding: 6px 10px;">VeloxFactory</td>
      <td style="padding: 6px 10px;">Task created, waiting for the print service to pick it up.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px;"><span style="color: #349b31; font-weight: 600;">printed</span></td>
      <td style="padding: 6px 10px;">Print service</td>
      <td style="padding: 6px 10px;">Print job executed and confirmed.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px;"><span style="color: #c0392b; font-weight: 600;">error</span></td>
      <td style="padding: 6px 10px;">Print service</td>
      <td style="padding: 6px 10px;">Print job failed. <code>errorMessage</code> contains the failure detail.</td>
    </tr>
    <tr>
      <td style="padding: 6px 10px;"><span style="color: #525E5A; font-weight: 600;">unknown</span></td>
      <td style="padding: 6px 10px;">—</td>
      <td style="padding: 6px 10px;">Status could not be determined.</td>
    </tr>
  </tbody>
</table>

The print service reports back using the dedicated status endpoint:

```
PATCH /api/v1/report-print-task/{id}/set-status

{
  "status": "error",
  "errorMessage": "Printer offline"
}
```

<h4 style="color: #203671; margin-top: 1.4em;">Resetting to Pending</h4>

A task can be reset to `pending` using the set-printed shortcut endpoint — setting the status flag to `false`:

```
PATCH /api/v1/report-print-task/{id}/set-printed/false
```

This re-queues the task. If the task has a `broadcastId`, VeloxFactory re-broadcasts the `ReportPrintTaskCreated` event immediately — notifying the print service to pick the task up again without polling. This is the standard retry mechanism for failed or stalled print jobs.

---

<h3 id="websocket-vs-polling" style="color: #203671; margin-top: 2.2em;">WebSocket vs. Polling</h3>

How the print service learns about a new task depends on whether a `broadcastId` is set.

**With `broadcastId`** — VeloxFactory broadcasts a `ReportPrintTaskCreated` event via WebSocket (Laravel Reverb) the moment the task is created. The print service subscribes to the channel identified by `broadcastId` and reacts immediately. This is the recommended mode for real-time printing — the task reaches the printer within milliseconds of the render completing.

**Without `broadcastId`** — No broadcast is sent. The print service must poll `GET /api/v1/report-print-task?status=pending` at a regular interval and process any tasks it finds. This works fine for workflows where sub-second delivery is not required.

<div style="border-left: 4px solid #5fc75d; background: #f6fdf6; padding: 10px 16px; margin: 16px 0; border-radius: 0 4px 4px 0;">
ℹ️ <strong>WebSocket delivery requires Laravel Reverb to be running.</strong> If Reverb is down, task creation will fail with an error rather than falling back silently to polling. Use Supervisor to keep the Reverb process alive — the same Supervisor configuration that manages the Laravel queue worker should include a <code>php artisan reverb:start</code> program entry. See <a href="/books/veloxfactory/page/installing-veloxfactory">Installing VeloxFactory</a> for a reference configuration.
</div>

---

<h3 id="retention" style="color: #203671; margin-top: 2.2em;">Retention and Deletion</h3>

Print tasks are automatically purged after a configurable number of days, set via the `PURGE_PRINTTASKS_DAYS` environment variable (default: 30 days). Purging runs as a scheduled background job — no manual action required.

Individual tasks can also be deleted directly via the API at any time:

```
DELETE /api/v1/report-print-task/{id}
```

There are no deletion constraints on print tasks themselves — they can always be removed. However, deleting a print task is a prerequisite for deleting the linked `ReportHistoryRecord`, which in turn must be cleared before a `ReportConfig` can be deleted. Automatic purging handles this chain in the background once retention periods expire.

</div>

# Our own C#-based print service

<div style="text-align: justify;">

VeloxFactory does not talk to printers directly. Instead, a lightweight companion application — the **Background Printing Service** — runs on any Windows machine that has the target printers installed. It receives print tasks from VeloxFactory, renders the PDF to the printer, and reports the result back. The two components communicate exclusively over the VeloxFactory API and WebSocket; there is no shared database or filesystem.

---

<h3 id="how-it-works" style="color: #203671; margin-top: 2.2em;">How it works</h3>

The service starts as a regular Windows console process and works through two sequential phases.

<h4 style="color: #203671; margin-top: 1.4em;">Phase 1 — Initial pull</h4>

On startup the service immediately calls `GET /api/v1/report-print-task?status=pending` and processes all tasks it finds. It repeats this in a loop — waiting two seconds between rounds — until the queue comes back empty *and* no jobs are still running. This ensures that any tasks queued while the service was offline are handled before switching to real-time mode.

<h4 style="color: #203671; margin-top: 1.4em;">Phase 2 — WebSocket listener</h4>

Once the initial queue is drained, the service connects to VeloxFactory's WebSocket endpoint (Laravel Reverb) and subscribes to the private channel `private-report-print-tasks`. From this point on, it reacts to incoming events in real time. If the WebSocket connection drops for any reason, the service waits five seconds and reconnects automatically — no manual restart required.

<div style="border-left: 4px solid #5fc75d; background: #f6fdf6; padding: 10px 16px; margin: 16px 0; border-radius: 0 4px 4px 0;">
ℹ️ <strong>The WebSocket uses the Pusher protocol.</strong> When a connection is established, the service authenticates with VeloxFactory via <code>POST /api/v1/broadcasting/auth</code> and subscribes to the private channel using the configured API token.
</div>

---

<h3 id="processing" style="color: #203671; margin-top: 2.2em;">Processing a print task</h3>

Whether a task arrives via the initial pull or via a WebSocket event, the processing steps are identical:

1. **Fetch** — The service calls `GET /api/v1/report-print-task/{id}` to retrieve the full task record, including the PDF as a Base64 string.
2. **Write temp file** — The PDF is decoded and written to a temporary file in `reportPdfFileTempPath` (e.g. `C:\VeloxFactory\temp\42_delivery_note.pdf`).
3. **Print** — PdfiumViewer opens the PDF and sends it to the printer specified in `printerName`. The print is repeated `numberOfCopies` times.
4. **Report back** — On success, the service calls `PATCH /api/v1/report-print-task/{id}/set-printed`, which sets the status to `printed`. On failure, it calls `PATCH /api/v1/report-print-task/{id}/set-status` with `{"status": "error", "errorMessage": "..."}`.
5. **Cleanup** — The temporary file is deleted regardless of the outcome.

<div style="border-left: 4px solid #203671; background: #f0f3fb; padding: 10px 16px; margin: 16px 0; border-radius: 0 4px 4px 0;">
⚠️ <strong>The WebSocket event only carries the task ID and <code>broadcastId</code> — not the PDF.</strong> The service always fetches the full task from the API as a second step. This means the printer machine needs HTTP access to VeloxFactory, not just WebSocket access.
</div>

---

<h3 id="broadcast-id-filtering" style="color: #203671; margin-top: 2.2em;">Broadcast ID filtering</h3>

`listeningBroadcastIds` is a list of broadcast channel identifiers the service will accept. Any `report-print-task.created` event whose `broadcastId` is not in this list is silently ignored.

This makes it straightforward to run multiple service instances in parallel — for example one per location or printer group — each configured to respond only to its own `broadcastId`. The initial pull is not filtered this way: it always processes all pending tasks returned by the API, regardless of `broadcastId`.

---

<h3 id="configuration" style="color: #203671; margin-top: 2.2em;">Configuration</h3>

All settings live in `App.config` in the `applicationSettings` section. Edit the file in a text editor and restart the service for changes to take effect.

<table style="width: 100%; border-collapse: collapse;">
  <thead>
    <tr style="border-top: 1px solid #e6e8ef; border-bottom: 1px solid #e6e8ef;">
      <th style="text-align: left; padding: 6px 10px; white-space: nowrap;">Setting</th>
      <th style="text-align: left; padding: 6px 10px;">Description</th>
      <th style="text-align: left; padding: 6px 10px;">Example</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>apiToken</code></td>
      <td style="padding: 6px 10px;">Bearer token used for all API requests. Must belong to a user with <code>report-print-task:read</code>, <code>:update</code>, and <code>:delete</code> permissions.</td>
      <td style="padding: 6px 10px;"><code>4|abc123...</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>websocketUrl</code></td>
      <td style="padding: 6px 10px;">WebSocket endpoint of Laravel Reverb.</td>
      <td style="padding: 6px 10px;"><code>ws://10.0.0.10:8080/app/veloxfactory</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>websocketAuthUrl</code></td>
      <td style="padding: 6px 10px;">VeloxFactory broadcasting auth endpoint.</td>
      <td style="padding: 6px 10px;"><code>http://10.0.0.10:8088/api/v1/broadcasting/auth</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportPrintTask_index</code></td>
      <td style="padding: 6px 10px;">URL for the initial pull — must include <code>?status=pending</code>.</td>
      <td style="padding: 6px 10px;"><code>http://10.0.0.10:8088/api/v1/report-print-task?status=pending</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportPrintTask_get</code></td>
      <td style="padding: 6px 10px;">URL template for fetching a single task. <code>{0}</code> is replaced with the task ID.</td>
      <td style="padding: 6px 10px;"><code>http://10.0.0.10:8088/api/v1/report-print-task/{0}</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportPrintTask_setPrinted</code></td>
      <td style="padding: 6px 10px;">URL template for marking a task as printed. <code>{0}</code> is replaced with the task ID.</td>
      <td style="padding: 6px 10px;"><code>http://10.0.0.10:8088/api/v1/report-print-task/{0}/set-printed</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportPrintTask_setError</code></td>
      <td style="padding: 6px 10px;">URL template for reporting a failed task. <code>{0}</code> is replaced with the task ID.</td>
      <td style="padding: 6px 10px;"><code>http://10.0.0.10:8088/api/v1/report-print-task/{0}/set-status</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>listeningBroadcastIds</code></td>
      <td style="padding: 6px 10px;">List of broadcast IDs this instance will accept. Add one <code>&lt;string&gt;</code> entry per ID.</td>
      <td style="padding: 6px 10px;"><code>Standard</code>, <code>Warehouse</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>maxParallelPrintJobs</code></td>
      <td style="padding: 6px 10px;">Maximum number of tasks processed concurrently. Default: <code>10</code>.</td>
      <td style="padding: 6px 10px;"><code>10</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>reportPdfFileTempPath</code></td>
      <td style="padding: 6px 10px;">Directory for temporary PDF files. Created automatically on startup if it does not exist.</td>
      <td style="padding: 6px 10px;"><code>C:\VeloxFactory\temp</code></td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>logFile</code></td>
      <td style="padding: 6px 10px;">Path to the log file. Relative paths are resolved from the executable directory.</td>
      <td style="padding: 6px 10px;"><code>.\Log.log</code></td>
    </tr>
    <tr>
      <td style="padding: 6px 10px; white-space: nowrap;"><code>laconicLogging</code></td>
      <td style="padding: 6px 10px;">If <code>True</code>, only errors are logged. If <code>False</code>, all informational messages are logged as well.</td>
      <td style="padding: 6px 10px;"><code>False</code></td>
    </tr>
  </tbody>
</table>

---

<h3 id="concurrency" style="color: #203671; margin-top: 2.2em;">Concurrency</h3>

The service uses two layers of concurrency control to avoid overloading printers.

A global semaphore limits the total number of tasks being processed at the same time to `maxParallelPrintJobs`. In addition, a per-printer semaphore ensures that only one print job runs on a given printer at a time — jobs targeting different printers can execute in parallel, but two jobs targeting the same printer are always serialised. This prevents the spooler from receiving multiple jobs simultaneously from the service.

---

<h3 id="logging" style="color: #203671; margin-top: 2.2em;">Logging</h3>

The service uses **Serilog** and writes to both the console and a rolling log file. Log files are capped at 100 MB each; up to 10 rotated files are retained before the oldest is deleted.

Set `laconicLogging` to `True` in `App.config` to suppress informational messages and log only errors — useful in production once the service is confirmed working.

---

<h3 id="dependencies" style="color: #203671; margin-top: 2.2em;">Dependencies</h3>

<table style="width: 100%; border-collapse: collapse;">
  <thead>
    <tr style="border-top: 1px solid #e6e8ef; border-bottom: 1px solid #e6e8ef;">
      <th style="text-align: left; padding: 6px 10px; white-space: nowrap;">Package</th>
      <th style="text-align: left; padding: 6px 10px;">Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>PdfiumViewer</code></td>
      <td style="padding: 6px 10px;">PDF rendering and printing. Wraps the native PDFium library (bundled via <code>PdfiumViewer.Native.x86_64.v8-xfa</code>) — no separate PDF reader installation required on the target machine.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>RestSharp</code></td>
      <td style="padding: 6px 10px;">HTTP client for all API calls to VeloxFactory.</td>
    </tr>
    <tr style="border-bottom: 1px solid #e6e8ef;">
      <td style="padding: 6px 10px; white-space: nowrap;"><code>Newtonsoft.Json</code></td>
      <td style="padding: 6px 10px;">JSON serialisation and deserialisation (API responses, WebSocket messages).</td>
    </tr>
    <tr>
      <td style="padding: 6px 10px; white-space: nowrap;"><code>Serilog</code></td>
      <td style="padding: 6px 10px;">Structured logging to console and rolling file.</td>
    </tr>
  </tbody>
</table>

<div style="border-left: 4px solid #5fc75d; background: #f6fdf6; padding: 10px 16px; margin: 16px 0; border-radius: 0 4px 4px 0;">
ℹ️ <strong>The service targets .NET Framework 4.7.2 and runs on Windows only.</strong> The PDFium native binary is bundled with the build output — no additional runtime installation is needed beyond .NET Framework 4.7.2, which ships with Windows 10 and Windows Server 2016 and later.
</div>

</div>