Printing and settings
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.
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.
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.
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.
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.
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