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>
258 lines
8.7 KiB
PL/PgSQL
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();
|