Skip to main content

Clinic Stamp — Signature Center extension

Date: 2026-06-03 Status: Approved (design)

Problem

Receipts (and other documents) can show an authorized signature, but there’s no way to add a clinic stamp/seal. Clinics need both on receipts. The Signature Center should let a clinic configure a signature and a stamp, both enabled at once.

Decision

Reuse the existing signature infrastructure. Add a clinic-wide stamp as a second kind of “signature” row, configured in the Signature Center, rendered beside the signature on receipts only (for now).

Goals

  • A clinic can upload/draw one clinic stamp (clinic-wide, not per-role).
  • Signature and stamp are independent — either, both, or neither.
  • The stamp renders on receipts next to the signature.
  • Reuse the existing upload → R2 → activate → download machinery; no parallel system.

Non-goals

  • Stamp on documents other than receipts (easy follow-up: pass stampDataUrl to other templates).
  • Per-role stamps (explicitly one clinic stamp).
  • Changing how signatures work today.

Design

Data model

server/src/schema/user_signatures.ts gains:
kind: text('kind').notNull().default('signature'),   // 'signature' | 'stamp'
  • Migration 0058: ALTER TABLE app.user_signatures ADD COLUMN kind text NOT NULL DEFAULT 'signature'. Additive + defaulted, so every existing row is a signature — zero behaviour change.
  • A stamp row: kind='stamp', role='clinic' (fixed sentinel so it’s clinic-wide), one is_active=true per clinic among kind='stamp' rows.

API — server/src/routes/signatures.ts

All handlers accept an optional kind (query for GET, body for POST), defaulting to 'signature' for back-compat:
  • GET /signatures?role=&kind= — list by kind.
  • GET /signatures/active?role=&kind= — active row. For kind='stamp', role is forced to 'clinic' server-side (clinic-wide), so callers just pass kind=stamp.
  • POST /signatures (kind in body; stamp sets role='clinic') — on upload, deactivate siblings keyed on (clinic_id, role, kind) so signature and stamp are independent.
  • PUT /signatures/:id/activate — deactivate siblings keyed on (clinic_id, role, kind).
  • DELETE, GET /:id/download — unchanged (operate by id).

Signature Center UI — ui/src/components/settings/SignaturesSettings.tsx

Add a “Clinic Stamp” section below the existing per-role signature tabs:
  • Same draw/upload control already used for signatures (extract/reuse the editor).
  • Operates on kind='stamp', role='clinic'. One active clinic stamp; shows the current stamp with an “Active” state + replace/delete.
  • Independent of the signature tabs — both can be set.

Rendering — receipts only

  • ui/src/lib/pdf/types.ts PdfAssets: add stampDataUrl?: string.
  • ui/src/lib/pdf-generator.ts:
    • Add a helper to fetch the active clinic stamp (kind='stamp', role 'clinic') → data URL (reuse getActiveSignature/getSignatureImageDataUrl with a kind param).
    • generateReceiptPDF includes stampDataUrl in its assets. Other generators do NOT (keeps stamp receipts-only).
  • ui/src/lib/pdf/templates/common.tsx PdfSignatureBlock: accept optional stampDataUrl; when present, render the stamp beside the signature (signature left, stamp right, ~110–120px square, objectFit: contain, a small “Stamp”/none label). Render gracefully when only one of the two exists. return null only when BOTH absent.
  • ui/src/lib/pdf/templates/ReceiptPdf.tsx: pass stampDataUrl={assets?.stampDataUrl} to PdfSignatureBlock. No other template changes.

Back-compat

  • kind defaults to 'signature' everywhere; existing signatures untouched.
  • Stamp absent → receipts render exactly as today (signature-only / nothing).

Files

  • server/src/schema/user_signatures.ts — add kind.
  • server/drizzle/0058_user_signatures_kind.sql — migration.
  • server/src/routes/signatures.tskind param + sibling-deactivation key.
  • ui/src/components/settings/SignaturesSettings.tsx — Clinic Stamp section.
  • ui/src/lib/serverComm.ts (or chat-v2/lib) — client calls gain kind.
  • ui/src/lib/pdf/types.tsstampDataUrl.
  • ui/src/lib/pdf-generator.ts — fetch stamp for receipts.
  • ui/src/lib/pdf/templates/common.tsx — render stamp beside signature.
  • ui/src/lib/pdf/templates/ReceiptPdf.tsx — pass stampDataUrl.

Testing

  • Server: kind filtering + (clinic, role, kind) sibling-deactivation (signature upload doesn’t deactivate the stamp and vice-versa).
  • Manual/visual: upload a stamp in the Signature Center; generate a receipt; confirm signature + stamp render side by side; generate an invoice and confirm NO stamp.

Rollout

Apply migration 0058 (additive) with a pre-flight (same approach as 0057), deploy server + UI. Stamp is opt-in per clinic via the Signature Center.