// NOTE: provider is temporarily set to "sqlite" for Phase 1 local verification. // Flip to "postgresql" once Docker + docker-compose Postgres is available. // All cascade rules and indexes below are portable between providers. // // Postgres-only additions applied post-cutover (see packages/db/POSTGRES_FTS.md): // * Generated tsvector column on Part(serial, mpn, notes) + GIN index (Phase 3 scope). // * WebhookSubscription.events and SavedView.filterJson currently stored as String (JSON // text) for SQLite portability; on Postgres these become String[] and Jsonb respectively. generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) username String @unique email String @unique passwordHash String role String @default("TECHNICIAN") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt partEvents PartEvent[] hostEvents HostEvent[] refreshTokens RefreshToken[] custodyParts Part[] @relation("Custody") repairs Repair[] savedViews SavedView[] csvImportJobs CsvImportJob[] } model RefreshToken { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) tokenHash String @unique expiresAt DateTime revokedAt DateTime? replacedBy String? createdAt DateTime @default(now()) @@index([userId]) @@index([expiresAt]) } model Manufacturer { id String @id @default(uuid()) name String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parts Part[] partModels PartModel[] } model PartModel { id String @id @default(uuid()) manufacturerId String manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id], onDelete: Restrict) mpn String eolDate DateTime? destroyOnFail Boolean @default(false) notes String? categoryId String? category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parts Part[] @@unique([manufacturerId, mpn]) @@index([manufacturerId]) @@index([eolDate]) @@index([categoryId]) } model Site { id String @id @default(uuid()) name String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt rooms Room[] } model Room { id String @id @default(uuid()) name String siteId String site Site @relation(fields: [siteId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bins Bin[] @@unique([siteId, name]) @@index([siteId]) } model Bin { id String @id @default(uuid()) name String roomId String room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parts Part[] @@unique([roomId, name]) @@index([roomId]) } model Category { id String @id @default(uuid()) name String @unique description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt partModels PartModel[] } model Part { id String @id @default(uuid()) serialNumber String @unique partModelId String partModel PartModel @relation(fields: [partModelId], references: [id], onDelete: Restrict) manufacturerId String manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id], onDelete: Restrict) price Float? state String @default("SPARE") binId String? bin Bin? @relation(fields: [binId], references: [id], onDelete: SetNull) hostId String? host Host? @relation(fields: [hostId], references: [id], onDelete: SetNull) custodianId String? custodian User? @relation("Custody", fields: [custodianId], references: [id], onDelete: SetNull) notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt events PartEvent[] tags PartTag[] problemInFms FmPart[] brokenRepairs Repair[] @relation("BrokenRepairs") replacementRepairs Repair[] @relation("ReplacementRepairs") @@index([state]) @@index([binId]) @@index([manufacturerId]) @@index([partModelId]) @@index([hostId]) @@index([custodianId]) } model PartEvent { id String @id @default(uuid()) partId String part Part @relation(fields: [partId], references: [id], onDelete: Cascade) userId String? user User? @relation(fields: [userId], references: [id], onDelete: SetNull) type String field String? oldValue String? newValue String? createdAt DateTime @default(now()) @@index([partId, createdAt(sort: Desc)]) @@index([userId]) } model Tag { id String @id @default(uuid()) name String @unique color String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parts PartTag[] } model PartTag { partId String tagId String part Part @relation(fields: [partId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@id([partId, tagId]) @@index([tagId]) } model Host { id String @id @default(uuid()) assetId String @unique name String @unique location String? notes String? state String @default("DEPLOYED") stack String @default("PRODUCTION") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parts Part[] fms Fm[] repairs Repair[] events HostEvent[] @@index([state]) @@index([stack]) } model HostEvent { id String @id @default(uuid()) hostId String host Host @relation(fields: [hostId], references: [id], onDelete: Cascade) userId String? user User? @relation(fields: [userId], references: [id], onDelete: SetNull) type String field String? oldValue String? newValue String? createdAt DateTime @default(now()) @@index([hostId, createdAt(sort: Desc)]) @@index([userId]) } model Fm { id String @id @default(uuid()) hostId String host Host @relation(fields: [hostId], references: [id], onDelete: Restrict) status String @default("OPEN") problem String openedAt DateTime @default(now()) closedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt problemParts FmPart[] repairs Repair[] @@index([status]) @@index([hostId]) @@index([status, openedAt(sort: Desc)]) @@map("fms") } model FmPart { fmId String partId String fm Fm @relation(fields: [fmId], references: [id], onDelete: Cascade) part Part @relation(fields: [partId], references: [id], onDelete: Restrict) createdAt DateTime @default(now()) @@id([fmId, partId]) @@index([partId]) @@map("fm_parts") } model Repair { id String @id @default(uuid()) hostId String host Host @relation(fields: [hostId], references: [id], onDelete: Restrict) brokenPartId String brokenPart Part @relation("BrokenRepairs", fields: [brokenPartId], references: [id], onDelete: Restrict) replacementPartId String replacement Part @relation("ReplacementRepairs", fields: [replacementPartId], references: [id], onDelete: Restrict) performedById String performedBy User @relation(fields: [performedById], references: [id], onDelete: Restrict) performedAt DateTime @default(now()) fmId String? fm Fm? @relation(fields: [fmId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([hostId, performedAt(sort: Desc)]) @@index([fmId]) @@index([performedById, performedAt(sort: Desc)]) @@index([brokenPartId]) @@index([replacementPartId]) @@map("repairs") } model WebhookSubscription { id String @id @default(uuid()) url String secret String // JSON array of WebhookEventName values. Becomes String[] on Postgres cutover. events String active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([active]) } model SavedView { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) resource String name String // JSON blob describing filters/sort/columns. Becomes Jsonb on Postgres cutover. filterJson String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([userId, resource, name]) @@index([userId]) } model CsvImportJob { id String @id @default(uuid()) userId String? user User? @relation(fields: [userId], references: [id], onDelete: SetNull) resource String status String @default("PENDING") filename String stagedRows Int @default(0) // JSON array of CsvImportRowError. Nullable until validation runs. errors String? startedAt DateTime? finishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([status]) }