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 secondkind 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
stampDataUrlto other templates). - Per-role stamps (explicitly one clinic stamp).
- Changing how signatures work today.
Design
Data model
server/src/schema/user_signatures.ts gains:
- 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), oneis_active=trueper clinic amongkind='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. Forkind='stamp', role is forced to'clinic'server-side (clinic-wide), so callers just passkind=stamp.POST /signatures(kindin body; stamp setsrole='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.tsPdfAssets: addstampDataUrl?: string.ui/src/lib/pdf-generator.ts:- Add a helper to fetch the active clinic stamp (
kind='stamp', role'clinic') → data URL (reusegetActiveSignature/getSignatureImageDataUrlwith a kind param). generateReceiptPDFincludesstampDataUrlin its assets. Other generators do NOT (keeps stamp receipts-only).
- Add a helper to fetch the active clinic stamp (
ui/src/lib/pdf/templates/common.tsxPdfSignatureBlock: accept optionalstampDataUrl; 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 nullonly when BOTH absent.ui/src/lib/pdf/templates/ReceiptPdf.tsx: passstampDataUrl={assets?.stampDataUrl}toPdfSignatureBlock. No other template changes.
Back-compat
kinddefaults to'signature'everywhere; existing signatures untouched.- Stamp absent → receipts render exactly as today (signature-only / nothing).
Files
server/src/schema/user_signatures.ts— addkind.server/drizzle/0058_user_signatures_kind.sql— migration.server/src/routes/signatures.ts—kindparam + sibling-deactivation key.ui/src/components/settings/SignaturesSettings.tsx— Clinic Stamp section.ui/src/lib/serverComm.ts(or chat-v2/lib) — client calls gainkind.ui/src/lib/pdf/types.ts—stampDataUrl.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— passstampDataUrl.
Testing
- Server:
kindfiltering + (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 migration0058 (additive) with a pre-flight (same approach as 0057), deploy
server + UI. Stamp is opt-in per clinic via the Signature Center.
