generator client { provider = "prisma-client" output = "../src/generated/prisma" } datasource db { provider = "postgresql" } // ─── Enums ────────────────────────────────────────────── enum Role { ADMIN PRODUCER ARTIST } enum ProjectStatus { ACTIVE ON_HOLD COMPLETED ARCHIVED } enum Priority { LOW MEDIUM HIGH URGENT } enum DeliverableStatus { NOT_STARTED IN_PROGRESS IN_REVIEW APPROVED ON_HOLD } enum StageStatus { BLOCKED NOT_STARTED IN_PROGRESS IN_REVIEW CHANGES_REQUESTED APPROVED DELIVERED SKIPPED } enum RevisionStatus { SUBMITTED IN_REVIEW CHANGES_REQUESTED APPROVED } enum NotificationType { ASSIGNMENT STATUS_CHANGE REVISION_SUBMITTED REVISION_FEEDBACK COMMENT DEADLINE_APPROACHING DEADLINE_OVERDUE STAGE_UNBLOCKED } enum AssignmentRole { LEAD SUPPORT } enum SkillLevel { JUNIOR INTERMEDIATE SENIOR LEAD } // ─── RBAC ────────────────────────────────────────────── model OrgRolePermission { id String @id @default(cuid()) organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) role Role permission Permission @@unique([organizationId, role, permission]) @@index([organizationId]) @@map("org_role_permissions") } // ─── Organization ─────────────────────────────────────── model Organization { id String @id @default(cuid()) name String domain String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] projects Project[] automationRules AutomationRule[] rolePermissions OrgRolePermission[] pipelineTemplates PipelineTemplate[] deliverables Deliverable[] deliverableStages DeliverableStage[] invitations Invitation[] customFieldDefs CustomFieldDefinition[] notificationRules NotificationRule[] @@map("organizations") } // ─── Auth.js models ───────────────────────────────────── model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? image String? role Role @default(ARTIST) department String? maxCapacity Int @default(5) organizationId String? organization Organization? @relation(fields: [organizationId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accounts Account[] sessions Session[] assignments StageAssignment[] comments Comment[] notifications Notification[] skills UserSkill[] searchLogs SearchLog[] automationRules AutomationRule[] @relation("AutomationCreator") chatMessages ChatMessage[] invitationsSent Invitation[] @relation("InvitedBy") annotations Annotation[] feedbackCreated FeedbackItem[] @relation("FeedbackCreator") feedbackAssigned FeedbackItem[] @relation("FeedbackAssignee") feedbackResolved FeedbackItem[] @relation("FeedbackResolver") feedbackVerified FeedbackItem[] @relation("FeedbackVerifier") colorProbes ColorProbe[] @relation("ColorProbeCreator") reviewSessionsCreated ReviewSession[] @relation("ReviewSessionCreator") reviewSessionDecisions ReviewSessionItem[] @relation("ReviewSessionDecider") @@map("users") } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@map("accounts") } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } model VerificationToken { identifier String token String expires DateTime @@unique([identifier, token]) @@map("verification_tokens") } // ─── Pipeline Templates (seed data) ──────────────────── model PipelineStageTemplate { id String @id @default(cuid()) name String @unique slug String @unique order Int @unique isCriticalGate Boolean @default(false) isOptional Boolean @default(false) description String? estimatedDays Float? dependsOn PipelineStageDependency[] @relation("DependsOnStage") dependedBy PipelineStageDependency[] @relation("PrerequisiteStage") deliverableStages DeliverableStage[] skillRequirements StageSkillRequirement[] @@map("pipeline_stage_templates") } model PipelineStageDependency { id String @id @default(cuid()) stageId String prerequisiteId String stage PipelineStageTemplate @relation("DependsOnStage", fields: [stageId], references: [id]) prerequisite PipelineStageTemplate @relation("PrerequisiteStage", fields: [prerequisiteId], references: [id]) @@unique([stageId, prerequisiteId]) @@map("pipeline_stage_dependencies") } // ─── Dynamic Pipeline Templates (org-scoped) ─────────── model PipelineTemplate { id String @id @default(cuid()) name String description String? organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) isArchived Boolean @default(false) isDefault Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt stages PipelineStageDefinition[] projects Project[] @@unique([organizationId, name]) @@index([organizationId]) @@map("pipeline_templates") } model PipelineStageDefinition { id String @id @default(cuid()) pipelineId String pipeline PipelineTemplate @relation(fields: [pipelineId], references: [id], onDelete: Cascade) name String slug String order Int isCriticalGate Boolean @default(false) isOptional Boolean @default(false) description String? estimatedDays Float? color String? customStatuses Json? dependsOn PipelineStageDependencyV2[] @relation("DependsOnStageV2") dependedBy PipelineStageDependencyV2[] @relation("PrerequisiteStageV2") deliverableStages DeliverableStage[] @@unique([pipelineId, slug]) @@unique([pipelineId, order]) @@map("pipeline_stage_definitions") } model PipelineStageDependencyV2 { id String @id @default(cuid()) stageId String prerequisiteId String stage PipelineStageDefinition @relation("DependsOnStageV2", fields: [stageId], references: [id], onDelete: Cascade) prerequisite PipelineStageDefinition @relation("PrerequisiteStageV2", fields: [prerequisiteId], references: [id], onDelete: Cascade) @@unique([stageId, prerequisiteId]) @@map("pipeline_stage_dependencies_v2") } // ─── Project ──────────────────────────────────────────── model Project { id String @id @default(cuid()) projectCode String @unique name String description String? status ProjectStatus @default(ACTIVE) priority Priority @default(MEDIUM) startDate DateTime? dueDate DateTime? businessUnit String? formFactor String? codeName String? npiOrRefresh String? quarter String? requestor String? workfrontId String? omgCode String? bmtId String? estimatedCost Float? actualCost Float? agency String? // pgvector embedding for semantic search (raw SQL — Prisma can't query this directly) embedding Unsupported("vector(768)")? customFields Json? organizationId String organization Organization @relation(fields: [organizationId], references: [id]) pipelineTemplateId String? pipelineTemplate PipelineTemplate? @relation(fields: [pipelineTemplateId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deliverables Deliverable[] @@index([organizationId]) @@index([pipelineTemplateId]) @@index([status]) @@map("projects") } // ─── Deliverable ──────────────────────────────────────── model Deliverable { id String @id @default(cuid()) name String status DeliverableStatus @default(NOT_STARTED) priority Priority @default(MEDIUM) dueDate DateTime? notes String? cmfSku String? assetCount Int? requestedDueDate DateTime? plannedDeliveryDate DateTime? actualDeliveryDate DateTime? wfInputDate DateTime? // pgvector embedding for semantic search (raw SQL — Prisma can't query this directly) embedding Unsupported("vector(768)")? customFields Json? projectId String project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) organizationId String? organization Organization? @relation(fields: [organizationId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt stages DeliverableStage[] @@index([projectId]) @@index([organizationId]) @@index([status]) @@map("deliverables") } // ─── Deliverable Stage (instance per deliverable) ─────── model DeliverableStage { id String @id @default(cuid()) status StageStatus @default(BLOCKED) revisionRound Int @default(0) startDate DateTime? completedDate DateTime? dueDate DateTime? notes String? subStatus String? manualSchedule Boolean @default(false) scheduleConflict Boolean @default(false) scheduleDelta Int? deliverableId String deliverable Deliverable @relation(fields: [deliverableId], references: [id], onDelete: Cascade) templateId String template PipelineStageTemplate @relation(fields: [templateId], references: [id]) stageDefinitionId String? stageDefinition PipelineStageDefinition? @relation(fields: [stageDefinitionId], references: [id]) organizationId String? organization Organization? @relation(fields: [organizationId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt assignments StageAssignment[] revisions Revision[] comments Comment[] feedbackItems FeedbackItem[] reviewSessionItems ReviewSessionItem[] @@unique([deliverableId, templateId]) @@index([deliverableId]) @@index([stageDefinitionId]) @@index([organizationId]) @@index([status]) @@map("deliverable_stages") } // ─── Stage Assignment ─────────────────────────────────── model StageAssignment { id String @id @default(cuid()) role AssignmentRole? @default(LEAD) deliverableStageId String deliverableStage DeliverableStage @relation(fields: [deliverableStageId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) @@unique([deliverableStageId, userId]) @@index([userId]) @@map("stage_assignments") } // ─── Revision ─────────────────────────────────────────── model Revision { id String @id @default(cuid()) roundNumber Int status RevisionStatus @default(SUBMITTED) feedbackNotes String? internalNotes String? attachments Json? deliverableStageId String deliverableStage DeliverableStage @relation(fields: [deliverableStageId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt annotations Annotation[] feedbackItems FeedbackItem[] colorProbes ColorProbe[] reviewSessionItems ReviewSessionItem[] @@index([deliverableStageId]) @@map("revisions") } // ─── Comment ──────────────────────────────────────────── model Comment { id String @id @default(cuid()) content String @db.Text deliverableStageId String deliverableStage DeliverableStage @relation(fields: [deliverableStageId], references: [id], onDelete: Cascade) authorId String author User @relation(fields: [authorId], references: [id]) parentId String? parent Comment? @relation("CommentThread", fields: [parentId], references: [id]) replies Comment[] @relation("CommentThread") annotations Annotation[] feedbackItems FeedbackItem[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([deliverableStageId]) @@index([parentId]) @@map("comments") } // ─── Notification ─────────────────────────────────────── model Notification { id String @id @default(cuid()) type NotificationType title String message String link String? isRead Boolean @default(false) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@index([userId, isRead]) @@map("notifications") } // ─── Skills & Capacity (Phase 6) ──────────────────────── model Skill { id String @id @default(cuid()) name String @unique createdAt DateTime @default(now()) users UserSkill[] stageRequirements StageSkillRequirement[] @@map("skills") } model UserSkill { userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) skillId String skill Skill @relation(fields: [skillId], references: [id], onDelete: Cascade) level SkillLevel @default(INTERMEDIATE) @@id([userId, skillId]) @@map("user_skills") } model StageSkillRequirement { stageTemplateId String stageTemplate PipelineStageTemplate @relation(fields: [stageTemplateId], references: [id], onDelete: Cascade) skillId String skill Skill @relation(fields: [skillId], references: [id], onDelete: Cascade) importance Int @default(1) // 1=nice-to-have, 2=important, 3=required @@id([stageTemplateId, skillId]) @@map("stage_skill_requirements") } // ─── Automation Engine (Phase 7.1) ────────────────────── model AutomationRule { id String @id @default(cuid()) name String description String? organizationId String organization Organization @relation(fields: [organizationId], references: [id]) isEnabled Boolean @default(true) trigger Json // { event, conditions[] } actions Json // [{ type, params }] createdById String createdBy User @relation("AutomationCreator", fields: [createdById], references: [id]) executions AutomationExecution[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([isEnabled]) @@map("automation_rules") } model AutomationExecution { id String @id @default(cuid()) ruleId String rule AutomationRule @relation(fields: [ruleId], references: [id], onDelete: Cascade) triggeredBy Json // the event payload that triggered execution result Json // what actions were taken + outcomes status ExecutionStatus error String? executedAt DateTime @default(now()) @@index([ruleId]) @@index([executedAt]) @@map("automation_executions") } enum ExecutionStatus { SUCCESS PARTIAL_FAILURE FAILURE } enum Permission { PROJECT_CREATE PROJECT_UPDATE PROJECT_DELETE PROJECT_VIEW DELIVERABLE_CREATE DELIVERABLE_UPDATE DELIVERABLE_DELETE STAGE_UPDATE_STATUS STAGE_ASSIGN STAGE_SCHEDULE REVISION_CREATE REVISION_REVIEW COMMENT_CREATE COMMENT_DELETE_ANY PIPELINE_MANAGE USER_MANAGE ROLE_MANAGE ORG_SETTINGS AUTOMATION_MANAGE FIELD_CUSTOMIZE } // ─── Chat History (CLI Anything) ──────────────────────── model ChatMessage { id String @id @default(cuid()) sessionId String role String // "user" | "assistant" | "system" content String @db.Text toolCalls Json? // tool calls made by assistant toolResults Json? // results of tool execution metadata Json? // context: active project, etc. userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) organizationId String createdAt DateTime @default(now()) @@index([sessionId]) @@index([userId]) @@map("chat_messages") } // ─── Custom Fields ────────────────────────────────────── model CustomFieldDefinition { id String @id @default(cuid()) organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) entityType String // "PROJECT" | "DELIVERABLE" fieldName String fieldType String // "TEXT" | "NUMBER" | "DATE" | "SELECT" | "BOOLEAN" fieldOptions Json? // For SELECT type: { options: string[] } isRequired Boolean @default(false) order Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([organizationId, entityType, fieldName]) @@index([organizationId]) @@map("custom_field_definitions") } // ─── Notification Rules ───────────────────────────────── model NotificationRule { id String @id @default(cuid()) organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) name String isEnabled Boolean @default(true) event String // e.g. "STAGE_STATUS_CHANGE", "DEADLINE_APPROACHING", "REVISION_SUBMITTED" conditions Json? // { field: string, operator: string, value: any }[] channels Json // ["IN_APP", "EMAIL"] recipientRoles Json // ["ADMIN", "PRODUCER"] or ["ASSIGNEE"] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([event]) @@map("notification_rules") } // ─── Invitations ──────────────────────────────────────── model Invitation { id String @id @default(cuid()) email String role Role @default(ARTIST) organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) invitedById String invitedBy User @relation("InvitedBy", fields: [invitedById], references: [id]) token String @unique @default(cuid()) expiresAt DateTime acceptedAt DateTime? createdAt DateTime @default(now()) @@unique([email, organizationId]) @@index([organizationId]) @@index([token]) @@map("invitations") } // ─── Semantic Search (Phase 8.4) ──────────────────────── model SearchLog { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) query String resultCount Int @default(0) clickedId String? createdAt DateTime @default(now()) @@index([userId]) @@map("search_logs") } // ─── Annotations (Visual Review) ──────────────────────── enum AnnotationType { RECTANGLE ELLIPSE ARROW FREEHAND TEXT PIN SCREENSHOT } enum FeedbackStatus { OPEN IN_PROGRESS RESOLVED VERIFIED REOPENED } // ─── Annotation ───────────────────────────────────────── model Annotation { id String @id @default(cuid()) commentId String comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) revisionId String revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) type AnnotationType data Json imageX Float imageY Float createdById String createdBy User @relation(fields: [createdById], references: [id]) createdAt DateTime @default(now()) feedbackItems FeedbackItem[] @@index([commentId]) @@index([revisionId]) @@map("annotations") } // ─── Feedback Item ────────────────────────────────────── model FeedbackItem { id String @id @default(cuid()) deliverableStageId String deliverableStage DeliverableStage @relation(fields: [deliverableStageId], references: [id], onDelete: Cascade) revisionId String revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) annotationId String? annotation Annotation? @relation(fields: [annotationId], references: [id], onDelete: SetNull) commentId String? comment Comment? @relation(fields: [commentId], references: [id], onDelete: SetNull) summary String isActionItem Boolean @default(true) // true = action item (must fix), false = info callout status FeedbackStatus @default(OPEN) sortOrder Int @default(0) assignedToId String? assignedTo User? @relation("FeedbackAssignee", fields: [assignedToId], references: [id], onDelete: SetNull) createdById String createdBy User @relation("FeedbackCreator", fields: [createdById], references: [id]) resolvedById String? resolvedBy User? @relation("FeedbackResolver", fields: [resolvedById], references: [id], onDelete: SetNull) resolvedAt DateTime? resolutionNote String? verifiedById String? verifiedBy User? @relation("FeedbackVerifier", fields: [verifiedById], references: [id], onDelete: SetNull) verifiedAt DateTime? carriedFromId String? carriedFrom FeedbackItem? @relation("FeedbackCarry", fields: [carriedFromId], references: [id], onDelete: SetNull) carriedTo FeedbackItem[] @relation("FeedbackCarry") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([deliverableStageId]) @@index([revisionId]) @@index([assignedToId]) @@index([status]) @@map("feedback_items") } // FeedbackSeverity removed — replaced by isActionItem boolean // Action items = things the artist must fix (default for annotations) // Info callouts = context/reference that doesn't need action // ─── Review Sessions (A6) ──────────────────────────────── enum ReviewSessionStatus { DRAFT IN_PROGRESS COMPLETED } model ReviewSession { id String @id @default(cuid()) name String description String? status ReviewSessionStatus @default(DRAFT) createdById String createdBy User @relation("ReviewSessionCreator", fields: [createdById], references: [id]) organizationId String items ReviewSessionItem[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([status]) @@map("review_sessions") } model ReviewSessionItem { id String @id @default(cuid()) sessionId String session ReviewSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) deliverableStageId String deliverableStage DeliverableStage @relation(fields: [deliverableStageId], references: [id]) revisionId String? revision Revision? @relation(fields: [revisionId], references: [id]) sortOrder Int decision String? // "APPROVED" | "CHANGES_REQUESTED" — stored as string to avoid enum coupling with D2 decisionNote String? decidedById String? decidedBy User? @relation("ReviewSessionDecider", fields: [decidedById], references: [id]) decidedAt DateTime? @@index([sessionId]) @@map("review_session_items") } // ─── Color Probes (CMF Eyedropper) ───────────────────── model ColorProbe { id String @id @default(cuid()) revisionId String revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) index Int // 1-12, display order workingX Float // image-space coordinates on working image workingY Float referenceX Float // image-space coordinates on reference (defaults to same as working) referenceY Float createdById String createdBy User @relation("ColorProbeCreator", fields: [createdById], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([revisionId, index]) @@index([revisionId]) @@map("color_probes") }