# Barcode and thermal printing

Storify now has one barcode domain for products, variants, POS scanning,
inventory labels, CSV import/export, and shipping labels. Product labels can be
printed through the browser driver or sent as native ZPL/TSPL. Shipping labels
are generated as exact 4 × 6 inch PDFs and can be downloaded or sent directly
to the saved QZ Tray printer.

## Supported product identifiers

| Format | Typical use | Validation and rendering |
| --- | --- | --- |
| EAN-13 | International retail unit | GS1 Mod-10 check digit, EAN-13 symbol |
| UPC-A | North American retail unit | GS1 Mod-10 check digit, UPC-A symbol |
| GTIN-14 | Cases and higher packaging levels | GS1 Mod-10 check digit, ITF-14 browser symbol |
| Code 128 | Internal SKU, lot, or alphanumeric identifier | Printable ASCII, Code 128 symbol |

The source field is as important as the number:

- `manufacturer` or `gs1` means the identifier came from an external issuer.
- `internal` means Storify generated or assigned it for internal operations.
- Auto-generated product values are restricted-circulation EAN-13 values in
  the `20` range. They scan in Storify but are deliberately marked ineligible
  for marketplace product identity.

Do not invent a UPC/EAN and mark it as GS1 or manufacturer-issued. Marketplace
eligibility in exports is only a preflight signal; the target marketplace is
the final authority and can also require a brand exemption or its own label.

## First deployment

Install dependencies and inspect existing data before creating the unique
barcode registry:

```bash
pnpm install
pnpm db:migrate:barcodes:dry
```

The dry run reports duplicate product or variant barcodes and does not write.
Resolve every duplicate, then run:

```bash
pnpm db:migrate:barcodes
```

The registry owns normalized barcode uniqueness across both products and
variants. New create/update/import operations reserve the value before saving,
so the same barcode cannot be assigned twice during normal concurrent use.

## Product and inventory workflow

1. Open a product or variant and enter the barcode, format, and source. `Auto`
   detects EAN-13, UPC-A, GTIN-14, or Code 128.
2. For products without an external GTIN, use Generate. This creates an
   internal-only, check-digit-valid EAN-13 value.
3. Open Admin, Staff, or Vendor **Inventory**. Select one or more rows and open
   **Barcode labels**.
4. Select 40 × 25, 50 × 30, or 60 × 40 mm; 203 or 300 DPI; and ZPL or TSPL.
5. Choose manual copies or current on-hand stock quantity. Price and SKU can be
   shown or hidden.
6. Use **Browser / driver print**, **Download raw commands**, or discover a QZ
   printer and use **Direct thermal print**.

Browser output uses exact millimetre `@page` sizes. Disable browser-added
headers/footers and print at 100% / Actual size. Calibrate the printer's media
and gap sensor before production runs.

## Direct QZ Tray setup

Install QZ Tray on each POS/warehouse terminal and install the printer's normal
driver. Configure a signed QZ certificate so the app can send jobs securely:

```dotenv
QZ_TRAY_CERTIFICATE="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
QZ_TRAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
```

Restart the Next.js server after changing these values. The private key is
read only by the server-side signing route and must never be exposed through a
`NEXT_PUBLIC_` variable or committed to source control. The certificate/sign
routes require an authenticated admin, staff, or vendor-capable session.

Discover and save the printer once in Inventory's barcode label studio. The
profile is stored per browser terminal. Product labels use native ZPL/TSPL;
order pages reuse the saved printer for direct 4 × 6 PDF printing.

If QZ is not configured, use browser/driver printing. A QZ production
certificate may require the licensing arrangement selected for the deployment.

## POS receipts

POS receipts now render the order barcode as Code 128 and the QR code locally,
so receipt creation does not send order data to an external QR image service.
When printed receipts are enabled in POS settings, a completed sale opens the
receipt print flow automatically. The configured receipt printer information is
included in the POS settings payload.

## Shipping workflow

Mark an order or vendor sub-order as shipped and provide its carrier and
tracking number. Storify snapshots the origin/destination address and creates a
shipment record. The order page can then:

- download an exact 4 × 6 inch PDF;
- print that PDF through the browser/driver; or
- use **Print 4 × 6** to send it to the saved QZ printer.

The built-in PDF is clearly marked **INTERNAL SHIPPING LABEL — NOT CARRIER
POSTAGE**. It is suitable for warehouse routing, packing, handoff, and a manual
carrier workflow. A postage-paid label, carrier routing barcode, customs form,
or marketplace-specific fulfillment label must come from that carrier or
marketplace API. When such an integration is added, store its external label
URL/ID and change the shipment label source from `internal` to `carrier`.

## Verification

Run these checks before release:

```bash
pnpm typecheck
pnpm test
pnpm lint
pnpm build
```

Also scan one printed sample of every label preset using the same scanner model
used at the POS, and run at least one full 4 × 6 shipping-label test on each
printer/DPI combination deployed in the warehouse.
