---
title: Printing and settings
description: Register branch printers, design receipt templates, audit print jobs, and configure the org settings singleton.
---

You manage printing from `apps/server/src/features/printing/route.ts`, with handlers under `apps/server/src/features/printing/printers/`, `apps/server/src/features/printing/templates/`, and `apps/server/src/features/printing/jobs/`, and organisation settings from `apps/server/src/features/settings/route.ts` with tax defaults in `apps/server/src/features/settings/tax.ts`. All printing routes need authentication and enforce branch isolation: non-admin users can only touch their own branch, while admins may pass any `branch_id`.

### Register a printer

List printers with `GET /printing/printers?branch_id=BRANCH_ID`, ordered with the default first. Only admins can create or update printers. On `POST /printing/printers`, you send `name` (required), `kind` (one of `receipt_80mm`, `receipt_58mm`, `a4_invoice`, `label`, `report`), `copies` (integer 1 to 20), and flags `is_default`, `auto_print`, and `kick_drawer`.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"branch_id":"BRANCH_ID","name":"Counter 80mm","kind":"receipt_80mm","copies":1,"is_default":true,"auto_print":true}' \
  http://localhost:8000/api/v1/printing/printers
```

The first printer in a branch automatically becomes the default. Update with `PUT /printing/printers/:printerId`. A branch must always keep one default, so demoting the last default returns a 400. The `connection` field accepts only `browser`, and sending any `device_path` is rejected because browser printing uses no device path.

### Design receipt templates

List templates with `GET /printing/receipt-templates?branch_id=BRANCH_ID`. Admins create them with `POST /printing/receipt-templates`, setting `name`, `paper` (default `mm80`), `header_text`, `footer_text`, and display flags `show_logo`, `show_vat`, `show_batch`, and `is_default`.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"branch_id":"BRANCH_ID","name":"Standard 80mm","paper":"mm80","header_text":"PharmaFlow Kampala","show_vat":true}' \
  http://localhost:8000/api/v1/printing/receipt-templates
```

Keep header text short so it fits the paper width, and toggle `show_batch` on only when your dispensers need batch numbers on the receipt.

### Audit print jobs

Log every print with `POST /printing/print-jobs`, passing `reference_id` (the sale or document id), `document_type` (one of `receipt`, `invoice_a4`, `credit_note`, `label`, `report`), `copies`, and `status` (one of `queued`, `completed`, `failed`, `reprint`). The read API maps the legacy stored `printed` value to `completed`.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"branch_id":"BRANCH_ID","reference_id":"SALE_ID","document_type":"receipt","copies":1,"status":"completed"}' \
  http://localhost:8000/api/v1/printing/print-jobs
```

Review history with `GET /printing/print-jobs`, paginated at 20 per page and filterable by `reference_id` to see every attempt and reprint for one sale. When a print fails, log a second job with status `reprint` against the same `reference_id` instead of editing the failed row, so the audit trail stays complete.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/api/v1/printing/print-jobs?branch_id=BRANCH_ID&reference_id=SALE_ID"
```

### Configure org settings

Read the singleton with `GET /settings`. When no row exists you receive the defaults in `apps/server/src/features/settings/queries.ts`: name `PharmaFlow`, `default_low_stock_threshold` 10, `expiry_warning_days` `"30,60,90"`, `tax_enabled` false, `tax_rate` 0.18, `auto_print_receipt` true, `default_copies` 1, `base_currency` `UGX`, `rate_staleness_days` 7, `server_mode` hosted, an empty `server_url`, plus receipt and display flags.

Only admins can update with `PUT /settings`, and every field is optional. You can set identity (`name`, `legal_name`, `phone`, `email`, `address`, `tin`, `vat_number`, `nda_license_no`, `premise_no`), receipt text (`receipt_header`, `receipt_footer`, `return_policy`), behaviour flags (`show_vat_breakdown`, `show_pharmacist_on_receipt`, `auto_print_receipt`), currency and rates (`base_currency` as a three-letter code that must be active in the currency registry, `rate_staleness_days` between 1 and 90), connectivity (`server_mode` hosted or custom, `server_url` as an http(s) address with no path), and thresholds (`default_low_stock_threshold`, `expiry_warning_days` as comma-separated days, `default_copies` 1 to 20). Tax follows `tax.ts`: `tax_enabled` toggles VAT and `tax_rate` is a fraction from 0 to 1, so 0.18 means 18 percent. Invalid values return a 400 naming the offending field, and every successful update writes an audit log entry that records the change to `org_settings`.

```bash
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"tax_enabled":true,"tax_rate":0.18,"receipt_header":"Thank you for visiting","default_low_stock_threshold":15}' \
  http://localhost:8000/api/v1/settings
```

### Next steps

**[Reports and dashboard](/operations/reports-and-dashboard)**

Verify your tax and threshold settings against the reports they feed.

**[Compliance](/operations/compliance)**

Keep licence and premise numbers on receipts inspection-ready.
