npi-2026/db/init/001_schema.sql
DJP 26d3eb03ed Initial commit — NPI Tracker application
Full-stack NPI (New Product Introduction) gate tracking tool with:
- Express/TypeScript API with PostgreSQL
- React/Vite/Mantine frontend
- 13-gate process (G0-G12) with 4 product categories
- RACI matrix auto-population from templates
- File attachments with preview (images, PDFs, text)
- Kanban board, Gantt/timeline views
- Docker Compose orchestration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:36:31 -04:00

258 lines
8.7 KiB
PL/PgSQL

-- NPI Product Tracker Database Schema
-- All tables for tracking product lifecycle through NPI gates
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Product category enum
CREATE TYPE category AS ENUM ('I', 'IB', 'II', 'III');
-- Project status enum
CREATE TYPE project_status AS ENUM ('active', 'on_hold', 'completed', 'cancelled');
-- Gate status enum
CREATE TYPE gate_status AS ENUM ('not_started', 'in_progress', 'blocked', 'passed', 'skipped', 'failed');
-- RACI type enum
CREATE TYPE raci_type AS ENUM ('R', 'A', 'C', 'I');
-- Update type enum
CREATE TYPE update_type AS ENUM ('progress', 'blocker', 'decision', 'note');
-- PPPP status enum
CREATE TYPE pppp_status AS ENUM ('draft', 'in_review', 'approved', 'final');
-- Notification type enum
CREATE TYPE notification_type AS ENUM (
'overdue_update', 'gate_review', 'weekly_digest',
'assignment', 'gate_passed', 'blocker'
);
-- ============================================================
-- REFERENCE TABLES (seeded from Excel, rarely changed)
-- ============================================================
-- 13 gate templates (G0-G12)
CREATE TABLE gates (
id SERIAL PRIMARY KEY,
gate_number INTEGER NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL
);
-- Which gates apply to which categories
CREATE TABLE gate_applicability (
id SERIAL PRIMARY KEY,
gate_id INTEGER NOT NULL REFERENCES gates(id) ON DELETE CASCADE,
category category NOT NULL,
is_required BOOLEAN DEFAULT true,
is_trigger_based BOOLEAN DEFAULT false,
UNIQUE(gate_id, category)
);
-- Steps within each gate per category (from Excel)
CREATE TABLE gate_steps (
id SERIAL PRIMARY KEY,
gate_id INTEGER NOT NULL REFERENCES gates(id) ON DELETE CASCADE,
category category NOT NULL,
step_name VARCHAR(500) NOT NULL,
description TEXT,
evidence_required TEXT,
common_failure_modes TEXT,
sort_order INTEGER NOT NULL DEFAULT 0
);
-- RACI assignment per step per role (reference)
CREATE TABLE gate_step_raci (
id SERIAL PRIMARY KEY,
gate_step_id INTEGER NOT NULL REFERENCES gate_steps(id) ON DELETE CASCADE,
role_name VARCHAR(100) NOT NULL,
raci raci_type NOT NULL,
UNIQUE(gate_step_id, role_name, raci)
);
-- ============================================================
-- PROJECT TABLES (instance data)
-- ============================================================
-- Each product being tracked through NPI
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
category category NOT NULL,
current_gate INTEGER DEFAULT 0,
status project_status DEFAULT 'active',
-- Key owners stored as text (no users table yet)
requestor_name VARCHAR(255),
requestor_email VARCHAR(255),
sponsor_name VARCHAR(255),
sponsor_email VARCHAR(255),
product_owner_name VARCHAR(255),
product_owner_email VARCHAR(255),
tech_lead_name VARCHAR(255),
tech_lead_email VARCHAR(255),
commercial_owner_name VARCHAR(255),
commercial_owner_email VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Instance: a gate for a specific project
CREATE TABLE project_gates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
gate_id INTEGER NOT NULL REFERENCES gates(id) ON DELETE CASCADE,
status gate_status DEFAULT 'not_started',
target_start_date DATE,
target_end_date DATE,
actual_start_date DATE,
actual_end_date DATE,
approved_by VARCHAR(255),
approved_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(project_id, gate_id)
);
-- Instance: checklist items per project gate
CREATE TABLE project_gate_checklist (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_gate_id UUID NOT NULL REFERENCES project_gates(id) ON DELETE CASCADE,
gate_step_id INTEGER REFERENCES gate_steps(id),
step_name VARCHAR(500) NOT NULL,
is_completed BOOLEAN DEFAULT false,
completed_by VARCHAR(255),
completed_at TIMESTAMPTZ,
evidence_link TEXT,
evidence_notes TEXT,
is_applicable BOOLEAN DEFAULT true,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RACI assignments per project (who fills each role)
CREATE TABLE project_raci_assignments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
role_name VARCHAR(100) NOT NULL,
assignee_name VARCHAR(255),
assignee_email VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(project_id, role_name)
);
-- Trigger flags driving gate tailoring
CREATE TABLE project_triggers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
trigger_name VARCHAR(255) NOT NULL,
is_triggered BOOLEAN DEFAULT false,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(project_id, trigger_name)
);
-- Status updates / progress notes
CREATE TABLE status_updates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
project_gate_id UUID REFERENCES project_gates(id) ON DELETE SET NULL,
author_name VARCHAR(255) NOT NULL,
author_email VARCHAR(255),
content TEXT NOT NULL,
update_type update_type DEFAULT 'progress',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- PPPP tracking (Category III commercial)
CREATE TABLE pppp_tracking (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
positioning TEXT,
pricing TEXT,
packaging TEXT,
proposition TEXT,
status pppp_status DEFAULT 'draft',
version INTEGER DEFAULT 1,
updated_by VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- In-app + email notifications
CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
recipient_email VARCHAR(255) NOT NULL,
recipient_name VARCHAR(255),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
type notification_type NOT NULL,
subject VARCHAR(500),
body TEXT,
is_read BOOLEAN DEFAULT false,
email_sent BOOLEAN DEFAULT false,
email_sent_at TIMESTAMPTZ,
mailgun_message_id VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Audit log
CREATE TABLE audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
actor_name VARCHAR(255),
actor_email VARCHAR(255),
project_id UUID REFERENCES projects(id) ON DELETE SET NULL,
action VARCHAR(255) NOT NULL,
entity_type VARCHAR(100),
entity_id UUID,
details JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- INDEXES
-- ============================================================
CREATE INDEX idx_project_gates_project ON project_gates(project_id);
CREATE INDEX idx_project_gates_status ON project_gates(status);
CREATE INDEX idx_checklist_project_gate ON project_gate_checklist(project_gate_id);
CREATE INDEX idx_checklist_completed ON project_gate_checklist(is_completed) WHERE is_applicable = true;
CREATE INDEX idx_raci_project ON project_raci_assignments(project_id);
CREATE INDEX idx_triggers_project ON project_triggers(project_id);
CREATE INDEX idx_updates_project ON status_updates(project_id, created_at DESC);
CREATE INDEX idx_notifications_recipient ON notifications(recipient_email, is_read);
CREATE INDEX idx_notifications_project ON notifications(project_id);
CREATE INDEX idx_audit_project ON audit_log(project_id, created_at DESC);
CREATE INDEX idx_projects_status ON projects(status);
CREATE INDEX idx_projects_type ON projects(category);
CREATE INDEX idx_projects_gate ON projects(current_gate);
-- ============================================================
-- TRIGGERS (auto-update updated_at)
-- ============================================================
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_projects_updated_at
BEFORE UPDATE ON projects
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_project_gates_updated_at
BEFORE UPDATE ON project_gates
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_checklist_updated_at
BEFORE UPDATE ON project_gate_checklist
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_pppp_updated_at
BEFORE UPDATE ON pppp_tracking
FOR EACH ROW EXECUTE FUNCTION update_updated_at();