> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alforse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 打印与标签

> 将 ZPL 和 PDF 标签发送到实体打印机：设置打印机路由和按位置的分配，控制收货、发货、拆分和工单开工时的自动打印，并通过打印作业队列跟踪每张标签。

打印功能把实体标签贴到零件、批次、货架和看板料箱上。管理员连接打印机并决定每个工位使用哪一台；仓库和车间人员在过账收货、拆分批次或完成工单的第一道工序时，就会自动获得标签。

## 概览

打印系统由三部分组成：

| 部分 | 作用 |
| - | - |
| **打印机路由** | 每台实体打印机一条记录，说明送往何处、使用什么格式。 |
| **分配** | 每个位置和上下文使用哪台打印机，以及是否自动打印。 |
| **打印作业队列** | 每一次打印（无论自动还是手动），从渲染到送达全程跟踪。 |

这与发票、采购订单、报价单和工单流转卡等生成的文档不同，后者以文件形式送达。打印是把输出发送到**实体打印机**，默认输出是热敏 ZPL 标签。

### 格式

作业被路由到的打印机决定其渲染方式：

* **ZPL** 是热敏标签打印机的命令语言，也是零件标签和存储单元标签的默认格式。只有当介质尺寸具有 ZPL 尺寸时，ZPL 标签才能渲染。
* **PDF** 是完整文档，由生成发票和工单流转卡的同一套 React-PDF 引擎绘制。看板卡片**仅支持 PDF**，因此除非有模板覆盖，将其发送到仅支持 ZPL 的打印机会失败。

### 单据类型

定义了三种标签类型（`packages/web/printing/src/registry.ts` 中的 `documentTypeRegistry`）：

FieldType

<details>
  <summary>**productLabel**`默认 ZPL`</summary>

  用于追踪实体（序列号或批次号）的标签。可从 `Receipt`、`Shipment`、`Operation`、`Entity`、`Job`、`Split` 或 `StockTransfer` 打印。
</details>

<details>
  <summary>**storageUnitLabel**`默认 ZPL`</summary>

  用于货架、料箱或其他存储位置的标签。从 `StorageUnit` 打印。
</details>

<details>
  <summary>**kanbanCard**`仅 PDF`</summary>

  用于看板料箱的补货卡片。从 `Kanban` 打印。
</details>

来源单据决定可以打印哪些类型：过账发货会生成产品标签，扫描存储单元会生成存储单元标签。查找由 `getDocumentTypesForSource` 完成。

## 设置打印

### 1. 添加打印机路由

**打印机路由**代表一台实体打印机。前往**设置 → 打印 → 打印机**，选择**添加打印机**。

FieldType

<details>
  <summary>**name**required`文本`</summary>

  您为该打印机起的名称，例如 "Zebra 2x1"。在其所在位置内必须唯一。
</details>

<details>
  <summary>**format**required`"zpl" | "pdf"`</summary>

  "ZPL (Thermal Label)" 或 "PDF (Document)"。
</details>

<details>
  <summary>**mediaSizeId**required`文本`</summary>

  打印机中装载的标签尺寸。ZPL 打印机只提供热敏（支持 ZPL）尺寸；PDF 打印机提供所有尺寸。
</details>

<details>
  <summary>**printerUrl**required`url`</summary>

  通过 POST 接收渲染后标签的 HTTP 端点。必须是有效的 URL。
</details>

<details>
  <summary>**apiKey**`文本`</summary>

  可选。随每次投递以 `X-API-Key` 标头发送，并加密存储。
</details>

<details>
  <summary>**templateId**`文本`</summary>

  可选。"Leave blank for built-in"（留空使用内置模板）：留空时使用 Alforse 内置渲染器。
</details>

<details>
  <summary>**locationId**`文本`</summary>

  可选。打印机所在位置；不填则该路由适用于整个公司。
</details>

NOTE

Alforse 从不打开打印机的原始端口。它通过向 `printerUrl` 发送包含渲染字节的 HTTP POST 来投递。因此，车间打印机需要一个 Alforse 可以访问的 HTTP 端点，例如打印网关或打印机自带的 HTTP 接口。目标地址还必须是该公司已批准的打印机来源；Alforse 拒绝向其他任何地方发送。

### 2. 测试打印机

每条路由都有一个**测试**操作，它会把一张小的 ZPL 标签直接发送到其 `printerUrl`，让您在依赖打印机之前先确认它可达。测试只适用于设置了介质尺寸的 ZPL 路由，成功时会显示 `"Test label sent to printer"`（测试标签已发送到打印机）。

### 3. 将打印机分配给上下文

路由是一台打印机；**分配**决定何时使用它。打开**设置 → 打印 → 分配**，每个位置都有各自的区块。在其中，为每个**上下文**指定一台打印机（`packages/web/printing/src/assignments.ts` 中的 `printerContexts`）：

FieldType

<details>
  <summary>**default**</summary>

  该位置的后备。没有专属打印机的上下文都使用它。
</details>

<details>
  <summary>**shipping**</summary>

  用于类型为 `Shipment` 的来源单据。
</details>

<details>
  <summary>**receiving**</summary>

  用于类型为 `Receipt` 的来源单据。
</details>

<details>
  <summary>**inventory**</summary>

  用于 `StockTransfer` 或 `StorageUnit`。
</details>

<details>
  <summary>**workCenter**</summary>

  按工作中心设置，用于从工单工序打印的标签。没有分配的工作中心直接使用该位置的默认打印机。
</details>

每个上下文行还有一个**自动打印**开关；开启后，匹配的业务事件无需任何人点击就会打印。`getPrinterContextForSource` 查找来源单据对应的上下文，`resolveContextAssignment` 应用后备规则，因此未分配的上下文会使用该位置的默认打印机。

HEADS UP

没有显式行的上下文，其自动打印为**开启**，即使该位置默认行已将其关闭。若要让某个工位（例如发运）保持安静，请为它单独设一行并关闭自动打印，而不要依赖默认行。

## 自动打印

在受支持的业务事件之后，Alforse 会查找该位置和上下文对应的打印机，检查 `autoPrint`（未设置时视为**开启**）并将作业加入队列。打印失败绝不会阻止触发它的收货、发货或工单。

| 事件 | 上下文 | 打印内容 |
| - | - | - |
| 收货已过账 | receiving | 所收追踪实体的产品标签。 |
| 发货已过账 | shipping | 产品标签，以及批次拆分的 `Split` 标签。 |
| 库存转移行被拆分 | inventory | 一张 `Split` 标签，以及原批次的 `Entity` 重新打印。 |
| 工单的第一道工序在 MES 中完成 | work center | 该工单的产品标签。 |

若要让某个工位静音，请在该位置对应上下文行上关闭**自动打印**。

HEADS UP

同一来源单据的自动打印会在 **30 秒内去重**：第二个事件会被丢弃，并返回一个不可重试的错误。手动打印和重新打印从不去重。

## 手动打印

在相应界面上选择**打印**，或将来源单据发送到手动打印路由（`/x/print`），它会验证请求并将同类作业加入队列。

**打印**的行为取决于该位置：

| 位置有打印机路由 | 行为 |
| - | - |
| 有 | 打开打印机选择器，并选中解析出的默认打印机。确认后将作业加入队列。 |
| 无 | 打开**下载**对话框，以便您将标签保存为 ZPL 或 PDF。 |

## 打印作业队列

### 作业状态

<details>
  <summary>**generating**</summary>

  作业已创建，其内容正在渲染，形式为 ZPL 文本或 base64 PDF。
</details>

<details>
  <summary>**queued**</summary>

  已渲染。向打印机的投递已排队。
</details>

<details>
  <summary>**printing**</summary>

  已发送到打印机端点；正在投递。
</details>

<details open>
  <summary>**completed**</summary>

  已送达，或已渲染但没有可发送的打印机（仅下载的作业）。
</details>

<details>
  <summary>**failed**</summary>

  渲染或投递失败，或无法确认投递。`error` 给出原因，`attempts` 统计尝试次数。
</details>

每个作业都会记录其 `origin`：

| 来源 | 含义 |
| - | - |
| **auto** | 业务事件之后自动打印。 |
| **manual** | 有人选择了打印。 |
| **reprint** | 从作业列表重新运行。 |

作业还会存储 `sourceDocument`、`sourceDocumentId`、可读 ID、`contentType`，以及它送往的 `printerUrl`。

### 监控与重新打印

打开**设置 → 打印 → 打印作业**（Print jobs）。表格会实时更新，显示状态、描述、来源、类型、来源方式和时间。展开一行可查看打印机 URL、尝试次数、时间戳和任何错误。在一行上您可以：

* **查看**内容：ZPL 通过 Labelary 预览，PDF 在内嵌框架中预览；
* **重新打印**，这会创建一个 `origin: "reprint"` 的新作业；
* **删除**。

NOTE

如果无法确认某次投递的结果（由于超时，或结果无法记录），作业会被标记为失败，且**绝不会自动重发**：标签可能已经打印。请检查打印机，必要时**重新打印**。

## 标签尺寸

介质尺寸来自固定目录（`packages/web/utils/src/labels.ts`）：`label2x1`、`label4x2`、公制的 `label100x50mm` 和 `label50x25mm`，以及仅限 PDF 的 `avery5163`。只有带 ZPL 尺寸的规格才能在热敏打印机上打印，因此 Avery 这类仅限 PDF 的尺寸不能。

尺寸在两个地方设置：

* 每条打印机路由固定自己的 `mediaSizeId`，与该打印机中装载的标签相符；
* 公司的**默认产品标签尺寸**（`companySettings.productLabelSize`）在文档模板界面设置，当没有路由设置尺寸时，交互式标签下载路由使用它。

该默认值的位置见[公司设置](https://docs.alforse.com/zh/docs/reference/company-settings.md)；为内置标签模板设定样式的自定义工具见[文档](https://docs.alforse.com/zh/docs/reference/documents.md)。

## 故障排除

### 标签没有打印

请按顺序检查：

1. 该位置和上下文已分配打印机路由，且其**自动打印**开关已开启。
2. 路由有可访问的 `printerUrl`。没有它，作业会渲染但无法投递。
3. 该作业在**设置 → 打印 → 打印作业**中不是**失败**状态。展开该行查看 `error`。

将自动打印加入队列时发生的失败会记录为 "Auto-print failed"（自动打印失败），绝不会显示在收货或发货界面上，所以应当查看打印作业列表。如果根本没有作业，则要么没有解析出打印机且自动打印已关闭，要么该事件不会自动打印。只有[自动打印](#automatic-printing)中的四种事件才会；其他情况请使用**打印**或手动路由。

### 错误消息

| 消息 | 原因 | 解决办法 |
| - | - | - |
| "Print jobs already exist for … …"（打印作业已存在……） | 30 秒内已为同一来源单据创建过自动作业，因此该重复项被丢弃。 | 需要再来一份时使用**重新打印**；重新打印不会去重。 |
| "Rendering failed: …"（渲染失败：……） | 无法生成内容，通常是因为 ZPL 标签被路由到没有 ZPL 尺寸的介质规格，或仅限 PDF 的看板卡片被发送到仅支持 ZPL 的打印机。 | 检查路由的介质尺寸和格式。 |
| "Printer delivery could not be confirmed. This job will not be sent again automatically."（无法确认打印机投递。此作业不会自动重发。） | 端点未及时响应，或结果无法记录。 | 检查打印机；如果没有打印出来，从作业列表重新打印。 |
| "Print job not found: …" / "The print job has no valid stored content."（找不到打印作业：…… / 打印作业没有有效的已存储内容。） | 投递无法加载作业或其内容；后者意味着渲染从未完成。 | 在作业列表中查看作业的状态和错误。 |
| "No printer is assigned to this print job; nothing was sent."（此打印作业未分配打印机；未发送任何内容。） | 对没有打印机的作业请求了重新打印。 | 为该位置和上下文分配打印机，然后重新打印。 |
| "Print failed (…)"（打印失败（……）） | 端点返回了 HTTP 错误，其代码在消息中。通常是打印机离线、`printerUrl` 错误，或 `apiKey` 缺失或无效。 | 使用路由的**测试**操作检查可达性。 |
| "Test print is only supported for ZPL label printers with a media size configured."（仅配置了介质尺寸的 ZPL 标签打印机支持测试打印。） | 对 PDF 路由或没有介质尺寸的 ZPL 路由使用了**测试**。 | 只测试设置了介质尺寸的 ZPL 路由。 |
| "Cannot reprint a job with no content" / "Cannot reprint a job that is still generating"（无法重新打印没有内容的作业 / 无法重新打印仍在生成的作业） | 重新打印复制的是原作业已渲染的内容，而该作业从未渲染。 | 先处理原作业，或从来源单据重新打印。 |

## 相关内容

**公司设置**

默认产品标签尺寸及其他公司级打印默认值。

[Open](https://docs.alforse.com/zh/docs/reference/company-settings.md)

**库存与位置**

存储单元标签，以及大多数打印作业所描述的追踪实体。

[Open](https://docs.alforse.com/zh/docs/reference/inventory.md)

**可追溯性**

产品标签上携带的序列号和批次号。

[Open](https://docs.alforse.com/zh/docs/reference/traceability.md)

**看板**

补货料箱及为其打印的 PDF 卡片。

[Open](https://docs.alforse.com/zh/docs/reference/kanban.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.