generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── USERS & AUTH ───────────────────────────────────────────────────────────── enum Role { ADMIN QC PRODUCTION PRODUCTION_LEAD LOGISTICS_LEAD MANAGEMENT } model User { id String @id @default(cuid()) email String @unique name String password String role Role @default(PRODUCTION) department String? active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sessions Session[] capasOwned CAPA[] @relation("CAPAOwner") capasRaised CAPA[] @relation("CAPARaisedBy") auditsLed Audit[] @relation("AuditLead") ncrsRaised NCR[] @relation("NCRRaisedBy") submissions FormSubmission[] auditLogs AuditLog[] notifications Notification[] } model Session { id String @id @default(cuid()) userId String token String @unique expiresAt DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } // ─── AUDIT TRAIL ────────────────────────────────────────────────────────────── model AuditLog { id String @id @default(cuid()) userId String action String entity String entityId String before Json? after Json? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) @@index([entity, entityId]) @@index([userId]) @@index([createdAt]) } // ─── NOTIFICATIONS ──────────────────────────────────────────────────────────── enum NotifType { CAPA_OVERDUE CAPA_ASSIGNED AUDIT_DUE NCR_ESCALATED FORM_REVIEW_READY DOC_EXPIRING STANDARD_APPROVED SOLUTION_CONFIRMED } model Notification { id String @id @default(cuid()) userId String type NotifType title String body String read Boolean @default(false) link String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, read]) } // ─── CAPA ───────────────────────────────────────────────────────────────────── enum CAPAPriority { CRITICAL HIGH MEDIUM LOW } enum CAPAStatus { OPEN IN_PROGRESS OVERDUE CLOSED } model CAPA { id String @id @default(cuid()) ref String @unique title String description String? priority CAPAPriority @default(MEDIUM) status CAPAStatus @default(OPEN) progress Int @default(0) ownerId String raisedById String dueDate DateTime closedAt DateTime? rootCause String? corrAction String? prevAction String? department String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt owner User @relation("CAPAOwner", fields: [ownerId], references: [id]) raisedBy User @relation("CAPARaisedBy", fields: [raisedById], references: [id]) timeline CAPAEvent[] ncrs NCR[] escapes QualityEscape[] @@index([status]) @@index([ownerId]) @@index([dueDate]) } model CAPAEvent { id String @id @default(cuid()) capaId String event String note String? createdAt DateTime @default(now()) capa CAPA @relation(fields: [capaId], references: [id], onDelete: Cascade) @@index([capaId]) } // ─── AUDITS ─────────────────────────────────────────────────────────────────── enum AuditType { INTERNAL SUPPLIER EXTERNAL } enum AuditStatus { SCHEDULED IN_PROGRESS COMPLETED CANCELLED } model Audit { id String @id @default(cuid()) ref String @unique name String type AuditType @default(INTERNAL) status AuditStatus @default(SCHEDULED) leadId String scope String? scheduledAt DateTime completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lead User @relation("AuditLead", fields: [leadId], references: [id]) findings Finding[] @@index([status]) @@index([scheduledAt]) } enum FindingSeverity { OBSERVATION MINOR MAJOR CRITICAL } enum FindingStatus { OPEN IN_PROGRESS CLOSED } model Finding { id String @id @default(cuid()) auditId String description String severity FindingSeverity @default(MINOR) status FindingStatus @default(OPEN) category String? dueDate DateTime? closedAt DateTime? createdAt DateTime @default(now()) audit Audit @relation(fields: [auditId], references: [id], onDelete: Cascade) @@index([auditId]) } // ─── NCR ────────────────────────────────────────────────────────────────────── enum NCRSeverity { OBSERVATION MINOR MAJOR } enum NCRStatus { OPEN INVESTIGATING ESCALATED RESOLVED } model NCR { id String @id @default(cuid()) ref String @unique description String source String? severity NCRSeverity? status NCRStatus @default(OPEN) raisedById String resolvedAt DateTime? resolution String? category String? notified Boolean @default(false) notifiedAt DateTime? capaId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt raisedBy User @relation("NCRRaisedBy", fields: [raisedById], references: [id]) capa CAPA? @relation(fields: [capaId], references: [id]) @@index([status]) @@index([createdAt]) } // ─── RESOLUTIONS LIBRARY ────────────────────────────────────────────────────── // Filed whenever an NCR or quality escape is resolved with a category. // Searched for "similar past fix" matching on new issues. model Resolution { id String @id @default(cuid()) title String category String resolution String linkedRef String createdAt DateTime @default(now()) @@index([category]) } // ─── DOCUMENTS ──────────────────────────────────────────────────────────────── enum DocStatus { CURRENT PENDING_REVIEW EXPIRED ARCHIVED } enum DocCategory { SOP POLICY FORM WORK_INSTRUCTION RECORD } model Document { id String @id @default(cuid()) ref String @unique title String category DocCategory @default(SOP) revision String @default("A") status DocStatus @default(CURRENT) fileUrl String? ownerId String? reviewDate DateTime? approvedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status]) @@index([category]) } // ─── RISK REGISTER ──────────────────────────────────────────────────────────── enum RiskLevel { LOW MEDIUM HIGH CRITICAL } model Risk { id String @id @default(cuid()) title String description String? likelihood Int @default(1) impact Int @default(1) score Int @default(1) level RiskLevel @default(LOW) owner String? controls String? reviewDate DateTime? accepted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([level]) } // ─── SUPPLIERS ──────────────────────────────────────────────────────────────── enum SupplierStatus { APPROVED UNDER_REVIEW SUSPENDED INACTIVE } enum SupplierTier { TIER_1 TIER_2 TIER_3 } model Supplier { id String @id @default(cuid()) name String category String? tier SupplierTier @default(TIER_2) status SupplierStatus @default(UNDER_REVIEW) score Float? contact String? email String? lastAudit DateTime? nextAudit DateTime? notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status]) } // ─── FIRST BUILD FORMS ──────────────────────────────────────────────────────── enum FieldType { SHORT_TEXT LONG_TEXT NUMBER DATE SINGLE_CHOICE MULTI_CHOICE RATING PHOTO } enum FormStatus { DRAFT ACTIVE SUSPENDED REVIEW_READY STANDARD_SET ARCHIVED } model BuildForm { id String @id @default(cuid()) name String product String? description String? status FormStatus @default(DRAFT) minSubmissions Int @default(10) createdById String? publishedAt DateTime? suspendedAt DateTime? archivedAt DateTime? clonedFromId String? clonedFromName String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt fields FormField[] submissions FormSubmission[] standard QualityStandard? @@index([status]) } model FormField { id String @id @default(cuid()) formId String label String type FieldType @default(SHORT_TEXT) hint String? options String[] required Boolean @default(false) trackStd Boolean @default(true) order Int @default(0) form BuildForm @relation(fields: [formId], references: [id], onDelete: Cascade) @@index([formId]) } model FormSubmission { id String @id @default(cuid()) formId String submittedBy String data Json createdAt DateTime @default(now()) form BuildForm @relation(fields: [formId], references: [id]) user User @relation(fields: [submittedBy], references: [id]) @@index([formId]) @@index([createdAt]) } model QualityStandard { id String @id @default(cuid()) formId String @unique title String specs Json status String @default("DRAFT") approvedBy String? approvedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt form BuildForm @relation(fields: [formId], references: [id]) } // ─── SETTINGS ───────────────────────────────────────────────────────────────── model Setting { key String @id value String updatedAt DateTime @updatedAt } // ─── CLIENT RELEASE / SHIPMENTS ──────────────────────────────────────────────── // "Good status" packages — grouped by product/batch/date — that get // batch-emailed to clients once everything has passed QC. // Send access: PRODUCTION_LEAD, LOGISTICS_LEAD, ADMIN. enum ShipmentItemType { FORM_DATA NCR_FIX AUDIT OTHER } model Shipment { id String @id @default(cuid()) ref String @unique product String batch String client String clientEmail String? shippedAt DateTime sentAt DateTime? sentTo String? createdById String? createdAt DateTime @default(now()) items ShipmentItem[] escapes QualityEscape[] @@index([product, batch]) } model ShipmentItem { id String @id @default(cuid()) shipmentId String label String type ShipmentItemType @default(OTHER) included Boolean @default(true) order Int @default(0) shipment Shipment @relation(fields: [shipmentId], references: [id], onDelete: Cascade) @@index([shipmentId]) } // ─── CLIENT ISSUES (QUALITY ESCAPES) ────────────────────────────────────────── // A defect that passed QC and reached the client. Reuses the NCR // investigation/resolution flow, but linked to a shipment, and resolving // one can add an entry to the living shipping standard. // Report access: PRODUCTION_LEAD, LOGISTICS_LEAD, ADMIN. enum EscapeStatus { OPEN INVESTIGATING RESOLVED ESCALATED } model QualityEscape { id String @id @default(cuid()) ref String @unique shipmentId String description String contact String? severity NCRSeverity? status EscapeStatus @default(OPEN) resolution String? category String? capaId String? standardItemAdded String? createdAt DateTime @default(now()) resolvedAt DateTime? shipment Shipment @relation(fields: [shipmentId], references: [id]) capa CAPA? @relation(fields: [capaId], references: [id]) @@index([status]) } // ─── LIVING SHIPPING STANDARD ───────────────────────────────────────────────── // What must be true before a product gets "good status". Baseline items // plus items added automatically when a quality escape is resolved. model ShippingStandardItem { id String @id @default(cuid()) text String source String @default("Baseline") order Int @default(0) createdAt DateTime @default(now()) }