// FieldOps — initial scaffold schema. // // All models except Tenant carry tenantId. Tenant scoping is enforced at runtime // by the Prisma extension in src/tenant-extension.ts — see that file's header for // the operations it covers and (more importantly) those it does NOT. generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum UserRole { ADMIN SUPERVISOR QUALITY OPERATOR } enum MaintenanceRequestStatus { OPEN CLAIMED RESOLVED } enum QualityDefectStatus { OPEN ACKNOWLEDGED CORRECTED } model Tenant { id String @id @default(cuid()) name String createdAt DateTime @default(now()) users User[] workstations Workstation[] events DomainEvent[] maintenanceRequests MaintenanceRequest[] operatorSessions OperatorSession[] qualityDefects QualityDefect[] } model User { id String @id @default(cuid()) tenantId String email String passwordHash String? role UserRole @default(OPERATOR) createdAt DateTime @default(now()) failedAttempts Int @default(0) lockedUntil DateTime? tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) reportedRequests MaintenanceRequest[] @relation("reported") claimedRequests MaintenanceRequest[] @relation("claimed") resolvedRequests MaintenanceRequest[] @relation("resolved") sessions OperatorSession[] createdDefects QualityDefect[] @relation("defectCreated") acknowledgedDefects QualityDefect[] @relation("defectAcknowledged") correctedDefects QualityDefect[] @relation("defectCorrected") @@unique([tenantId, email]) @@index([tenantId]) } model Workstation { id String @id @default(cuid()) tenantId String code String name String area String tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) maintenanceRequests MaintenanceRequest[] operatorSessions OperatorSession[] qualityDefects QualityDefect[] @@unique([tenantId, code]) @@index([tenantId]) } model DomainEvent { id String @id @default(cuid()) tenantId String aggregateType String aggregateId String eventType String payload Json occurredAt DateTime @default(now()) processedAt DateTime? tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) @@index([tenantId]) @@index([tenantId, processedAt]) @@index([tenantId, aggregateType, aggregateId]) } model MaintenanceRequest { id String @id @default(cuid()) tenantId String workstationId String reportedByUserId String description String photoKey String? status MaintenanceRequestStatus @default(OPEN) clientRequestId String createdAt DateTime @default(now()) claimedByUserId String? claimedAt DateTime? resolvedByUserId String? resolvedAt DateTime? resolutionNote String? tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) workstation Workstation @relation(fields: [workstationId], references: [id]) reportedBy User @relation("reported", fields: [reportedByUserId], references: [id]) claimedBy User? @relation("claimed", fields: [claimedByUserId], references: [id]) resolvedBy User? @relation("resolved", fields: [resolvedByUserId], references: [id]) @@unique([tenantId, clientRequestId]) @@index([tenantId, status, createdAt]) @@index([tenantId, reportedByUserId]) } /// MY QUALITY — an operator's active binding to a workstation ("badge-in"). /// At most one active session (endedAt == null) per user; starting a new one /// ends the previous. Quality defects route to whoever has the active session /// at the targeted workstation. model OperatorSession { id String @id @default(cuid()) tenantId String userId String workstationId String startedAt DateTime @default(now()) endedAt DateTime? tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id]) workstation Workstation @relation(fields: [workstationId], references: [id]) @@index([tenantId]) @@index([tenantId, userId, endedAt]) @@index([tenantId, workstationId, endedAt]) } /// MY QUALITY — a quality defect raised by QCP against a workstation, routed to /// the operator currently bound there. Mirrors MaintenanceRequest but in the /// opposite direction (quality -> operator). State: OPEN -> ACKNOWLEDGED -> /// CORRECTED. model QualityDefect { id String @id @default(cuid()) tenantId String workstationId String createdByUserId String defectType String location String? description String rfsCode String? photoKey String? status QualityDefectStatus @default(OPEN) createdAt DateTime @default(now()) acknowledgedByUserId String? acknowledgedAt DateTime? correctedByUserId String? correctedAt DateTime? correctionNote String? tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) workstation Workstation @relation(fields: [workstationId], references: [id]) createdBy User @relation("defectCreated", fields: [createdByUserId], references: [id]) acknowledgedBy User? @relation("defectAcknowledged", fields: [acknowledgedByUserId], references: [id]) correctedBy User? @relation("defectCorrected", fields: [correctedByUserId], references: [id]) @@index([tenantId, status, createdAt]) @@index([tenantId, workstationId, status]) }