init-source

This commit is contained in:
2026-06-15 16:21:53 -05:00
parent a12a3fc72e
commit 631890c5bd
1010 changed files with 107132 additions and 0 deletions
@@ -0,0 +1,549 @@
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())
}