Phase 8: Visitor Management - Visitor/VisitorLog/AccessZone models - Check-in/out with badge generation - Zone occupancy tracking - Kiosk and management pages Phase 9: Messaging & Communication - Announcements with priority levels - Acknowledgement tracking - Shift notes for team handoffs - AnnouncementBanner component Phase 10: Compliance & Audit Trail - Immutable AuditLog model - Document versioning and approval workflow - Acknowledgement tracking for SOPs - CSV export for audit logs Phase 11: Accessibility & i18n - WCAG 2.1 AA compliance utilities - react-i18next with EN/ES translations - User preferences context (theme, font size, etc) - High contrast and reduced motion support Phase 12: Hardware Integration - QR code generation for batches/plants/visitors - Printable label system - Visitor badge printing Phase 13: Advanced Features - Environmental monitoring (sensors, readings, alerts) - Financial tracking (transactions, P&L reports) - AI/ML insights (yield predictions, anomaly detection)
1098 lines
28 KiB
Text
1098 lines
28 KiB
Text
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum RoleEnum {
|
|
OWNER
|
|
MANAGER
|
|
GROWER
|
|
STAFF
|
|
}
|
|
|
|
enum RoomType {
|
|
VEG
|
|
FLOWER
|
|
DRY
|
|
CURE
|
|
MOTHER
|
|
CLONE
|
|
FACILITY
|
|
}
|
|
|
|
enum TaskStatus {
|
|
PENDING
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
BLOCKED
|
|
}
|
|
|
|
// Daily Walkthrough Enums
|
|
enum WalkthroughStatus {
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
INCOMPLETE
|
|
}
|
|
|
|
enum TankType {
|
|
VEG
|
|
FLOWER
|
|
}
|
|
|
|
enum TankStatus {
|
|
OK
|
|
LOW
|
|
CRITICAL
|
|
}
|
|
|
|
enum HealthStatus {
|
|
GOOD
|
|
FAIR
|
|
NEEDS_ATTENTION
|
|
}
|
|
|
|
enum AccessStatus {
|
|
OK
|
|
ISSUES
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
passwordHash String
|
|
name String?
|
|
role RoleEnum @default(STAFF) // Kept for legacy/fallback, but relying on roleId usually
|
|
|
|
roleId String?
|
|
userRole Role? @relation(fields: [roleId], references: [id])
|
|
|
|
rate Decimal? @map("hourly_rate")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
tasks Task[]
|
|
timeLogs TimeLog[]
|
|
walkthroughs DailyWalkthrough[]
|
|
touchPoints PlantTouchPoint[]
|
|
|
|
// Phase 3 Relations
|
|
weightLogs WeightLog[]
|
|
batchNotes BatchNote[]
|
|
batchPhotos BatchPhoto[]
|
|
|
|
// Phase 8: Visitor Management
|
|
visitorEscorts VisitorLog[] @relation("VisitorEscort")
|
|
visitorApprovals VisitorLog[] @relation("VisitorApprover")
|
|
|
|
// Phase 9: Messaging
|
|
announcements Announcement[] @relation("AnnouncementCreator")
|
|
announcementAcks AnnouncementAck[] @relation("AnnouncementAcker")
|
|
shiftNotes ShiftNote[] @relation("ShiftNoteCreator")
|
|
|
|
// Phase 10: Compliance
|
|
documentsCreated Document[] @relation("DocumentCreator")
|
|
documentsApproved Document[] @relation("DocumentApprover")
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model Role {
|
|
id String @id @default(uuid())
|
|
name String @unique
|
|
description String?
|
|
permissions Json // Store permissions as JSON: { users: { read: true, write: true }, ... }
|
|
isSystem Boolean @default(false) // System roles cannot be deleted
|
|
|
|
users User[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("roles")
|
|
}
|
|
|
|
model Room {
|
|
id String @id @default(uuid())
|
|
name String
|
|
type RoomType
|
|
sqft Float?
|
|
width Float?
|
|
length Float?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
capacity Int? // plant count or sq ft
|
|
status RoomStatus @default(ACTIVE)
|
|
targetTemp Float?
|
|
targetHumidity Float?
|
|
targetVPD Float?
|
|
|
|
batches Batch[]
|
|
tasks Task[]
|
|
|
|
@@map("rooms")
|
|
}
|
|
|
|
model Batch {
|
|
id String @id @default(uuid())
|
|
name String // e.g., "B-2023-10-15-GG4"
|
|
strain String
|
|
startDate DateTime
|
|
harvestDate DateTime?
|
|
status String @default("ACTIVE") // ACTIVE, HARVESTED, COMPLETED
|
|
|
|
plantCount Int @default(0)
|
|
source BatchSource @default(CLONE)
|
|
stage BatchStage @default(CLONE_IN)
|
|
metrcTags String[] // array of METRC tag IDs
|
|
|
|
roomId String?
|
|
room Room? @relation(fields: [roomId], references: [id])
|
|
|
|
tasks Task[]
|
|
touchPoints PlantTouchPoint[]
|
|
ipmSchedule IPMSchedule?
|
|
|
|
// Phase 3 Relations
|
|
weights WeightLog[]
|
|
notes BatchNote[]
|
|
photos BatchPhoto[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("batches")
|
|
}
|
|
|
|
model TaskTemplate {
|
|
id String @id @default(uuid())
|
|
title String
|
|
description String? // Instructions/SOP
|
|
roomType RoomType?
|
|
estimatedMinutes Int?
|
|
materials String[] // Array of material names
|
|
recurrence Json? // Cron or custom pattern
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
tasks Task[]
|
|
|
|
@@map("task_templates")
|
|
}
|
|
|
|
model Task {
|
|
id String @id @default(uuid())
|
|
title String
|
|
description String?
|
|
status TaskStatus @default(PENDING)
|
|
priority String @default("MEDIUM")
|
|
|
|
templateId String?
|
|
template TaskTemplate? @relation(fields: [templateId], references: [id])
|
|
|
|
assignedToId String?
|
|
assignedTo User? @relation(fields: [assignedToId], references: [id])
|
|
|
|
batchId String?
|
|
batch Batch? @relation(fields: [batchId], references: [id])
|
|
|
|
roomId String?
|
|
room Room? @relation(fields: [roomId], references: [id])
|
|
|
|
dueDate DateTime?
|
|
completedAt DateTime?
|
|
|
|
notes String?
|
|
photos String[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("tasks")
|
|
}
|
|
|
|
model TimeLog {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
startTime DateTime
|
|
endTime DateTime?
|
|
activityType String? // e.g. "Trimming", "Feeding", "Cleaning"
|
|
notes String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("time_logs")
|
|
}
|
|
|
|
// Daily Walkthrough Models
|
|
model DailyWalkthrough {
|
|
id String @id @default(uuid())
|
|
date DateTime @default(now())
|
|
completedBy String
|
|
user User @relation(fields: [completedBy], references: [id])
|
|
startTime DateTime @default(now())
|
|
endTime DateTime?
|
|
status WalkthroughStatus @default(IN_PROGRESS)
|
|
|
|
reservoirChecks ReservoirCheck[]
|
|
irrigationChecks IrrigationCheck[]
|
|
plantHealthChecks PlantHealthCheck[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("daily_walkthroughs")
|
|
}
|
|
|
|
model WalkthroughSettings {
|
|
id String @id @default("default")
|
|
|
|
// Photo Requirements
|
|
reservoirPhotos PhotoRequirement @default(OPTIONAL)
|
|
irrigationPhotos PhotoRequirement @default(OPTIONAL)
|
|
plantHealthPhotos PhotoRequirement @default(REQUIRED)
|
|
|
|
// Enabled Sections
|
|
enableReservoirs Boolean @default(true)
|
|
enableIrrigation Boolean @default(true)
|
|
enablePlantHealth Boolean @default(true)
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("walkthrough_settings")
|
|
}
|
|
|
|
enum PhotoRequirement {
|
|
REQUIRED
|
|
OPTIONAL
|
|
WEEKLY
|
|
ON_DEMAND
|
|
}
|
|
|
|
model ReservoirCheck {
|
|
id String @id @default(uuid())
|
|
walkthroughId String
|
|
walkthrough DailyWalkthrough @relation(fields: [walkthroughId], references: [id], onDelete: Cascade)
|
|
|
|
tankName String
|
|
tankType TankType
|
|
levelPercent Int
|
|
status TankStatus
|
|
photoUrl String?
|
|
notes String?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("reservoir_checks")
|
|
}
|
|
|
|
model IrrigationCheck {
|
|
id String @id @default(uuid())
|
|
walkthroughId String
|
|
walkthrough DailyWalkthrough @relation(fields: [walkthroughId], references: [id], onDelete: Cascade)
|
|
|
|
zoneName String // "Veg Upstairs", "Veg Downstairs", "Flower Upstairs", "Flower Downstairs"
|
|
drippersTotal Int
|
|
drippersWorking Int
|
|
drippersFailed String? // JSON array of failed dripper IDs
|
|
waterFlow Boolean
|
|
nutrientsMixed Boolean
|
|
scheduleActive Boolean
|
|
photoUrl String? // Photo of working system
|
|
issues String?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("irrigation_checks")
|
|
}
|
|
|
|
model PlantHealthCheck {
|
|
id String @id @default(uuid())
|
|
walkthroughId String
|
|
walkthrough DailyWalkthrough @relation(fields: [walkthroughId], references: [id], onDelete: Cascade)
|
|
|
|
zoneName String
|
|
healthStatus HealthStatus
|
|
pestsObserved Boolean
|
|
pestType String?
|
|
waterAccess AccessStatus
|
|
foodAccess AccessStatus
|
|
flaggedForAttention Boolean @default(false)
|
|
issuePhotoUrl String?
|
|
referencePhotoUrl String? // Photo of healthy system
|
|
notes String?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("plant_health_checks")
|
|
}
|
|
// Supply/Shopping List models
|
|
model SupplyItem {
|
|
id String @id @default(uuid())
|
|
name String
|
|
category SupplyCategory
|
|
quantity Int @default(0)
|
|
minThreshold Int @default(0)
|
|
unit String // "each", "box", "roll", "gallon", etc.
|
|
location String? // "Storage Room", "Bathroom", etc.
|
|
vendor String? // "Amazon", "Local", etc.
|
|
productUrl String? // Link to reorder
|
|
lastOrdered DateTime?
|
|
notes String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("supply_items")
|
|
}
|
|
|
|
enum SupplyCategory {
|
|
FILTER // Air filters, water filters
|
|
CLEANING // Cleaning supplies
|
|
PPE // Gloves, masks, suits
|
|
OFFICE // Paper, pens, etc.
|
|
BATHROOM // Toilet paper, soap, etc.
|
|
KITCHEN // Coffee, snacks, etc.
|
|
MAINTENANCE // Tools, parts, etc.
|
|
OTHER
|
|
}
|
|
|
|
// Plant Touch Points
|
|
model PlantTouchPoint {
|
|
id String @id @default(uuid())
|
|
type TouchType
|
|
notes String?
|
|
photoUrls String[] // Changed from single photoUrl to array
|
|
|
|
// Measurements
|
|
heightCm Float?
|
|
widthCm Float?
|
|
|
|
// IPM specific
|
|
ipmProduct String? // e.g., "Pyganic 5.0"
|
|
ipmDosage String? // e.g., "1 oz per gallon"
|
|
|
|
// Issues
|
|
issuesObserved Boolean @default(false)
|
|
issueType String?
|
|
|
|
batchId String
|
|
batch Batch @relation(fields: [batchId], references: [id])
|
|
|
|
createdBy String
|
|
user User @relation(fields: [createdBy], references: [id])
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("plant_touch_points")
|
|
}
|
|
|
|
enum TouchType {
|
|
WATER
|
|
FEED
|
|
PRUNE
|
|
TRAIN
|
|
INSPECT
|
|
IPM
|
|
TRANSPLANT
|
|
HARVEST
|
|
OTHER
|
|
}
|
|
|
|
model IPMSchedule {
|
|
id String @id @default(uuid())
|
|
batchId String @unique // One schedule per batch
|
|
batch Batch @relation(fields: [batchId], references: [id])
|
|
|
|
product String // "Pyganic 5.0"
|
|
intervalDays Int // 10
|
|
lastTreatment DateTime?
|
|
nextTreatment DateTime? // Calculated
|
|
|
|
isActive Boolean @default(true)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("ipm_schedules")
|
|
}
|
|
|
|
// --- Phase 3: Batch Lifecycle & Room Management ---
|
|
|
|
enum BatchSource {
|
|
CLONE
|
|
SEED
|
|
}
|
|
|
|
enum BatchStage {
|
|
CLONE_IN
|
|
VEGETATIVE
|
|
FLOWERING
|
|
HARVEST
|
|
DRYING
|
|
CURING
|
|
FINISHED
|
|
}
|
|
|
|
enum RoomStatus {
|
|
ACTIVE
|
|
CLEANING
|
|
MAINTENANCE
|
|
}
|
|
|
|
enum WeightType {
|
|
WET
|
|
BUCKED
|
|
FINAL_DRY
|
|
TRIM
|
|
WASTE
|
|
}
|
|
|
|
model WeightLog {
|
|
id String @id @default(uuid())
|
|
batchId String
|
|
batch Batch @relation(fields: [batchId], references: [id])
|
|
weightType WeightType
|
|
weight Float // grams
|
|
unit String @default("g")
|
|
notes String?
|
|
photos String[] // URLs
|
|
loggedBy String
|
|
user User @relation(fields: [loggedBy], references: [id])
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("weight_logs")
|
|
}
|
|
|
|
model BatchNote {
|
|
id String @id @default(uuid())
|
|
batchId String
|
|
batch Batch @relation(fields: [batchId], references: [id])
|
|
content String // markdown
|
|
authorId String
|
|
author User @relation(fields: [authorId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("batch_notes")
|
|
}
|
|
|
|
model BatchPhoto {
|
|
id String @id @default(uuid())
|
|
batchId String
|
|
batch Batch @relation(fields: [batchId], references: [id])
|
|
url String
|
|
caption String?
|
|
uploadedBy String
|
|
user User @relation(fields: [uploadedBy], references: [id])
|
|
createdAt DateTime @default(now())
|
|
|
|
@@map("batch_photos")
|
|
}
|
|
|
|
// ============================================
|
|
// FACILITY LAYOUT MODELS (Phase 7)
|
|
// ============================================
|
|
|
|
enum SectionType {
|
|
TABLE
|
|
RACK
|
|
TRAY
|
|
HANGER
|
|
FLOOR
|
|
}
|
|
|
|
model FacilityProperty {
|
|
id String @id @default(uuid())
|
|
name String
|
|
address String?
|
|
licenseNum String? // DCC License Number
|
|
buildings FacilityBuilding[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_properties")
|
|
}
|
|
|
|
model FacilityBuilding {
|
|
id String @id @default(uuid())
|
|
property FacilityProperty @relation(fields: [propertyId], references: [id], onDelete: Cascade)
|
|
propertyId String
|
|
name String
|
|
code String // Short code: "GROW1"
|
|
type String // CULTIVATION, PROCESSING, DRYING, STORAGE
|
|
floors FacilityFloor[]
|
|
layout Json? // Visual layout data
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_buildings")
|
|
}
|
|
|
|
model FacilityFloor {
|
|
id String @id @default(uuid())
|
|
building FacilityBuilding @relation(fields: [buildingId], references: [id], onDelete: Cascade)
|
|
buildingId String
|
|
name String // "Floor 1", "Ground Floor"
|
|
number Int // 1, 2, 3...
|
|
width Int // feet
|
|
height Int // feet
|
|
rooms FacilityRoom[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_floors")
|
|
}
|
|
|
|
model FacilityRoom {
|
|
id String @id @default(uuid())
|
|
floor FacilityFloor @relation(fields: [floorId], references: [id], onDelete: Cascade)
|
|
floorId String
|
|
name String
|
|
code String // "VEG-A"
|
|
type RoomType // VEG, FLOWER, etc.
|
|
posX Int // Position X in pixels
|
|
posY Int // Position Y in pixels
|
|
width Int // Width in pixels
|
|
height Int // Height in pixels
|
|
rotation Int @default(0)
|
|
color String? // Custom color override
|
|
sections FacilitySection[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_rooms")
|
|
}
|
|
|
|
model FacilitySection {
|
|
id String @id @default(uuid())
|
|
room FacilityRoom @relation(fields: [roomId], references: [id], onDelete: Cascade)
|
|
roomId String
|
|
name String // "Table 1"
|
|
code String // "T1"
|
|
type SectionType
|
|
posX Int // Relative to room
|
|
posY Int
|
|
width Int
|
|
height Int
|
|
rows Int // Grid rows
|
|
columns Int // Grid columns
|
|
spacing Int @default(12) // inches between positions
|
|
positions FacilityPosition[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_sections")
|
|
}
|
|
|
|
model FacilityPosition {
|
|
id String @id @default(uuid())
|
|
section FacilitySection @relation(fields: [sectionId], references: [id], onDelete: Cascade)
|
|
sectionId String
|
|
row Int
|
|
column Int
|
|
slot Int @default(1) // For multi-plant positions
|
|
status String @default("EMPTY") // EMPTY, OCCUPIED, RESERVED, DAMAGED
|
|
plant FacilityPlant?
|
|
|
|
@@unique([sectionId, row, column, slot])
|
|
@@map("facility_positions")
|
|
}
|
|
|
|
model FacilityPlant {
|
|
id String @id @default(uuid())
|
|
tagNumber String @unique // METRC tag
|
|
batchId String?
|
|
position FacilityPosition @relation(fields: [positionId], references: [id])
|
|
positionId String @unique
|
|
address String // Full hierarchical address
|
|
status String @default("ACTIVE") // ACTIVE, HARVESTED, DESTROYED, TRANSFERRED
|
|
history PlantLocationHistory[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("facility_plants")
|
|
}
|
|
|
|
model PlantLocationHistory {
|
|
id String @id @default(uuid())
|
|
plant FacilityPlant @relation(fields: [plantId], references: [id], onDelete: Cascade)
|
|
plantId String
|
|
fromAddress String?
|
|
toAddress String
|
|
movedById String // User ID
|
|
reason String? // TRANSPLANT, REORGANIZE, HARVEST
|
|
movedAt DateTime @default(now())
|
|
|
|
@@map("plant_location_history")
|
|
}
|
|
|
|
// =============================================================================
|
|
// PHASE 8: VISITOR MANAGEMENT
|
|
// =============================================================================
|
|
|
|
enum VisitorType {
|
|
VISITOR
|
|
CONTRACTOR
|
|
INSPECTOR
|
|
VENDOR
|
|
DELIVERY
|
|
OTHER
|
|
}
|
|
|
|
enum VisitorStatus {
|
|
PRE_REGISTERED
|
|
CHECKED_IN
|
|
CHECKED_OUT
|
|
DENIED
|
|
}
|
|
|
|
model Visitor {
|
|
id String @id @default(uuid())
|
|
name String
|
|
company String?
|
|
email String?
|
|
phone String?
|
|
photoUrl String?
|
|
idType String? // Driver License, Passport, etc.
|
|
idNumber String?
|
|
type VisitorType @default(VISITOR)
|
|
purpose String
|
|
ndaAccepted Boolean @default(false)
|
|
ndaAcceptedAt DateTime?
|
|
notes String?
|
|
logs VisitorLog[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("visitors")
|
|
}
|
|
|
|
model VisitorLog {
|
|
id String @id @default(uuid())
|
|
visitor Visitor @relation(fields: [visitorId], references: [id], onDelete: Cascade)
|
|
visitorId String
|
|
status VisitorStatus @default(CHECKED_IN)
|
|
entryTime DateTime @default(now())
|
|
exitTime DateTime?
|
|
escort User? @relation("VisitorEscort", fields: [escortId], references: [id])
|
|
escortId String?
|
|
approvedBy User? @relation("VisitorApprover", fields: [approvedById], references: [id])
|
|
approvedById String?
|
|
badgeNumber String?
|
|
badgeExpiry DateTime?
|
|
zones String[] // AccessZone IDs visited
|
|
signature String? // Base64 or URL
|
|
temperatureF Float? // Health screening
|
|
notes String?
|
|
|
|
@@map("visitor_logs")
|
|
}
|
|
|
|
model AccessZone {
|
|
id String @id @default(uuid())
|
|
name String
|
|
code String @unique
|
|
description String?
|
|
escortRequired Boolean @default(false)
|
|
badgeRequired Boolean @default(true)
|
|
ndaRequired Boolean @default(false)
|
|
allowedTypes VisitorType[]
|
|
maxOccupancy Int?
|
|
parentZoneId String?
|
|
parentZone AccessZone? @relation("ZoneHierarchy", fields: [parentZoneId], references: [id])
|
|
childZones AccessZone[] @relation("ZoneHierarchy")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("access_zones")
|
|
}
|
|
|
|
// =============================================================================
|
|
// PHASE 9: MESSAGING & ANNOUNCEMENTS
|
|
// =============================================================================
|
|
|
|
enum AnnouncementPriority {
|
|
INFO
|
|
WARNING
|
|
CRITICAL
|
|
}
|
|
|
|
model Announcement {
|
|
id String @id @default(uuid())
|
|
title String
|
|
body String
|
|
priority AnnouncementPriority @default(INFO)
|
|
requiresAck Boolean @default(false)
|
|
targetRoles String[] // Which roles see this
|
|
expiresAt DateTime?
|
|
createdBy User @relation("AnnouncementCreator", fields: [createdById], references: [id])
|
|
createdById String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
acks AnnouncementAck[]
|
|
|
|
@@map("announcements")
|
|
}
|
|
|
|
model AnnouncementAck {
|
|
id String @id @default(uuid())
|
|
announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade)
|
|
announcementId String
|
|
user User @relation("AnnouncementAcker", fields: [userId], references: [id])
|
|
userId String
|
|
readAt DateTime?
|
|
acknowledgedAt DateTime?
|
|
|
|
@@unique([announcementId, userId])
|
|
@@map("announcement_acks")
|
|
}
|
|
|
|
// Shift notes for handoff communication
|
|
model ShiftNote {
|
|
id String @id @default(uuid())
|
|
roomId String?
|
|
batchId String?
|
|
content String
|
|
importance String @default("NORMAL") // LOW, NORMAL, HIGH, URGENT
|
|
readBy String[] // User IDs who have read this
|
|
createdBy User @relation("ShiftNoteCreator", fields: [createdById], references: [id])
|
|
createdById String
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime? // Auto-expire old notes
|
|
|
|
@@map("shift_notes")
|
|
}
|
|
|
|
// =============================================================================
|
|
// PHASE 10: COMPLIANCE & AUDIT TRAIL
|
|
// =============================================================================
|
|
|
|
enum AuditAction {
|
|
CREATE
|
|
UPDATE
|
|
DELETE
|
|
LOGIN
|
|
LOGOUT
|
|
ACCESS
|
|
EXPORT
|
|
APPROVE
|
|
REJECT
|
|
}
|
|
|
|
// Immutable audit log - append only
|
|
model AuditLog {
|
|
id String @id @default(uuid())
|
|
userId String?
|
|
userName String? // Denormalized for persistence
|
|
action AuditAction
|
|
entity String // Model name: Batch, Task, Plant, etc.
|
|
entityId String?
|
|
entityName String? // Denormalized description
|
|
before Json? // State before change
|
|
after Json? // State after change
|
|
changes Json? // Only the changed fields
|
|
ipAddress String?
|
|
userAgent String?
|
|
sessionId String?
|
|
metadata Json? // Additional context
|
|
timestamp DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@index([entity, entityId])
|
|
@@index([timestamp])
|
|
@@map("audit_logs")
|
|
}
|
|
|
|
enum DocumentType {
|
|
SOP
|
|
POLICY
|
|
TRAINING
|
|
FORM
|
|
CHECKLIST
|
|
OTHER
|
|
}
|
|
|
|
enum DocumentStatus {
|
|
DRAFT
|
|
PENDING_REVIEW
|
|
APPROVED
|
|
ARCHIVED
|
|
}
|
|
|
|
model Document {
|
|
id String @id @default(uuid())
|
|
title String
|
|
type DocumentType
|
|
status DocumentStatus @default(DRAFT)
|
|
content String // Markdown or HTML
|
|
version Int @default(1)
|
|
effectiveDate DateTime?
|
|
expiresAt DateTime?
|
|
requiresAck Boolean @default(false)
|
|
targetRoles String[] // Who must read this
|
|
createdBy User @relation("DocumentCreator", fields: [createdById], references: [id])
|
|
createdById String
|
|
approvedBy User? @relation("DocumentApprover", fields: [approvedById], references: [id])
|
|
approvedById String?
|
|
approvedAt DateTime?
|
|
versions DocumentVersion[]
|
|
acks DocumentAck[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("documents")
|
|
}
|
|
|
|
model DocumentVersion {
|
|
id String @id @default(uuid())
|
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
|
documentId String
|
|
version Int
|
|
title String
|
|
content String
|
|
changedBy String // User ID
|
|
changeNotes String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([documentId, version])
|
|
@@map("document_versions")
|
|
}
|
|
|
|
model DocumentAck {
|
|
id String @id @default(uuid())
|
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
|
documentId String
|
|
userId String
|
|
version Int // Which version was acknowledged
|
|
acknowledgedAt DateTime @default(now())
|
|
|
|
@@unique([documentId, userId])
|
|
@@map("document_acks")
|
|
}
|
|
|
|
// =============================================================================
|
|
// PHASE 13: ADVANCED FEATURES
|
|
// =============================================================================
|
|
|
|
// ---------------------- Environmental Monitoring ----------------------
|
|
|
|
enum SensorType {
|
|
TEMPERATURE
|
|
HUMIDITY
|
|
CO2
|
|
LIGHT_PAR
|
|
LIGHT_LUX
|
|
PH
|
|
EC
|
|
VPD
|
|
SOIL_MOISTURE
|
|
AIR_FLOW
|
|
}
|
|
|
|
model Sensor {
|
|
id String @id @default(uuid())
|
|
name String
|
|
type SensorType
|
|
roomId String?
|
|
location String? // e.g., "North wall", "Canopy level"
|
|
deviceId String? // Hardware device ID
|
|
manufacturer String?
|
|
model String?
|
|
isActive Boolean @default(true)
|
|
minThreshold Float?
|
|
maxThreshold Float?
|
|
readings SensorReading[]
|
|
alerts EnvironmentAlert[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([roomId])
|
|
@@index([type])
|
|
@@map("sensors")
|
|
}
|
|
|
|
model SensorReading {
|
|
id String @id @default(uuid())
|
|
sensor Sensor @relation(fields: [sensorId], references: [id], onDelete: Cascade)
|
|
sensorId String
|
|
value Float
|
|
unit String // °F, %, ppm, μmol/m²/s, etc.
|
|
timestamp DateTime @default(now())
|
|
|
|
@@index([sensorId, timestamp])
|
|
@@map("sensor_readings")
|
|
}
|
|
|
|
enum AlertSeverity {
|
|
INFO
|
|
WARNING
|
|
CRITICAL
|
|
EMERGENCY
|
|
}
|
|
|
|
model EnvironmentAlert {
|
|
id String @id @default(uuid())
|
|
sensor Sensor? @relation(fields: [sensorId], references: [id])
|
|
sensorId String?
|
|
roomId String?
|
|
type String // TEMP_HIGH, HUMIDITY_LOW, CO2_HIGH, etc.
|
|
severity AlertSeverity
|
|
message String
|
|
value Float?
|
|
threshold Float?
|
|
acknowledgedAt DateTime?
|
|
acknowledgedBy String?
|
|
resolvedAt DateTime?
|
|
resolvedBy String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([roomId, createdAt])
|
|
@@index([severity])
|
|
@@map("environment_alerts")
|
|
}
|
|
|
|
// Environment targets per room/stage
|
|
model EnvironmentProfile {
|
|
id String @id @default(uuid())
|
|
name String
|
|
stage String? // CLONE, VEG, FLOWER, DRY, CURE
|
|
roomId String?
|
|
tempMinF Float?
|
|
tempMaxF Float?
|
|
humidityMin Float?
|
|
humidityMax Float?
|
|
co2Min Float?
|
|
co2Max Float?
|
|
lightHours Float?
|
|
vpdMin Float?
|
|
vpdMax Float?
|
|
isDefault Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("environment_profiles")
|
|
}
|
|
|
|
// ---------------------- Financial Tracking ----------------------
|
|
|
|
enum TransactionType {
|
|
EXPENSE
|
|
REVENUE
|
|
ADJUSTMENT
|
|
}
|
|
|
|
enum ExpenseCategory {
|
|
LABOR
|
|
NUTRIENTS
|
|
SUPPLIES
|
|
EQUIPMENT
|
|
UTILITIES
|
|
RENT
|
|
LICENSES
|
|
TESTING
|
|
PACKAGING
|
|
MARKETING
|
|
OTHER
|
|
}
|
|
|
|
model FinancialTransaction {
|
|
id String @id @default(uuid())
|
|
type TransactionType
|
|
category ExpenseCategory?
|
|
amount Float
|
|
description String
|
|
batchId String? // Link to batch for cost tracking
|
|
roomId String?
|
|
vendorId String?
|
|
invoiceNumber String?
|
|
date DateTime @default(now())
|
|
createdBy String
|
|
attachments String[] // URLs to receipts/invoices
|
|
tags String[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([type, date])
|
|
@@index([batchId])
|
|
@@index([category])
|
|
@@map("financial_transactions")
|
|
}
|
|
|
|
// Cost aggregation per batch
|
|
model BatchCost {
|
|
id String @id @default(uuid())
|
|
batchId String @unique
|
|
laborCost Float @default(0)
|
|
nutrientCost Float @default(0)
|
|
utilityCost Float @default(0)
|
|
supplyCost Float @default(0)
|
|
otherCost Float @default(0)
|
|
totalCost Float @default(0)
|
|
plantCount Int @default(0)
|
|
costPerPlant Float?
|
|
yieldGrams Float?
|
|
costPerGram Float?
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("batch_costs")
|
|
}
|
|
|
|
// Revenue tracking
|
|
model Sale {
|
|
id String @id @default(uuid())
|
|
batchId String?
|
|
product String
|
|
quantity Float
|
|
unit String // grams, ounces, units
|
|
pricePerUnit Float
|
|
totalPrice Float
|
|
customerId String?
|
|
invoiceId String?
|
|
date DateTime @default(now())
|
|
notes String?
|
|
createdBy String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([batchId])
|
|
@@index([date])
|
|
@@map("sales")
|
|
}
|
|
|
|
// ---------------------- AI/ML Predictions ----------------------
|
|
|
|
model YieldPrediction {
|
|
id String @id @default(uuid())
|
|
batchId String
|
|
predictedYield Float // grams
|
|
confidence Float // 0-1
|
|
factors Json? // Contributing factors
|
|
predictedAt DateTime @default(now())
|
|
actualYield Float? // Filled in after harvest
|
|
accuracy Float? // Calculated after actual
|
|
|
|
@@index([batchId])
|
|
@@map("yield_predictions")
|
|
}
|
|
|
|
model AnomalyDetection {
|
|
id String @id @default(uuid())
|
|
entityType String // Batch, Room, Sensor
|
|
entityId String
|
|
anomalyType String // GROWTH_SLOW, YIELD_LOW, ENV_UNSTABLE
|
|
severity String
|
|
description String
|
|
data Json?
|
|
isResolved Boolean @default(false)
|
|
detectedAt DateTime @default(now())
|
|
|
|
@@index([entityType, entityId])
|
|
@@map("anomaly_detections")
|
|
}
|