Add generic eSign portal for all compliance document types

Reusable signing flow: service handler generates document → inserts
esign_records row → emails JWT link → client reviews PDF + signs →
API stores signature + resumes pipeline. Works for RMD, CPNI, CALEA,
499-A engagement, discontinuance, CRTC, and any future doc types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-05-04 10:45:37 -05:00
parent 37a22cf474
commit 40844b2aff
6 changed files with 879 additions and 0 deletions

View file

@ -0,0 +1,48 @@
-- 076: Generic eSign records — stores signatures for any document type.
--
-- Instead of adding esign columns to every order table, this table
-- stores one row per signature event, linked by order_number.
--
-- Supports: RMD, CPNI, CALEA SSI, 499-A engagement, discontinuance,
-- CRTC, and any future document types.
--
-- The portal token carries { order_id (= order_number), order_type, email }.
-- order_type tells us which table/service the document belongs to.
CREATE TABLE IF NOT EXISTS esign_records (
id SERIAL PRIMARY KEY,
order_number TEXT NOT NULL, -- e.g. CO-ABCD1234
document_type TEXT NOT NULL, -- rmd, cpni, calea, 499a-engagement, discontinuance, crtc, etc.
document_title TEXT NOT NULL DEFAULT '', -- human-readable title shown to signer
entity_name TEXT NOT NULL DEFAULT '', -- company name shown on signing page
-- Document reference
document_minio_key TEXT, -- MinIO key for the PDF to sign
document_metadata JSONB DEFAULT '{}', -- any extra data (FRN, order details, etc.)
-- Signature
signature_type TEXT CHECK (signature_type IN ('drawn', 'typed')),
signature_data TEXT, -- base64 PNG (drawn) or typed name
signer_email TEXT,
signer_ip TEXT,
signer_user_agent TEXT,
-- Perjury declaration
requires_perjury BOOLEAN DEFAULT FALSE,
perjury_agreed BOOLEAN DEFAULT FALSE,
-- Status
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'signed', 'expired', 'revoked')),
signed_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_esign_order ON esign_records(order_number);
CREATE INDEX IF NOT EXISTS idx_esign_status ON esign_records(status) WHERE status = 'pending';
CREATE UNIQUE INDEX IF NOT EXISTS idx_esign_order_doc ON esign_records(order_number, document_type)
WHERE status IN ('pending', 'signed');